1#[cfg(test)]
16mod tests {
17 use risingwave_common::hash::VirtualNode;
18 use risingwave_meta_model::FragmentId;
19 use risingwave_meta_model::fragment::DistributionType;
20 use risingwave_meta_model::table::HandleConflictBehavior;
21 use risingwave_pb::catalog::subscription::SubscriptionState;
22 use risingwave_pb::catalog::{PbSinkType, StreamSourceInfo};
23 use risingwave_pb::common::{HostAddress, WorkerNode, WorkerType, worker_node};
24 use risingwave_pb::meta::SubscribeType;
25 use risingwave_pb::stream_plan::PbStreamNode;
26 use tokio::sync::{mpsc, oneshot};
27
28 use crate::controller::catalog::*;
29 use crate::manager::{LocalNotification, WorkerKey};
30 use crate::model::FragmentDownstreamRelation;
31 use crate::serving::ServingVnodeMapping;
32
33 const TEST_DATABASE_ID: DatabaseId = DatabaseId::new(1);
34 const TEST_SCHEMA_ID: SchemaId = SchemaId::new(2);
35 const TEST_OWNER_ID: UserId = UserId::new(1);
36
37 async fn insert_test_table(
38 txn: &DatabaseTransaction,
39 table_id: TableId,
40 name: &str,
41 table_type: TableType,
42 belongs_to_job_id: Option<JobId>,
43 definition: &str,
44 ) -> MetaResult<()> {
45 table::ActiveModel {
46 table_id: Set(table_id),
47 name: Set(name.to_owned()),
48 optional_associated_source_id: Set(None),
49 table_type: Set(table_type),
50 belongs_to_job_id: Set(belongs_to_job_id),
51 columns: Set(vec![].into()),
52 pk: Set(vec![].into()),
53 distribution_key: Set(Vec::<i32>::new().into()),
54 stream_key: Set(Vec::<i32>::new().into()),
55 append_only: Set(false),
56 fragment_id: Set(None),
57 vnode_col_index: Set(None),
58 row_id_index: Set(None),
59 value_indices: Set(Vec::<i32>::new().into()),
60 definition: Set(definition.to_owned()),
61 handle_pk_conflict_behavior: Set(HandleConflictBehavior::NoCheck),
62 version_column_indices: Set(None),
63 read_prefix_len_hint: Set(0),
64 watermark_indices: Set(Vec::<i32>::new().into()),
65 dist_key_in_pk: Set(Vec::<i32>::new().into()),
66 dml_fragment_id: Set(None),
67 cardinality: Set(None),
68 cleaned_by_watermark: Set(false),
69 description: Set(None),
70 version: Set(None),
71 retention_seconds: Set(None),
72 cdc_table_id: Set(None),
73 vnode_count: Set(1),
74 webhook_info: Set(None),
75 engine: Set(None),
76 clean_watermark_index_in_pk: Set(None),
77 clean_watermark_indices: Set(None),
78 refreshable: Set(false),
79 vector_index_info: Set(None),
80 cdc_table_type: Set(None),
81 }
82 .insert(txn)
83 .await?;
84 Ok(())
85 }
86
87 async fn insert_test_fragment(
88 txn: &DatabaseTransaction,
89 fragment_id: FragmentId,
90 job_id: JobId,
91 state_table_ids: TableIdArray,
92 ) -> MetaResult<()> {
93 fragment::ActiveModel {
94 fragment_id: Set(fragment_id),
95 job_id: Set(job_id),
96 fragment_type_mask: Set(0),
97 distribution_type: Set(fragment::DistributionType::Hash),
98 stream_node: Set(StreamNode::from(&PbStreamNode::default())),
99 state_table_ids: Set(state_table_ids),
100 upstream_fragment_id: Set(I32Array::default()),
101 vnode_count: Set(1),
102 parallelism: Set(None),
103 }
104 .insert(txn)
105 .await?;
106 Ok(())
107 }
108
109 async fn insert_test_streaming_job(
110 txn: &DatabaseTransaction,
111 name: &str,
112 has_result_table: bool,
113 policy: Option<CacheRefillPolicy>,
114 ) -> MetaResult<(JobId, Option<TableId>, TableId)> {
115 let object_type = if has_result_table {
116 ObjectType::Table
117 } else {
118 ObjectType::Sink
119 };
120 let job_id = CatalogController::create_object(
121 txn,
122 object_type,
123 TEST_OWNER_ID,
124 Some(TEST_DATABASE_ID),
125 Some(TEST_SCHEMA_ID),
126 )
127 .await?
128 .oid
129 .as_job_id();
130 let result_table_id = has_result_table.then_some(job_id.as_mv_table_id());
131 if let Some(table_id) = result_table_id {
132 insert_test_table(txn, table_id, name, TableType::MaterializedView, None, "").await?;
133 }
134
135 let internal_table_id = CatalogController::create_object(
136 txn,
137 ObjectType::Table,
138 TEST_OWNER_ID,
139 Some(TEST_DATABASE_ID),
140 Some(TEST_SCHEMA_ID),
141 )
142 .await?
143 .oid
144 .as_table_id();
145 insert_test_table(
146 txn,
147 internal_table_id,
148 &format!("__internal_{name}"),
149 TableType::Internal,
150 Some(job_id),
151 "",
152 )
153 .await?;
154
155 insert_test_streaming_job_model(txn, job_id, policy).await?;
156
157 Ok((job_id, result_table_id, internal_table_id))
158 }
159
160 async fn insert_test_streaming_job_model(
161 txn: &DatabaseTransaction,
162 job_id: JobId,
163 policy: Option<CacheRefillPolicy>,
164 ) -> MetaResult<()> {
165 streaming_job::ActiveModel {
166 job_id: Set(job_id),
167 job_status: Set(JobStatus::Created),
168 create_type: Set(CreateType::Foreground),
169 timezone: Set(None),
170 config_override: Set(policy.map(|policy| {
171 format!(
172 "[streaming.developer]\ncache_refill_policy = \"{}\"\n",
173 policy
174 )
175 })),
176 adaptive_parallelism_strategy: Set(None),
177 parallelism: Set(StreamingParallelism::Adaptive),
178 backfill_parallelism: Set(None),
179 backfill_adaptive_parallelism_strategy: Set(None),
180 backfill_orders: Set(None),
181 max_parallelism: Set(1),
182 specific_resource_group: Set(None),
183 is_serverless_backfill: Set(false),
184 refresh_interval_sec: Set(None),
185 }
186 .insert(txn)
187 .await?;
188
189 Ok(())
190 }
191
192 #[tokio::test]
193 async fn test_table_refill_catalog_snapshot_classifies_table_identity() -> MetaResult<()> {
194 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
195 let inner = mgr.inner.write().await;
196 let txn = inner.db.begin().await?;
197
198 let (mv_job, Some(mv_result), mv_internal) =
199 insert_test_streaming_job(&txn, "mv_both", true, Some(CacheRefillPolicy::Both)).await?
200 else {
201 unreachable!()
202 };
203 let (default_job, Some(_default_result), default_internal) =
204 insert_test_streaming_job(&txn, "mv_default", true, None).await?
205 else {
206 unreachable!()
207 };
208 let (sink_job, None, sink_internal) = insert_test_streaming_job(
209 &txn,
210 "sink_streaming",
211 false,
212 Some(CacheRefillPolicy::Streaming),
213 )
214 .await?
215 else {
216 unreachable!()
217 };
218
219 let result_fragment = FragmentId::new(100);
220 let internal_fragment = FragmentId::new(101);
221 let sink_fragment = FragmentId::new(102);
222 for (fragment_id, job_id, table_ids) in [
223 (result_fragment, mv_job, vec![mv_result, mv_internal]),
224 (internal_fragment, default_job, vec![default_internal]),
225 (sink_fragment, sink_job, vec![sink_internal]),
226 ] {
227 insert_test_fragment(&txn, fragment_id, job_id, TableIdArray(table_ids)).await?;
228 }
229 txn.commit().await?;
230 drop(inner);
231
232 let serving_infos = mgr.fragment_serving_infos().await?;
233 assert_eq!(serving_infos.len(), 3);
234 assert_eq!(
235 serving_infos[&result_fragment].result_table_id,
236 Some(mv_result)
237 );
238 assert_eq!(serving_infos[&internal_fragment].result_table_id, None);
239 assert_eq!(serving_infos[&sink_fragment].result_table_id, None);
240
241 let policies = mgr.table_cache_refill_policies_snapshot().await?;
242 assert_eq!(
243 policies
244 .table_policies
245 .into_iter()
246 .map(|policy| (policy.table_id, policy.policy))
247 .collect::<HashMap<_, _>>(),
248 HashMap::from([(
249 mv_result.as_raw_id(),
250 CacheRefillPolicy::Both.to_protobuf() as i32,
251 )])
252 );
253 assert_eq!(
254 policies
255 .internal_table_policies
256 .into_iter()
257 .map(|policy| (policy.table_id, policy.policy))
258 .collect::<HashMap<_, _>>(),
259 HashMap::from([
260 (
261 mv_internal.as_raw_id(),
262 CacheRefillPolicy::Both.to_protobuf() as i32,
263 ),
264 (
265 sink_internal.as_raw_id(),
266 CacheRefillPolicy::Streaming.to_protobuf() as i32,
267 ),
268 ])
269 );
270
271 Ok(())
272 }
273
274 #[tokio::test]
275 async fn test_foreground_creating_catalog_lifecycle() -> MetaResult<()> {
276 let env = MetaSrvEnv::for_test().await;
277 let (tx, mut notification_rx) = mpsc::unbounded_channel();
278 env.notification_manager().insert_sender(
279 SubscribeType::Frontend,
280 WorkerKey(HostAddress {
281 host: "localhost".to_owned(),
282 port: 1234,
283 }),
284 tx,
285 );
286 let mgr = CatalogController::new(env).await?;
287 let inner = mgr.inner.write().await;
288 let txn = inner.db.begin().await?;
289
290 let (job_id, Some(table_id), internal_table_id) =
292 insert_test_streaming_job(&txn, "creating_table", true, None).await?
293 else {
294 unreachable!()
295 };
296 let associated_source_id = CatalogController::create_object(
297 &txn,
298 ObjectType::Source,
299 TEST_OWNER_ID,
300 Some(TEST_DATABASE_ID),
301 Some(TEST_SCHEMA_ID),
302 )
303 .await?
304 .oid
305 .as_source_id();
306 Source::insert(source::ActiveModel::from(PbSource {
307 id: associated_source_id,
308 schema_id: TEST_SCHEMA_ID,
309 database_id: TEST_DATABASE_ID,
310 name: "creating_table_source".to_owned(),
311 owner: TEST_OWNER_ID as _,
312 optional_associated_table_id: Some(
313 risingwave_pb::catalog::source::OptionalAssociatedTableId::AssociatedTableId(
314 table_id,
315 ),
316 ),
317 ..Default::default()
318 }))
319 .exec(&txn)
320 .await?;
321 table::ActiveModel {
322 table_id: Set(table_id),
323 table_type: Set(TableType::Table),
324 optional_associated_source_id: Set(Some(associated_source_id)),
325 ..Default::default()
326 }
327 .update(&txn)
328 .await?;
329 streaming_job::ActiveModel {
330 job_id: Set(job_id),
331 job_status: Set(JobStatus::Initial),
332 ..Default::default()
333 }
334 .update(&txn)
335 .await?;
336
337 let (_primary_job_id, Some(primary_table_id), _) =
339 insert_test_streaming_job(&txn, "primary_table", true, None).await?
340 else {
341 unreachable!()
342 };
343 let index_job_id = CatalogController::create_object(
344 &txn,
345 ObjectType::Index,
346 TEST_OWNER_ID,
347 Some(TEST_DATABASE_ID),
348 Some(TEST_SCHEMA_ID),
349 )
350 .await?
351 .oid
352 .as_job_id();
353 let index_table_id = index_job_id.as_mv_table_id();
354 insert_test_table(
355 &txn,
356 index_table_id,
357 "creating_index",
358 TableType::Index,
359 None,
360 "",
361 )
362 .await?;
363 index::ActiveModel {
364 index_id: Set(index_job_id.as_index_id()),
365 name: Set("creating_index".to_owned()),
366 index_table_id: Set(index_table_id),
367 primary_table_id: Set(primary_table_id),
368 index_items: Set(vec![].into()),
369 index_column_properties: Set(None),
370 index_columns_len: Set(0),
371 }
372 .insert(&txn)
373 .await?;
374 insert_test_streaming_job_model(&txn, index_job_id, None).await?;
375 streaming_job::ActiveModel {
376 job_id: Set(index_job_id),
377 job_status: Set(JobStatus::Initial),
378 ..Default::default()
379 }
380 .update(&txn)
381 .await?;
382
383 let source_job_id = CatalogController::create_object(
385 &txn,
386 ObjectType::Source,
387 TEST_OWNER_ID,
388 Some(TEST_DATABASE_ID),
389 Some(TEST_SCHEMA_ID),
390 )
391 .await?
392 .oid
393 .as_job_id();
394 Source::insert(source::ActiveModel::from(PbSource {
395 id: source_job_id.as_shared_source_id(),
396 schema_id: TEST_SCHEMA_ID,
397 database_id: TEST_DATABASE_ID,
398 name: "creating_shared_source".to_owned(),
399 owner: TEST_OWNER_ID as _,
400 info: Some(StreamSourceInfo {
401 cdc_source_job: true,
402 ..Default::default()
403 }),
404 ..Default::default()
405 }))
406 .exec(&txn)
407 .await?;
408 insert_test_streaming_job_model(&txn, source_job_id, None).await?;
409 streaming_job::ActiveModel {
410 job_id: Set(source_job_id),
411 job_status: Set(JobStatus::Initial),
412 ..Default::default()
413 }
414 .update(&txn)
415 .await?;
416
417 let sink_job_id = CatalogController::create_object(
418 &txn,
419 ObjectType::Sink,
420 TEST_OWNER_ID,
421 Some(TEST_DATABASE_ID),
422 Some(TEST_SCHEMA_ID),
423 )
424 .await?
425 .oid
426 .as_job_id();
427 Sink::insert(sink::ActiveModel::from(PbSink {
428 id: sink_job_id.as_sink_id(),
429 schema_id: TEST_SCHEMA_ID,
430 database_id: TEST_DATABASE_ID,
431 name: "creating_sink".to_owned(),
432 owner: TEST_OWNER_ID as _,
433 sink_type: PbSinkType::AppendOnly as i32,
434 ..Default::default()
435 }))
436 .exec(&txn)
437 .await?;
438 insert_test_streaming_job_model(&txn, sink_job_id, None).await?;
439 streaming_job::ActiveModel {
440 job_id: Set(sink_job_id),
441 job_status: Set(JobStatus::Initial),
442 ..Default::default()
443 }
444 .update(&txn)
445 .await?;
446
447 txn.commit().await?;
448
449 let (catalog, _) = inner.snapshot().await?;
450 assert!(!catalog.2.iter().any(|table| table.id == table_id));
451 assert!(!catalog.2.iter().any(|table| table.id == internal_table_id));
452 assert!(!catalog.2.iter().any(|table| table.id == index_table_id));
453 assert!(
454 !catalog
455 .3
456 .iter()
457 .any(|source| source.id == associated_source_id)
458 );
459 assert!(
460 !catalog
461 .3
462 .iter()
463 .any(|source| source.id == source_job_id.as_shared_source_id())
464 );
465 assert!(
466 !catalog
467 .6
468 .iter()
469 .any(|index| index.id == index_job_id.as_index_id())
470 );
471 assert!(
472 !catalog
473 .4
474 .iter()
475 .any(|sink| sink.id == sink_job_id.as_sink_id())
476 );
477
478 drop(inner);
479
480 let downstreams = FragmentDownstreamRelation::new();
481 let mut add_notifications = vec![];
482 for creating_job_id in [job_id, index_job_id, source_job_id, sink_job_id] {
483 mgr.post_collect_job_fragments(creating_job_id, &downstreams, None, None, None, true)
484 .await?;
485 let notification = notification_rx
486 .recv()
487 .await
488 .expect("frontend should receive a creating notification")
489 .expect("creating notification should be valid");
490 assert_eq!(notification.operation(), NotificationOperation::Add);
491 let object_group = match notification.info {
492 Some(NotificationInfo::ObjectGroup(object_group)) => object_group,
493 other => panic!("unexpected notification: {other:?}"),
494 };
495 add_notifications.push(object_group);
496 }
497
498 assert!(add_notifications[0].objects.iter().any(|object| matches!(
499 &object.object_info,
500 Some(PbObjectInfo::Table(table)) if table.id == table_id
501 )));
502 assert!(add_notifications[0].objects.iter().any(|object| matches!(
503 &object.object_info,
504 Some(PbObjectInfo::Table(table)) if table.id == internal_table_id
505 )));
506 assert!(add_notifications[0].objects.iter().any(|object| matches!(
507 &object.object_info,
508 Some(PbObjectInfo::Source(source)) if source.id == associated_source_id
509 )));
510 assert!(add_notifications[1].objects.iter().any(|object| matches!(
511 &object.object_info,
512 Some(PbObjectInfo::Table(table)) if table.id == index_table_id
513 )));
514 assert!(add_notifications[1].objects.iter().any(|object| matches!(
515 &object.object_info,
516 Some(PbObjectInfo::Index(index)) if index.id == index_job_id.as_index_id()
517 )));
518 assert!(add_notifications[2].objects.iter().any(|object| matches!(
519 &object.object_info,
520 Some(PbObjectInfo::Source(source)) if source.id == source_job_id.as_shared_source_id()
521 )));
522 assert!(add_notifications[3].objects.iter().any(|object| matches!(
523 &object.object_info,
524 Some(PbObjectInfo::Sink(sink)) if sink.id == sink_job_id.as_sink_id()
525 )));
526
527 let inner = mgr.inner.write().await;
528 let (catalog, _) = inner.snapshot().await?;
529 assert!(catalog.2.iter().any(|table| table.id == table_id));
530 assert!(catalog.2.iter().any(|table| table.id == internal_table_id));
531 assert!(catalog.2.iter().any(|table| table.id == index_table_id));
532 assert!(
533 catalog
534 .3
535 .iter()
536 .any(|source| source.id == source_job_id.as_shared_source_id())
537 );
538 assert!(
539 catalog
540 .6
541 .iter()
542 .any(|index| index.id == index_job_id.as_index_id())
543 );
544 assert!(
545 catalog
546 .4
547 .iter()
548 .any(|sink| sink.id == sink_job_id.as_sink_id())
549 );
550
551 let txn = inner.db.begin().await?;
552 let (operation, _, _, _) = mgr.finish_streaming_job_inner(&txn, job_id).await?;
553 assert_eq!(operation, NotificationOperation::Update);
554 let (operation, _, _, _) = mgr.finish_streaming_job_inner(&txn, index_job_id).await?;
555 assert_eq!(operation, NotificationOperation::Update);
556 let (operation, _, _, _) = mgr.finish_streaming_job_inner(&txn, source_job_id).await?;
557 assert_eq!(operation, NotificationOperation::Update);
558 let (operation, _, _, _) = mgr.finish_streaming_job_inner(&txn, sink_job_id).await?;
559 assert_eq!(operation, NotificationOperation::Update);
560 txn.commit().await?;
561
562 Ok(())
563 }
564
565 #[tokio::test]
566 async fn test_alter_streaming_job_cache_refill_policy_notifies_hummock() -> MetaResult<()> {
567 let env = MetaSrvEnv::for_test().await;
568 let (tx, mut rx) = mpsc::unbounded_channel();
569 env.notification_manager().insert_sender(
570 SubscribeType::Hummock,
571 WorkerKey(HostAddress {
572 host: "localhost".to_owned(),
573 port: 1234,
574 }),
575 tx,
576 );
577 let mgr = CatalogController::new(env).await?;
578
579 let inner = mgr.inner.write().await;
580 let txn = inner.db.begin().await?;
581 let (_job, Some(result_table_id), internal_table_id) =
582 insert_test_streaming_job(&txn, "mv_cache_refill", true, None).await?
583 else {
584 unreachable!()
585 };
586 txn.commit().await?;
587 drop(inner);
588
589 mgr.alter_streaming_job_config(
590 result_table_id.as_job_id(),
591 HashMap::from([(
592 STREAMING_CACHE_REFILL_POLICY_CONFIG_PATH.to_owned(),
593 "\"both\"".to_owned(),
594 )]),
595 vec![],
596 )
597 .await?;
598
599 let response = rx
600 .recv()
601 .await
602 .expect("should receive hummock notification")
603 .expect("notification should be ok");
604 assert_eq!(response.operation(), NotificationOperation::Update);
605 let info = response.info;
606 let Some(NotificationInfo::TableRefillRuntimeConfig(config)) = info else {
607 panic!("unexpected notification: {:?}", info);
608 };
609 assert!(config.serving_table_vnode_mappings.is_none());
610 let policies = config
611 .table_cache_refill_policies
612 .expect("policy snapshot should be present");
613 assert_eq!(
614 policies
615 .table_policies
616 .into_iter()
617 .map(|policy| (policy.table_id, policy.policy))
618 .collect::<HashMap<_, _>>(),
619 HashMap::from([(
620 result_table_id.as_raw_id(),
621 CacheRefillPolicy::Both.to_protobuf() as i32,
622 )])
623 );
624 assert_eq!(
625 policies
626 .internal_table_policies
627 .into_iter()
628 .map(|policy| (policy.table_id, policy.policy))
629 .collect::<HashMap<_, _>>(),
630 HashMap::from([(
631 internal_table_id.as_raw_id(),
632 CacheRefillPolicy::Both.to_protobuf() as i32,
633 )])
634 );
635
636 Ok(())
637 }
638
639 async fn insert_dirty_creating_job_with_fragment(
640 mgr: &CatalogController,
641 fragment_id: FragmentId,
642 vnode_count: i32,
643 ) -> MetaResult<(JobId, TableId)> {
644 let inner = mgr.inner.write().await;
645 let txn = inner.db.begin().await?;
646 let job_obj = CatalogController::create_object(
647 &txn,
648 ObjectType::Table,
649 TEST_OWNER_ID,
650 Some(TEST_DATABASE_ID),
651 Some(TEST_SCHEMA_ID),
652 )
653 .await?;
654 let job_id = job_obj.oid.as_job_id();
655 let table_id = job_id.as_mv_table_id();
656 insert_test_table(
657 &txn,
658 table_id,
659 "mv_dirty_serving_mapping",
660 TableType::MaterializedView,
661 None,
662 "CREATE MATERIALIZED VIEW mv_dirty_serving_mapping AS SELECT 1",
663 )
664 .await?;
665 streaming_job::ActiveModel {
666 job_id: Set(job_id),
667 job_status: Set(JobStatus::Creating),
668 create_type: Set(CreateType::Foreground),
669 timezone: Set(None),
670 config_override: Set(None),
671 adaptive_parallelism_strategy: Set(None),
672 parallelism: Set(StreamingParallelism::Adaptive),
673 backfill_parallelism: Set(None),
674 backfill_adaptive_parallelism_strategy: Set(None),
675 backfill_orders: Set(None),
676 max_parallelism: Set(1),
677 specific_resource_group: Set(None),
678 is_serverless_backfill: Set(false),
679 refresh_interval_sec: Set(None),
680 }
681 .insert(&txn)
682 .await?;
683 fragment::ActiveModel {
684 fragment_id: Set(fragment_id),
685 job_id: Set(job_id),
686 fragment_type_mask: Set(0),
687 distribution_type: Set(DistributionType::Hash),
688 stream_node: Set(StreamNode::default()),
689 state_table_ids: Set(Vec::<TableId>::new().into()),
690 upstream_fragment_id: Set(Vec::<i32>::new().into()),
691 vnode_count: Set(vnode_count),
692 parallelism: Set(None),
693 }
694 .insert(&txn)
695 .await?;
696 txn.commit().await?;
697 drop(inner);
698
699 Ok((job_id, table_id))
700 }
701
702 #[tokio::test]
703 async fn test_dirty_cleanup_reconcile_removes_stale_serving_vnode_mapping() -> MetaResult<()> {
704 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
705 let fragment_id = FragmentId::new(42);
706 insert_dirty_creating_job_with_fragment(
707 &mgr,
708 fragment_id,
709 VirtualNode::COUNT_FOR_TEST as i32,
710 )
711 .await?;
712
713 let worker = WorkerNode {
714 id: WorkerId::new(1),
715 r#type: WorkerType::ComputeNode.into(),
716 host: Some(HostAddress {
717 host: "localhost".to_owned(),
718 port: 1,
719 }),
720 state: worker_node::State::Running as i32,
721 property: Some(worker_node::Property {
722 is_serving: true,
723 parallelism: 1,
724 ..Default::default()
725 }),
726 ..Default::default()
727 };
728 let serving_vnode_mapping = ServingVnodeMapping::default();
729 let initial_snapshot = mgr.fragment_serving_infos().await?;
730 serving_vnode_mapping.upsert(&initial_snapshot, std::slice::from_ref(&worker), None);
731 assert!(serving_vnode_mapping.all().contains_key(&fragment_id));
732
733 mgr.clean_dirty_creating_jobs(Some(TEST_DATABASE_ID))
734 .await?;
735 let current_snapshot = mgr.fragment_serving_infos().await?;
736 assert!(!current_snapshot.contains_key(&fragment_id));
737
738 serving_vnode_mapping.reconcile(¤t_snapshot, &[worker], None);
739 assert!(!serving_vnode_mapping.all().contains_key(&fragment_id));
740
741 Ok(())
742 }
743
744 #[tokio::test]
745 async fn test_database_func() -> MetaResult<()> {
746 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
747 let pb_database = PbDatabase {
748 name: "db1".to_owned(),
749 owner: TEST_OWNER_ID as _,
750 ..Default::default()
751 };
752 mgr.create_database(pb_database).await?;
753
754 let database_id: DatabaseId = Database::find()
755 .select_only()
756 .column(database::Column::DatabaseId)
757 .filter(database::Column::Name.eq("db1"))
758 .into_tuple()
759 .one(&mgr.inner.read().await.db)
760 .await?
761 .unwrap();
762
763 mgr.alter_name(ObjectType::Database, database_id, "db2")
764 .await?;
765 let database = Database::find_by_id(database_id)
766 .one(&mgr.inner.read().await.db)
767 .await?
768 .unwrap();
769 assert_eq!(database.name, "db2");
770
771 mgr.drop_object(ObjectType::Database, database_id, DropMode::Cascade)
772 .await?;
773
774 Ok(())
775 }
776
777 #[tokio::test]
778 async fn test_schema_func() -> MetaResult<()> {
779 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
780 let pb_schema = PbSchema {
781 database_id: TEST_DATABASE_ID,
782 name: "schema1".to_owned(),
783 owner: TEST_OWNER_ID as _,
784 ..Default::default()
785 };
786 mgr.create_schema(pb_schema.clone()).await?;
787 assert!(mgr.create_schema(pb_schema).await.is_err());
788
789 let schema_id: SchemaId = Schema::find()
790 .select_only()
791 .column(schema::Column::SchemaId)
792 .filter(schema::Column::Name.eq("schema1"))
793 .into_tuple()
794 .one(&mgr.inner.read().await.db)
795 .await?
796 .unwrap();
797
798 mgr.alter_name(ObjectType::Schema, schema_id, "schema2")
799 .await?;
800 let schema = Schema::find_by_id(schema_id)
801 .one(&mgr.inner.read().await.db)
802 .await?
803 .unwrap();
804 assert_eq!(schema.name, "schema2");
805 mgr.drop_object(ObjectType::Schema, schema_id, DropMode::Restrict)
806 .await?;
807
808 Ok(())
809 }
810
811 #[tokio::test]
812 async fn test_create_view() -> MetaResult<()> {
813 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
814 let pb_view = PbView {
815 schema_id: TEST_SCHEMA_ID,
816 database_id: TEST_DATABASE_ID,
817 name: "view".to_owned(),
818 owner: TEST_OWNER_ID as _,
819 sql: "CREATE VIEW view AS SELECT 1".to_owned(),
820 ..Default::default()
821 };
822 mgr.create_view(pb_view.clone(), HashSet::new()).await?;
823 assert!(mgr.create_view(pb_view, HashSet::new()).await.is_err());
824
825 let view = View::find().one(&mgr.inner.read().await.db).await?.unwrap();
826 mgr.drop_object(ObjectType::View, view.view_id, DropMode::Cascade)
827 .await?;
828 assert!(
829 View::find_by_id(view.view_id)
830 .one(&mgr.inner.read().await.db)
831 .await?
832 .is_none()
833 );
834
835 Ok(())
836 }
837
838 #[tokio::test]
839 async fn test_create_function() -> MetaResult<()> {
840 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
841 let test_data_type = risingwave_pb::data::DataType {
842 type_name: risingwave_pb::data::data_type::TypeName::Int32 as _,
843 ..Default::default()
844 };
845 let arg_types = vec![test_data_type.clone()];
846 let pb_function = PbFunction {
847 schema_id: TEST_SCHEMA_ID,
848 database_id: TEST_DATABASE_ID,
849 name: "test_function".to_owned(),
850 owner: TEST_OWNER_ID as _,
851 arg_types,
852 return_type: Some(test_data_type.clone()),
853 language: "python".to_owned(),
854 kind: Some(risingwave_pb::catalog::function::Kind::Scalar(
855 Default::default(),
856 )),
857 ..Default::default()
858 };
859 mgr.create_function(pb_function.clone()).await?;
860 assert!(mgr.create_function(pb_function).await.is_err());
861
862 let function = Function::find()
863 .inner_join(Object)
864 .filter(
865 object::Column::DatabaseId
866 .eq(TEST_DATABASE_ID)
867 .and(object::Column::SchemaId.eq(TEST_SCHEMA_ID))
868 .add(function::Column::Name.eq("test_function")),
869 )
870 .one(&mgr.inner.read().await.db)
871 .await?
872 .unwrap();
873 assert_eq!(function.return_type.to_protobuf(), test_data_type);
874 assert_eq!(function.arg_types.to_protobuf().len(), 1);
875 assert_eq!(function.language, "python");
876
877 mgr.drop_object(
878 ObjectType::Function,
879 function.function_id,
880 DropMode::Restrict,
881 )
882 .await?;
883 assert!(
884 Object::find_by_id(function.function_id)
885 .one(&mgr.inner.read().await.db)
886 .await?
887 .is_none()
888 );
889
890 Ok(())
891 }
892
893 #[tokio::test]
894 async fn test_alter_relation_rename() -> MetaResult<()> {
895 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
896 let pb_source = PbSource {
897 schema_id: TEST_SCHEMA_ID,
898 database_id: TEST_DATABASE_ID,
899 name: "s1".to_owned(),
900 owner: TEST_OWNER_ID as _,
901 definition: r#"CREATE SOURCE s1 (v1 int) with (
902 connector = 'kafka',
903 topic = 'kafka_alter',
904 properties.bootstrap.server = 'message_queue:29092',
905 scan.startup.mode = 'earliest'
906) FORMAT PLAIN ENCODE JSON"#
907 .to_owned(),
908 info: Some(StreamSourceInfo {
909 ..Default::default()
910 }),
911 ..Default::default()
912 };
913 mgr.create_source(pb_source).await?;
914 let source_id: SourceId = Source::find()
915 .select_only()
916 .column(source::Column::SourceId)
917 .filter(source::Column::Name.eq("s1"))
918 .into_tuple()
919 .one(&mgr.inner.read().await.db)
920 .await?
921 .unwrap();
922
923 let pb_view = PbView {
924 schema_id: TEST_SCHEMA_ID,
925 database_id: TEST_DATABASE_ID,
926 name: "view_1".to_owned(),
927 owner: TEST_OWNER_ID as _,
928 sql: "CREATE VIEW view_1 AS SELECT v1 FROM s1".to_owned(),
929 ..Default::default()
930 };
931 mgr.create_view(pb_view, HashSet::from([source_id.as_object_id()]))
932 .await?;
933 let view_id: ViewId = View::find()
934 .select_only()
935 .column(view::Column::ViewId)
936 .filter(view::Column::Name.eq("view_1"))
937 .into_tuple()
938 .one(&mgr.inner.read().await.db)
939 .await?
940 .unwrap();
941
942 mgr.alter_name(ObjectType::Source, source_id, "s2").await?;
943 let source = Source::find_by_id(source_id)
944 .one(&mgr.inner.read().await.db)
945 .await?
946 .unwrap();
947 assert_eq!(source.name, "s2");
948 assert_eq!(
949 source.definition,
950 "CREATE SOURCE s2 (v1 INT) WITH (\
951 connector = 'kafka', \
952 topic = 'kafka_alter', \
953 properties.bootstrap.server = 'message_queue:29092', \
954 scan.startup.mode = 'earliest'\
955) FORMAT PLAIN ENCODE JSON"
956 );
957
958 let view = View::find_by_id(view_id)
959 .one(&mgr.inner.read().await.db)
960 .await?
961 .unwrap();
962 assert_eq!(
963 view.definition,
964 "CREATE VIEW view_1 AS SELECT v1 FROM s2 AS s1"
965 );
966
967 mgr.drop_object(ObjectType::Source, source_id, DropMode::Cascade)
968 .await?;
969 assert!(
970 View::find_by_id(view_id)
971 .one(&mgr.inner.read().await.db)
972 .await?
973 .is_none()
974 );
975
976 Ok(())
977 }
978
979 #[tokio::test]
980 async fn test_cancel_creating_table_deletes_associated_source() -> MetaResult<()> {
981 let env = MetaSrvEnv::for_test().await;
982 let (tx, mut notification_rx) = mpsc::unbounded_channel();
983 env.notification_manager().insert_sender(
984 SubscribeType::Frontend,
985 WorkerKey(HostAddress {
986 host: "localhost".to_owned(),
987 port: 1234,
988 }),
989 tx,
990 );
991 let mgr = CatalogController::new(env).await?;
992
993 let mut inner = mgr.inner.write().await;
994 let txn = inner.db.begin().await?;
995 let source_obj = CatalogController::create_object(
996 &txn,
997 ObjectType::Source,
998 TEST_OWNER_ID,
999 Some(TEST_DATABASE_ID),
1000 Some(TEST_SCHEMA_ID),
1001 )
1002 .await?;
1003 Source::insert(source::ActiveModel::from(PbSource {
1004 id: source_obj.oid.as_source_id(),
1005 schema_id: TEST_SCHEMA_ID,
1006 database_id: TEST_DATABASE_ID,
1007 name: "source_abort_initial".to_owned(),
1008 owner: TEST_OWNER_ID as _,
1009 ..Default::default()
1010 }))
1011 .exec(&txn)
1012 .await?;
1013 let obj = CatalogController::create_object(
1014 &txn,
1015 ObjectType::Table,
1016 TEST_OWNER_ID,
1017 Some(TEST_DATABASE_ID),
1018 Some(TEST_SCHEMA_ID),
1019 )
1020 .await?;
1021 let job_id = obj.oid.as_job_id();
1022
1023 table::ActiveModel {
1024 table_id: Set(obj.oid.as_table_id()),
1025 name: Set("table_abort_initial".to_owned()),
1026 optional_associated_source_id: Set(Some(source_obj.oid.as_source_id())),
1027 table_type: Set(TableType::Table),
1028 belongs_to_job_id: Set(None),
1029 columns: Set(vec![].into()),
1030 pk: Set(vec![].into()),
1031 distribution_key: Set(Vec::<i32>::new().into()),
1032 stream_key: Set(Vec::<i32>::new().into()),
1033 append_only: Set(false),
1034 fragment_id: Set(None),
1035 vnode_col_index: Set(None),
1036 row_id_index: Set(None),
1037 value_indices: Set(Vec::<i32>::new().into()),
1038 definition: Set("CREATE TABLE table_abort_initial (v1 INT)".to_owned()),
1039 handle_pk_conflict_behavior: Set(HandleConflictBehavior::NoCheck),
1040 version_column_indices: Set(None),
1041 read_prefix_len_hint: Set(0),
1042 watermark_indices: Set(Vec::<i32>::new().into()),
1043 dist_key_in_pk: Set(Vec::<i32>::new().into()),
1044 dml_fragment_id: Set(None),
1045 cardinality: Set(None),
1046 cleaned_by_watermark: Set(false),
1047 description: Set(None),
1048 version: Set(None),
1049 retention_seconds: Set(None),
1050 cdc_table_id: Set(None),
1051 vnode_count: Set(1),
1052 webhook_info: Set(None),
1053 engine: Set(None),
1054 clean_watermark_index_in_pk: Set(None),
1055 clean_watermark_indices: Set(None),
1056 refreshable: Set(false),
1057 vector_index_info: Set(None),
1058 cdc_table_type: Set(None),
1059 }
1060 .insert(&txn)
1061 .await?;
1062
1063 streaming_job::ActiveModel {
1064 job_id: Set(job_id),
1065 job_status: Set(JobStatus::Creating),
1066 create_type: Set(CreateType::Foreground),
1067 timezone: Set(None),
1068 config_override: Set(None),
1069 adaptive_parallelism_strategy: Set(None),
1070 parallelism: Set(StreamingParallelism::Adaptive),
1071 backfill_parallelism: Set(None),
1072 backfill_adaptive_parallelism_strategy: Set(None),
1073 backfill_orders: Set(None),
1074 max_parallelism: Set(1),
1075 specific_resource_group: Set(None),
1076 is_serverless_backfill: Set(false),
1077 refresh_interval_sec: Set(None),
1078 }
1079 .insert(&txn)
1080 .await?;
1081
1082 let (tx, rx) = oneshot::channel();
1083 inner.register_finish_notifier(TEST_DATABASE_ID, job_id, tx);
1084 txn.commit().await?;
1085 drop(inner);
1086
1087 let abort_result = mgr.try_abort_creating_streaming_job(job_id, true).await?;
1088 assert!(abort_result.aborted);
1089 assert_eq!(abort_result.database_id, Some(TEST_DATABASE_ID));
1090
1091 let err = rx
1092 .await
1093 .expect("finish notifier should be notified")
1094 .expect_err("creating job cancellation should fail the create wait");
1095 assert!(err.contains("cancelled"));
1096
1097 let db = &mgr.inner.read().await.db;
1098 assert!(Object::find_by_id(job_id).one(db).await?.is_none());
1099 assert!(StreamingJob::find_by_id(job_id).one(db).await?.is_none());
1100 assert!(
1101 Table::find_by_id(job_id.as_mv_table_id())
1102 .one(db)
1103 .await?
1104 .is_none()
1105 );
1106 assert!(
1107 Source::find_by_id(source_obj.oid.as_source_id())
1108 .one(db)
1109 .await?
1110 .is_none()
1111 );
1112
1113 let notification = notification_rx
1114 .recv()
1115 .await
1116 .expect("frontend should receive an abort notification")
1117 .expect("abort notification should be valid");
1118 assert_eq!(notification.operation(), NotificationOperation::Delete);
1119 let object_group = match notification.info {
1120 Some(NotificationInfo::ObjectGroup(object_group)) => object_group,
1121 other => panic!("unexpected notification: {other:?}"),
1122 };
1123 assert!(object_group.objects.iter().any(|object| matches!(
1124 &object.object_info,
1125 Some(PbObjectInfo::Table(table)) if table.id == job_id.as_mv_table_id()
1126 )));
1127 assert!(object_group.objects.iter().any(|object| matches!(
1128 &object.object_info,
1129 Some(PbObjectInfo::Source(source)) if source.id == source_obj.oid.as_source_id()
1130 )));
1131
1132 Ok(())
1133 }
1134
1135 #[tokio::test]
1136 async fn test_clean_dirty_creating_jobs_records_dropped_tables_for_per_db_recovery()
1137 -> MetaResult<()> {
1138 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
1139
1140 let inner = mgr.inner.write().await;
1141 let txn = inner.db.begin().await?;
1142 let mv_obj = CatalogController::create_object(
1143 &txn,
1144 ObjectType::Table,
1145 TEST_OWNER_ID,
1146 Some(TEST_DATABASE_ID),
1147 Some(TEST_SCHEMA_ID),
1148 )
1149 .await?;
1150 let job_id = mv_obj.oid.as_job_id();
1151 let mv_table_id = job_id.as_mv_table_id();
1152 insert_test_table(
1153 &txn,
1154 mv_table_id,
1155 "mv_dirty",
1156 TableType::MaterializedView,
1157 None,
1158 "CREATE MATERIALIZED VIEW mv_dirty AS SELECT 1",
1159 )
1160 .await?;
1161
1162 let internal_obj = CatalogController::create_object(
1163 &txn,
1164 ObjectType::Table,
1165 TEST_OWNER_ID,
1166 Some(TEST_DATABASE_ID),
1167 Some(TEST_SCHEMA_ID),
1168 )
1169 .await?;
1170 let internal_table_id = internal_obj.oid.as_table_id();
1171 insert_test_table(
1172 &txn,
1173 internal_table_id,
1174 "__internal_mv_dirty",
1175 TableType::Internal,
1176 Some(job_id),
1177 "",
1178 )
1179 .await?;
1180
1181 streaming_job::ActiveModel {
1182 job_id: Set(job_id),
1183 job_status: Set(JobStatus::Creating),
1184 create_type: Set(CreateType::Foreground),
1185 timezone: Set(None),
1186 config_override: Set(None),
1187 adaptive_parallelism_strategy: Set(None),
1188 parallelism: Set(StreamingParallelism::Adaptive),
1189 backfill_parallelism: Set(None),
1190 backfill_adaptive_parallelism_strategy: Set(None),
1191 backfill_orders: Set(None),
1192 max_parallelism: Set(1),
1193 specific_resource_group: Set(None),
1194 is_serverless_backfill: Set(false),
1195 refresh_interval_sec: Set(None),
1196 }
1197 .insert(&txn)
1198 .await?;
1199 txn.commit().await?;
1200 drop(inner);
1201
1202 let cleaned = mgr
1203 .clean_dirty_creating_jobs(Some(TEST_DATABASE_ID))
1204 .await?;
1205 assert_eq!(cleaned.streaming_job_ids, vec![job_id]);
1206 assert!(cleaned.source_ids.is_empty());
1207 let mut dropped_table_ids = cleaned.dropped_table_ids;
1208 dropped_table_ids.sort_unstable();
1209 assert_eq!(dropped_table_ids, vec![mv_table_id, internal_table_id]);
1210
1211 let inner = mgr.inner.read().await;
1212 assert!(inner.dropped_tables.contains_key(&mv_table_id));
1213 assert!(inner.dropped_tables.contains_key(&internal_table_id));
1214 assert!(Object::find_by_id(job_id).one(&inner.db).await?.is_none());
1215 assert!(
1216 StreamingJob::find_by_id(job_id)
1217 .one(&inner.db)
1218 .await?
1219 .is_none()
1220 );
1221 assert!(
1222 Table::find_by_id(mv_table_id)
1223 .one(&inner.db)
1224 .await?
1225 .is_none()
1226 );
1227 assert!(
1228 Table::find_by_id(internal_table_id)
1229 .one(&inner.db)
1230 .await?
1231 .is_none()
1232 );
1233
1234 Ok(())
1235 }
1236
1237 #[tokio::test]
1238 async fn test_clean_dirty_creating_jobs_notifies_serving_mapping_fragment_delete()
1239 -> MetaResult<()> {
1240 let env = MetaSrvEnv::for_test().await;
1241 let (local_notification_tx, mut local_notification_rx) = mpsc::unbounded_channel();
1242 env.notification_manager()
1243 .insert_local_sender(local_notification_tx);
1244 let mgr = CatalogController::new(env).await?;
1245 let fragment_id = FragmentId::new(3);
1246 let (job_id, mv_table_id) =
1247 insert_dirty_creating_job_with_fragment(&mgr, fragment_id, 1).await?;
1248
1249 assert!(
1250 mgr.fragment_serving_infos()
1251 .await?
1252 .contains_key(&fragment_id)
1253 );
1254
1255 let cleaned = mgr
1256 .clean_dirty_creating_jobs(Some(TEST_DATABASE_ID))
1257 .await?;
1258 assert_eq!(cleaned.streaming_job_ids, vec![job_id]);
1259
1260 let inner = mgr.inner.read().await;
1261 assert!(Object::find_by_id(job_id).one(&inner.db).await?.is_none());
1262 assert!(
1263 StreamingJob::find_by_id(job_id)
1264 .one(&inner.db)
1265 .await?
1266 .is_none()
1267 );
1268 assert!(
1269 Table::find_by_id(mv_table_id)
1270 .one(&inner.db)
1271 .await?
1272 .is_none()
1273 );
1274 drop(inner);
1275 assert!(
1276 !mgr.fragment_serving_infos()
1277 .await?
1278 .contains_key(&fragment_id)
1279 );
1280
1281 let notification = local_notification_rx.try_recv().expect(
1282 "dirty-job cleanup must notify the serving mapping worker about deleted fragments",
1283 );
1284 match notification {
1285 LocalNotification::ServingFragmentMappingsDelete(fragment_ids) => {
1286 assert_eq!(fragment_ids, vec![fragment_id]);
1287 }
1288 notification => panic!("unexpected local notification: {notification:?}"),
1289 }
1290
1291 Ok(())
1292 }
1293
1294 #[tokio::test]
1295 async fn test_abort_creating_subscription_commits_delete() -> MetaResult<()> {
1296 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
1297 let pb_view = PbView {
1298 schema_id: TEST_SCHEMA_ID,
1299 database_id: TEST_DATABASE_ID,
1300 name: "subscription_dep_view".to_owned(),
1301 owner: TEST_OWNER_ID as _,
1302 sql: "CREATE VIEW subscription_dep_view AS SELECT 1".to_owned(),
1303 ..Default::default()
1304 };
1305 mgr.create_view(pb_view, HashSet::new()).await?;
1306
1307 let view_id: ViewId = View::find()
1308 .select_only()
1309 .column(view::Column::ViewId)
1310 .filter(view::Column::Name.eq("subscription_dep_view"))
1311 .into_tuple()
1312 .one(&mgr.inner.read().await.db)
1313 .await?
1314 .unwrap();
1315
1316 let mut pb_subscription = PbSubscription {
1317 name: "subscription_to_abort".to_owned(),
1318 definition: "CREATE SUBSCRIPTION subscription_to_abort FROM subscription_dep_view"
1319 .to_owned(),
1320 retention_seconds: 86400,
1321 database_id: TEST_DATABASE_ID,
1322 schema_id: TEST_SCHEMA_ID,
1323 dependent_table_id: view_id.as_object_id().as_table_id(),
1324 owner: TEST_OWNER_ID as _,
1325 subscription_state: SubscriptionState::Init as _,
1326 ..Default::default()
1327 };
1328 mgr.create_subscription_catalog(&mut pb_subscription)
1329 .await?;
1330
1331 mgr.try_abort_creating_subscription(pb_subscription.id)
1332 .await?;
1333
1334 assert!(
1335 Subscription::find_by_id(pb_subscription.id)
1336 .one(&mgr.inner.read().await.db)
1337 .await?
1338 .is_none()
1339 );
1340
1341 Ok(())
1342 }
1343
1344 #[tokio::test]
1345 async fn test_drop_table_cascade_drops_dependent_subscription() -> MetaResult<()> {
1346 let mgr = CatalogController::new(MetaSrvEnv::for_test().await).await?;
1347
1348 let inner = mgr.inner.write().await;
1349 let txn = inner.db.begin().await?;
1350 let table_obj = CatalogController::create_object(
1351 &txn,
1352 ObjectType::Table,
1353 TEST_OWNER_ID,
1354 Some(TEST_DATABASE_ID),
1355 Some(TEST_SCHEMA_ID),
1356 )
1357 .await?;
1358 let table_id = table_obj.oid.as_table_id();
1359 insert_test_table(
1360 &txn,
1361 table_id,
1362 "subscription_dep_table",
1363 TableType::Table,
1364 None,
1365 "CREATE TABLE subscription_dep_table (v1 INT)",
1366 )
1367 .await?;
1368 txn.commit().await?;
1369 drop(inner);
1370
1371 let mut pb_subscription = PbSubscription {
1372 name: "subscription_to_drop_with_table".to_owned(),
1373 definition:
1374 "CREATE SUBSCRIPTION subscription_to_drop_with_table FROM subscription_dep_table"
1375 .to_owned(),
1376 retention_seconds: 86400,
1377 database_id: TEST_DATABASE_ID,
1378 schema_id: TEST_SCHEMA_ID,
1379 dependent_table_id: table_id,
1380 owner: TEST_OWNER_ID as _,
1381 subscription_state: SubscriptionState::Created as _,
1382 ..Default::default()
1383 };
1384 mgr.create_subscription_catalog(&mut pb_subscription)
1385 .await?;
1386
1387 mgr.drop_object(ObjectType::Table, table_id, DropMode::Cascade)
1388 .await?;
1389
1390 let db = &mgr.inner.read().await.db;
1391 assert!(Table::find_by_id(table_id).one(db).await?.is_none());
1392 assert!(
1393 Object::find_by_id(table_id.as_object_id())
1394 .one(db)
1395 .await?
1396 .is_none()
1397 );
1398 assert!(
1399 Subscription::find_by_id(pb_subscription.id)
1400 .one(db)
1401 .await?
1402 .is_none()
1403 );
1404 assert!(
1405 Object::find_by_id(pb_subscription.id.as_object_id())
1406 .one(db)
1407 .await?
1408 .is_none()
1409 );
1410
1411 Ok(())
1412 }
1413}