Skip to main content

risingwave_frontend/catalog/
catalog_service.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17
18use anyhow::anyhow;
19use parking_lot::lock_api::ArcRwLockReadGuard;
20use parking_lot::{RawRwLock, RwLock};
21use risingwave_common::catalog::{
22    AlterDatabaseParam, CatalogVersion, FunctionId, IndexId, ObjectId,
23};
24use risingwave_common::id::{ConnectionId, JobId, SchemaId, SourceId, ViewId};
25use risingwave_common::system_param::AdaptiveParallelismStrategy;
26use risingwave_pb::catalog::{
27    PbComment, PbCreateType, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource,
28    PbSubscription, PbTable, PbView,
29};
30use risingwave_pb::ddl_service::create_iceberg_table_request::{PbSinkJobInfo, PbTableJobInfo};
31use risingwave_pb::ddl_service::replace_job_plan::{
32    ReplaceJob, ReplaceMaterializedView, ReplaceSink, ReplaceSource, ReplaceTable,
33};
34use risingwave_pb::ddl_service::{
35    PbTableJobType, StreamingJobResourceType, TableJobType, WaitVersion, alter_name_request,
36    alter_owner_request, alter_set_schema_request, alter_swap_rename_request,
37    create_connection_request, streaming_job_resource_type,
38};
39use risingwave_pb::meta::PbTableParallelism;
40use risingwave_pb::stream_plan::StreamFragmentGraph;
41use risingwave_rpc_client::MetaClient;
42use tokio::sync::watch::Receiver;
43
44use super::root_catalog::Catalog;
45use super::{DatabaseId, SecretId, SinkId, SubscriptionId, TableId};
46use crate::error::Result;
47use crate::scheduler::HummockSnapshotManagerRef;
48use crate::session::current::notice_to_user;
49use crate::user::UserId;
50
51pub type CatalogReadGuard = ArcRwLockReadGuard<RawRwLock, Catalog>;
52
53/// [`CatalogReader`] can read catalog from local catalog and force the holder can not modify it.
54#[derive(Clone)]
55pub struct CatalogReader(Arc<RwLock<Catalog>>);
56
57impl CatalogReader {
58    pub fn new(inner: Arc<RwLock<Catalog>>) -> Self {
59        CatalogReader(inner)
60    }
61
62    pub fn read_guard(&self) -> CatalogReadGuard {
63        // Make this recursive so that one can get this guard in the same thread without fear.
64        self.0.read_arc_recursive()
65    }
66}
67
68/// [`CatalogWriter`] initiate DDL operations (create table/schema/database/function/connection).
69/// It will only send rpc to meta and get the catalog version as response.
70/// Then it will wait for the local catalog to be synced to the version, which is performed by
71/// [observer](`crate::observer::FrontendObserverNode`).
72#[async_trait::async_trait]
73pub trait CatalogWriter: Send + Sync {
74    async fn create_database(
75        &self,
76        db_name: &str,
77        owner: UserId,
78        resource_group: &str,
79        barrier_interval_ms: Option<u32>,
80        checkpoint_frequency: Option<u64>,
81    ) -> Result<()>;
82
83    async fn create_schema(
84        &self,
85        db_id: DatabaseId,
86        schema_name: &str,
87        owner: UserId,
88    ) -> Result<()>;
89
90    async fn create_view(&self, view: PbView, dependencies: HashSet<ObjectId>) -> Result<()>;
91
92    async fn create_materialized_view(
93        &self,
94        table: PbTable,
95        graph: StreamFragmentGraph,
96        dependencies: HashSet<ObjectId>,
97        resource_type: streaming_job_resource_type::ResourceType,
98        if_not_exists: bool,
99        refresh_interval_sec: Option<u64>,
100    ) -> Result<()>;
101
102    async fn replace_materialized_view(
103        &self,
104        table: PbTable,
105        graph: StreamFragmentGraph,
106    ) -> Result<()>;
107
108    async fn create_table(
109        &self,
110        source: Option<PbSource>,
111        table: PbTable,
112        graph: StreamFragmentGraph,
113        job_type: PbTableJobType,
114        if_not_exists: bool,
115        dependencies: HashSet<ObjectId>,
116    ) -> Result<()>;
117
118    async fn replace_table(
119        &self,
120        source: Option<PbSource>,
121        table: PbTable,
122        graph: StreamFragmentGraph,
123        job_type: TableJobType,
124    ) -> Result<()>;
125
126    async fn replace_source(&self, source: PbSource, graph: StreamFragmentGraph) -> Result<()>;
127
128    async fn create_index(
129        &self,
130        index: PbIndex,
131        table: PbTable,
132        graph: StreamFragmentGraph,
133        resource_type: streaming_job_resource_type::ResourceType,
134        if_not_exists: bool,
135    ) -> Result<()>;
136
137    async fn create_source(
138        &self,
139        source: PbSource,
140        graph: Option<StreamFragmentGraph>,
141        if_not_exists: bool,
142    ) -> Result<()>;
143
144    async fn create_sink(
145        &self,
146        sink: PbSink,
147        graph: StreamFragmentGraph,
148        dependencies: HashSet<ObjectId>,
149        resource_type: streaming_job_resource_type::ResourceType,
150        if_not_exists: bool,
151        since_timestamp_epoch: Option<u64>,
152    ) -> Result<()>;
153
154    async fn replace_sink(
155        &self,
156        old_sink_id: SinkId,
157        sink: PbSink,
158        graph: StreamFragmentGraph,
159        dependencies: HashSet<ObjectId>,
160        resource_type: streaming_job_resource_type::ResourceType,
161    ) -> Result<()>;
162
163    async fn create_subscription(&self, subscription: PbSubscription) -> Result<()>;
164
165    async fn create_function(&self, function: PbFunction) -> Result<()>;
166
167    async fn create_connection(
168        &self,
169        connection_name: String,
170        database_id: DatabaseId,
171        schema_id: SchemaId,
172        owner_id: UserId,
173        connection: create_connection_request::Payload,
174    ) -> Result<()>;
175
176    async fn create_secret(
177        &self,
178        secret_name: String,
179        database_id: DatabaseId,
180        schema_id: SchemaId,
181        owner_id: UserId,
182        payload: Vec<u8>,
183    ) -> Result<()>;
184
185    async fn comment_on(&self, comment: PbComment) -> Result<()>;
186
187    async fn drop_table(
188        &self,
189        source_id: Option<SourceId>,
190        table_id: TableId,
191        cascade: bool,
192    ) -> Result<()>;
193
194    async fn drop_materialized_view(&self, table_id: TableId, cascade: bool) -> Result<()>;
195
196    async fn drop_view(&self, view_id: ViewId, cascade: bool) -> Result<()>;
197
198    async fn drop_source(&self, source_id: SourceId, cascade: bool) -> Result<()>;
199
200    async fn reset_source(&self, source_id: SourceId) -> Result<()>;
201
202    async fn drop_sink(&self, sink_id: SinkId, cascade: bool) -> Result<()>;
203
204    async fn drop_subscription(&self, subscription_id: SubscriptionId, cascade: bool)
205    -> Result<()>;
206
207    async fn drop_database(&self, database_id: DatabaseId) -> Result<()>;
208
209    async fn drop_schema(&self, schema_id: SchemaId, cascade: bool) -> Result<()>;
210
211    async fn drop_index(&self, index_id: IndexId, cascade: bool) -> Result<()>;
212
213    async fn drop_function(&self, function_id: FunctionId, cascade: bool) -> Result<()>;
214
215    async fn drop_connection(&self, connection_id: ConnectionId, cascade: bool) -> Result<()>;
216
217    async fn drop_secret(&self, secret_id: SecretId, cascade: bool) -> Result<()>;
218
219    async fn alter_secret(
220        &self,
221        secret_id: SecretId,
222        secret_name: String,
223        database_id: DatabaseId,
224        schema_id: SchemaId,
225        owner_id: UserId,
226        payload: Vec<u8>,
227    ) -> Result<()>;
228
229    async fn alter_subscription_retention(
230        &self,
231        subscription_id: SubscriptionId,
232        retention_seconds: u64,
233        definition: String,
234    ) -> Result<()>;
235
236    async fn alter_name(
237        &self,
238        object_id: alter_name_request::Object,
239        object_name: &str,
240    ) -> Result<()>;
241
242    async fn alter_owner(
243        &self,
244        object: alter_owner_request::Object,
245        owner_id: UserId,
246    ) -> Result<()>;
247
248    /// Replace the source in the catalog.
249    async fn alter_source(&self, source: PbSource) -> Result<()>;
250
251    async fn alter_parallelism(
252        &self,
253        job_id: JobId,
254        parallelism: PbTableParallelism,
255        adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
256        deferred: bool,
257    ) -> Result<()>;
258
259    async fn alter_backfill_parallelism(
260        &self,
261        job_id: JobId,
262        parallelism: Option<PbTableParallelism>,
263        adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
264        deferred: bool,
265    ) -> Result<()>;
266
267    async fn alter_config(
268        &self,
269        job_id: JobId,
270        entries_to_add: HashMap<String, String>,
271        keys_to_remove: Vec<String>,
272    ) -> Result<()>;
273
274    async fn alter_resource_group(
275        &self,
276        job_id: JobId,
277        resource_group: Option<String>,
278        deferred: bool,
279    ) -> Result<()>;
280
281    async fn alter_database_resource_group(
282        &self,
283        database_id: DatabaseId,
284        resource_group: Option<String>,
285        deferred: bool,
286    ) -> Result<()>;
287
288    async fn alter_set_schema(
289        &self,
290        object: alter_set_schema_request::Object,
291        new_schema_id: SchemaId,
292    ) -> Result<()>;
293
294    async fn alter_swap_rename(&self, object: alter_swap_rename_request::Object) -> Result<()>;
295
296    async fn alter_database_param(
297        &self,
298        database_id: DatabaseId,
299        param: AlterDatabaseParam,
300    ) -> Result<()>;
301
302    async fn create_iceberg_table(
303        &self,
304        table_job_info: PbTableJobInfo,
305        sink_job_info: PbSinkJobInfo,
306        iceberg_source: PbSource,
307        if_not_exists: bool,
308    ) -> Result<()>;
309
310    async fn wait(&self, job_id: Option<JobId>) -> Result<()>;
311}
312
313#[derive(Clone)]
314pub struct CatalogWriterImpl {
315    meta_client: MetaClient,
316    catalog_updated_rx: Receiver<CatalogVersion>,
317    hummock_snapshot_manager: HummockSnapshotManagerRef,
318}
319
320#[async_trait::async_trait]
321impl CatalogWriter for CatalogWriterImpl {
322    async fn create_database(
323        &self,
324        db_name: &str,
325        owner: UserId,
326        resource_group: &str,
327        barrier_interval_ms: Option<u32>,
328        checkpoint_frequency: Option<u64>,
329    ) -> Result<()> {
330        let version = self
331            .meta_client
332            .create_database(PbDatabase {
333                name: db_name.to_owned(),
334                id: 0.into(),
335                owner,
336                resource_group: resource_group.to_owned(),
337                barrier_interval_ms,
338                checkpoint_frequency,
339            })
340            .await?;
341        self.wait_version(version).await
342    }
343
344    async fn create_schema(
345        &self,
346        db_id: DatabaseId,
347        schema_name: &str,
348        owner: UserId,
349    ) -> Result<()> {
350        let version = self
351            .meta_client
352            .create_schema(PbSchema {
353                id: 0.into(),
354                name: schema_name.to_owned(),
355                database_id: db_id,
356                owner,
357            })
358            .await?;
359        self.wait_version(version).await
360    }
361
362    // TODO: maybe here to pass a materialize plan node
363    async fn create_materialized_view(
364        &self,
365        table: PbTable,
366        graph: StreamFragmentGraph,
367        dependencies: HashSet<ObjectId>,
368        resource_type: streaming_job_resource_type::ResourceType,
369        if_not_exists: bool,
370        refresh_interval_sec: Option<u64>,
371    ) -> Result<()> {
372        let create_type = table.get_create_type().unwrap_or(PbCreateType::Foreground);
373        let version = self
374            .meta_client
375            .create_materialized_view(
376                table,
377                graph,
378                dependencies,
379                resource_type,
380                if_not_exists,
381                refresh_interval_sec,
382            )
383            .await?;
384        if matches!(create_type, PbCreateType::Foreground) {
385            self.wait_version(version).await?
386        }
387        Ok(())
388    }
389
390    async fn replace_materialized_view(
391        &self,
392        table: PbTable,
393        graph: StreamFragmentGraph,
394    ) -> Result<()> {
395        // TODO: this is a dummy implementation for debugging only.
396        notice_to_user(format!("table: {table:#?}"));
397        notice_to_user(format!("graph: {graph:#?}"));
398
399        let version = self
400            .meta_client
401            .replace_job(
402                graph,
403                ReplaceJob::ReplaceMaterializedView(ReplaceMaterializedView { table: Some(table) }),
404            )
405            .await?;
406
407        self.wait_version(version).await
408    }
409
410    async fn create_view(&self, view: PbView, dependencies: HashSet<ObjectId>) -> Result<()> {
411        let version = self.meta_client.create_view(view, dependencies).await?;
412        self.wait_version(version).await
413    }
414
415    async fn create_index(
416        &self,
417        index: PbIndex,
418        table: PbTable,
419        graph: StreamFragmentGraph,
420        resource_type: streaming_job_resource_type::ResourceType,
421        if_not_exists: bool,
422    ) -> Result<()> {
423        let version = self
424            .meta_client
425            .create_index(index, table, graph, resource_type, if_not_exists)
426            .await?;
427        self.wait_version(version).await
428    }
429
430    async fn create_table(
431        &self,
432        source: Option<PbSource>,
433        table: PbTable,
434        graph: StreamFragmentGraph,
435        job_type: PbTableJobType,
436        if_not_exists: bool,
437        dependencies: HashSet<ObjectId>,
438    ) -> Result<()> {
439        let version = self
440            .meta_client
441            .create_table(source, table, graph, job_type, if_not_exists, dependencies)
442            .await?;
443        self.wait_version(version).await
444    }
445
446    async fn replace_table(
447        &self,
448        source: Option<PbSource>,
449        table: PbTable,
450        graph: StreamFragmentGraph,
451        job_type: TableJobType,
452    ) -> Result<()> {
453        let version = self
454            .meta_client
455            .replace_job(
456                graph,
457                ReplaceJob::ReplaceTable(ReplaceTable {
458                    source,
459                    table: Some(table),
460                    job_type: job_type as _,
461                }),
462            )
463            .await?;
464        self.wait_version(version).await
465    }
466
467    async fn replace_source(&self, source: PbSource, graph: StreamFragmentGraph) -> Result<()> {
468        let version = self
469            .meta_client
470            .replace_job(
471                graph,
472                ReplaceJob::ReplaceSource(ReplaceSource {
473                    source: Some(source),
474                }),
475            )
476            .await?;
477        self.wait_version(version).await
478    }
479
480    async fn create_source(
481        &self,
482        source: PbSource,
483        graph: Option<StreamFragmentGraph>,
484        if_not_exists: bool,
485    ) -> Result<()> {
486        let version = self
487            .meta_client
488            .create_source(source, graph, if_not_exists)
489            .await?;
490        self.wait_version(version).await
491    }
492
493    async fn create_sink(
494        &self,
495        sink: PbSink,
496        graph: StreamFragmentGraph,
497        dependencies: HashSet<ObjectId>,
498        resource_type: streaming_job_resource_type::ResourceType,
499        if_not_exists: bool,
500        since_timestamp_epoch: Option<u64>,
501    ) -> Result<()> {
502        let version = self
503            .meta_client
504            .create_sink(
505                sink,
506                graph,
507                dependencies,
508                resource_type,
509                if_not_exists,
510                since_timestamp_epoch,
511            )
512            .await?;
513        self.wait_version(version).await
514    }
515
516    async fn replace_sink(
517        &self,
518        old_sink_id: SinkId,
519        sink: PbSink,
520        graph: StreamFragmentGraph,
521        dependencies: HashSet<ObjectId>,
522        resource_type: streaming_job_resource_type::ResourceType,
523    ) -> Result<()> {
524        let version = self
525            .meta_client
526            .replace_job(
527                graph,
528                ReplaceJob::ReplaceSink(ReplaceSink {
529                    sink: Some(sink),
530                    old_sink_id,
531                    dependencies: dependencies.into_iter().collect(),
532                    resource_type: Some(StreamingJobResourceType {
533                        resource_type: Some(resource_type),
534                    }),
535                }),
536            )
537            .await?;
538        self.wait_version(version).await
539    }
540
541    async fn create_subscription(&self, subscription: PbSubscription) -> Result<()> {
542        let version = self.meta_client.create_subscription(subscription).await?;
543        self.wait_version(version).await
544    }
545
546    async fn create_function(&self, function: PbFunction) -> Result<()> {
547        let version = self.meta_client.create_function(function).await?;
548        self.wait_version(version).await
549    }
550
551    async fn create_connection(
552        &self,
553        connection_name: String,
554        database_id: DatabaseId,
555        schema_id: SchemaId,
556        owner_id: UserId,
557        connection: create_connection_request::Payload,
558    ) -> Result<()> {
559        let version = self
560            .meta_client
561            .create_connection(
562                connection_name,
563                database_id,
564                schema_id,
565                owner_id,
566                connection,
567            )
568            .await?;
569        self.wait_version(version).await
570    }
571
572    async fn create_secret(
573        &self,
574        secret_name: String,
575        database_id: DatabaseId,
576        schema_id: SchemaId,
577        owner_id: UserId,
578        payload: Vec<u8>,
579    ) -> Result<()> {
580        let version = self
581            .meta_client
582            .create_secret(secret_name, database_id, schema_id, owner_id, payload)
583            .await?;
584        self.wait_version(version).await
585    }
586
587    async fn comment_on(&self, comment: PbComment) -> Result<()> {
588        let version = self.meta_client.comment_on(comment).await?;
589        self.wait_version(version).await
590    }
591
592    async fn drop_table(
593        &self,
594        source_id: Option<SourceId>,
595        table_id: TableId,
596        cascade: bool,
597    ) -> Result<()> {
598        let version = self
599            .meta_client
600            .drop_table(source_id, table_id, cascade)
601            .await?;
602        self.wait_version(version).await
603    }
604
605    async fn drop_materialized_view(&self, table_id: TableId, cascade: bool) -> Result<()> {
606        let version = self
607            .meta_client
608            .drop_materialized_view(table_id, cascade)
609            .await?;
610        self.wait_version(version).await
611    }
612
613    async fn drop_view(&self, view_id: ViewId, cascade: bool) -> Result<()> {
614        let version = self.meta_client.drop_view(view_id, cascade).await?;
615        self.wait_version(version).await
616    }
617
618    async fn drop_source(&self, source_id: SourceId, cascade: bool) -> Result<()> {
619        let version = self.meta_client.drop_source(source_id, cascade).await?;
620        self.wait_version(version).await
621    }
622
623    async fn reset_source(&self, source_id: SourceId) -> Result<()> {
624        let version = self.meta_client.reset_source(source_id).await?;
625        self.wait_version(version).await
626    }
627
628    async fn drop_sink(&self, sink_id: SinkId, cascade: bool) -> Result<()> {
629        let version = self.meta_client.drop_sink(sink_id, cascade).await?;
630        self.wait_version(version).await
631    }
632
633    async fn drop_subscription(
634        &self,
635        subscription_id: SubscriptionId,
636        cascade: bool,
637    ) -> Result<()> {
638        let version = self
639            .meta_client
640            .drop_subscription(subscription_id, cascade)
641            .await?;
642        self.wait_version(version).await
643    }
644
645    async fn drop_index(&self, index_id: IndexId, cascade: bool) -> Result<()> {
646        let version = self.meta_client.drop_index(index_id, cascade).await?;
647        self.wait_version(version).await
648    }
649
650    async fn drop_function(&self, function_id: FunctionId, cascade: bool) -> Result<()> {
651        let version = self.meta_client.drop_function(function_id, cascade).await?;
652        self.wait_version(version).await
653    }
654
655    async fn drop_schema(&self, schema_id: SchemaId, cascade: bool) -> Result<()> {
656        let version = self.meta_client.drop_schema(schema_id, cascade).await?;
657        self.wait_version(version).await
658    }
659
660    async fn drop_database(&self, database_id: DatabaseId) -> Result<()> {
661        let version = self.meta_client.drop_database(database_id).await?;
662        self.wait_version(version).await
663    }
664
665    async fn drop_connection(&self, connection_id: ConnectionId, cascade: bool) -> Result<()> {
666        let version = self
667            .meta_client
668            .drop_connection(connection_id, cascade)
669            .await?;
670        self.wait_version(version).await
671    }
672
673    async fn drop_secret(&self, secret_id: SecretId, cascade: bool) -> Result<()> {
674        let version = self.meta_client.drop_secret(secret_id, cascade).await?;
675        self.wait_version(version).await
676    }
677
678    async fn alter_name(
679        &self,
680        object_id: alter_name_request::Object,
681        object_name: &str,
682    ) -> Result<()> {
683        let version = self.meta_client.alter_name(object_id, object_name).await?;
684        self.wait_version(version).await
685    }
686
687    async fn alter_owner(
688        &self,
689        object: alter_owner_request::Object,
690        owner_id: UserId,
691    ) -> Result<()> {
692        let version = self.meta_client.alter_owner(object, owner_id).await?;
693        self.wait_version(version).await
694    }
695
696    async fn alter_set_schema(
697        &self,
698        object: alter_set_schema_request::Object,
699        new_schema_id: SchemaId,
700    ) -> Result<()> {
701        let version = self
702            .meta_client
703            .alter_set_schema(object, new_schema_id)
704            .await?;
705        self.wait_version(version).await
706    }
707
708    async fn alter_source(&self, source: PbSource) -> Result<()> {
709        let version = self.meta_client.alter_source(source).await?;
710        self.wait_version(version).await
711    }
712
713    async fn alter_parallelism(
714        &self,
715        job_id: JobId,
716        parallelism: PbTableParallelism,
717        adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
718        deferred: bool,
719    ) -> Result<()> {
720        self.meta_client
721            .alter_parallelism(job_id, parallelism, adaptive_parallelism_strategy, deferred)
722            .await?;
723        Ok(())
724    }
725
726    async fn alter_backfill_parallelism(
727        &self,
728        job_id: JobId,
729        parallelism: Option<PbTableParallelism>,
730        adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
731        deferred: bool,
732    ) -> Result<()> {
733        self.meta_client
734            .alter_backfill_parallelism(
735                job_id,
736                parallelism,
737                adaptive_parallelism_strategy,
738                deferred,
739            )
740            .await?;
741        Ok(())
742    }
743
744    async fn alter_config(
745        &self,
746        job_id: JobId,
747        entries_to_add: HashMap<String, String>,
748        keys_to_remove: Vec<String>,
749    ) -> Result<()> {
750        self.meta_client
751            .alter_streaming_job_config(job_id, entries_to_add, keys_to_remove)
752            .await?;
753        Ok(())
754    }
755
756    async fn alter_swap_rename(&self, object: alter_swap_rename_request::Object) -> Result<()> {
757        let version = self.meta_client.alter_swap_rename(object).await?;
758        self.wait_version(version).await
759    }
760
761    async fn alter_secret(
762        &self,
763        secret_id: SecretId,
764        secret_name: String,
765        database_id: DatabaseId,
766        schema_id: SchemaId,
767        owner_id: UserId,
768        payload: Vec<u8>,
769    ) -> Result<()> {
770        let version = self
771            .meta_client
772            .alter_secret(
773                secret_id,
774                secret_name,
775                database_id,
776                schema_id,
777                owner_id,
778                payload,
779            )
780            .await?;
781        self.wait_version(version).await
782    }
783
784    async fn alter_subscription_retention(
785        &self,
786        subscription_id: SubscriptionId,
787        retention_seconds: u64,
788        definition: String,
789    ) -> Result<()> {
790        let version = self
791            .meta_client
792            .alter_subscription_retention(subscription_id, retention_seconds, definition)
793            .await?;
794        self.wait_version(version).await
795    }
796
797    async fn alter_resource_group(
798        &self,
799        job_id: JobId,
800        resource_group: Option<String>,
801        deferred: bool,
802    ) -> Result<()> {
803        self.meta_client
804            .alter_resource_group(job_id, resource_group, deferred)
805            .await
806            .map_err(|e| anyhow!(e))?;
807
808        Ok(())
809    }
810
811    async fn alter_database_resource_group(
812        &self,
813        database_id: DatabaseId,
814        resource_group: Option<String>,
815        deferred: bool,
816    ) -> Result<()> {
817        let version = self
818            .meta_client
819            .alter_database_resource_group(database_id, resource_group, deferred)
820            .await
821            .map_err(|e| anyhow!(e))?;
822        self.wait_version(version).await
823    }
824
825    async fn alter_database_param(
826        &self,
827        database_id: DatabaseId,
828        param: AlterDatabaseParam,
829    ) -> Result<()> {
830        let version = self
831            .meta_client
832            .alter_database_param(database_id, param)
833            .await
834            .map_err(|e| anyhow!(e))?;
835        self.wait_version(version).await
836    }
837
838    async fn create_iceberg_table(
839        &self,
840        table_job_info: PbTableJobInfo,
841        sink_job_info: PbSinkJobInfo,
842        iceberg_source: PbSource,
843        if_not_exists: bool,
844    ) -> Result<()> {
845        let version = Box::pin(self.meta_client.create_iceberg_table(
846            table_job_info,
847            sink_job_info,
848            iceberg_source,
849            if_not_exists,
850        ))
851        .await?;
852        self.wait_version(version).await
853    }
854
855    async fn wait(&self, job_id: Option<JobId>) -> Result<()> {
856        let version = self
857            .meta_client
858            .wait(job_id)
859            .await
860            .map_err(|e| anyhow!(e))?;
861        self.wait_version(version).await
862    }
863}
864
865impl CatalogWriterImpl {
866    pub fn new(
867        meta_client: MetaClient,
868        catalog_updated_rx: Receiver<CatalogVersion>,
869        hummock_snapshot_manager: HummockSnapshotManagerRef,
870    ) -> Self {
871        Self {
872            meta_client,
873            catalog_updated_rx,
874            hummock_snapshot_manager,
875        }
876    }
877
878    async fn wait_version(&self, version: WaitVersion) -> Result<()> {
879        let mut rx = self.catalog_updated_rx.clone();
880        while *rx.borrow_and_update() < version.catalog_version {
881            rx.changed().await.map_err(|e| anyhow!(e))?;
882        }
883        self.hummock_snapshot_manager
884            .wait(version.hummock_version_id)
885            .await;
886        Ok(())
887    }
888}