Skip to main content

risingwave_frontend/
test_utils.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::{BTreeMap, HashMap, HashSet};
16use std::io::Write;
17use std::net::{IpAddr, Ipv4Addr, SocketAddr};
18use std::sync::Arc;
19use std::sync::atomic::{AtomicU32, Ordering};
20
21use futures_async_stream::for_await;
22use parking_lot::RwLock;
23use pgwire::net::{Address, AddressRef};
24use pgwire::pg_response::StatementType;
25use pgwire::pg_server::{SessionId, SessionManager, UserAuthenticator};
26use pgwire::types::Row;
27use risingwave_common::catalog::{
28    AlterDatabaseParam, DEFAULT_DATABASE_NAME, DEFAULT_SCHEMA_NAME, DEFAULT_SUPER_USER,
29    DEFAULT_SUPER_USER_FOR_ADMIN, DEFAULT_SUPER_USER_FOR_ADMIN_ID, DEFAULT_SUPER_USER_ID,
30    FunctionId, IndexId, NON_RESERVED_USER_ID, ObjectId, PG_CATALOG_SCHEMA_NAME,
31    RW_CATALOG_SCHEMA_NAME, TableId,
32};
33use risingwave_common::config::FrontendConfig;
34use risingwave_common::hash::{VirtualNode, VnodeCount, VnodeCountCompat};
35use risingwave_common::id::{ConnectionId, JobId, SourceId, SubscriptionId, ViewId, WorkerId};
36use risingwave_common::session_config::SessionConfig;
37use risingwave_common::system_param::AdaptiveParallelismStrategy;
38use risingwave_common::system_param::reader::SystemParamsReader;
39use risingwave_common::util::cluster_limit::ClusterLimit;
40use risingwave_common::util::worker_util::DEFAULT_RESOURCE_GROUP;
41use risingwave_hummock_sdk::change_log::TableChangeLogs;
42use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta};
43use risingwave_hummock_sdk::{CompactionGroupId, HummockVersionId, INVALID_VERSION_ID};
44use risingwave_pb::backup_service::MetaSnapshotMetadata;
45use risingwave_pb::catalog::{
46    PbComment, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbStreamJobStatus,
47    PbSubscription, PbTable, PbView, Table,
48};
49use risingwave_pb::common::{PbObjectType, WorkerNode};
50use risingwave_pb::ddl_service::alter_owner_request::Object;
51use risingwave_pb::ddl_service::create_iceberg_table_request::{PbSinkJobInfo, PbTableJobInfo};
52use risingwave_pb::ddl_service::{
53    DdlProgress, PbTableJobType, RemoveIcebergTableOrphanFilesResponse, TableJobType,
54    alter_name_request, alter_set_schema_request, alter_swap_rename_request,
55    create_connection_request, streaming_job_resource_type,
56};
57use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config::MutableConfig as PbMutableConfig;
58use risingwave_pb::hummock::write_limits::WriteLimit;
59use risingwave_pb::hummock::{
60    BranchedObject, CompactTaskAssignment, CompactTaskProgress, CompactionGroupInfo,
61};
62use risingwave_pb::id::{ActorId, IcebergCompactionTaskId};
63use risingwave_pb::meta::cancel_creating_jobs_request::PbJobs;
64use risingwave_pb::meta::list_actor_splits_response::ActorSplit;
65use risingwave_pb::meta::list_actor_states_response::ActorState;
66use risingwave_pb::meta::list_cdc_progress_response::PbCdcProgress;
67use risingwave_pb::meta::list_iceberg_compaction_status_response::IcebergCompactionStatus;
68use risingwave_pb::meta::list_iceberg_tables_response::IcebergTable;
69use risingwave_pb::meta::list_rate_limits_response::RateLimitInfo;
70use risingwave_pb::meta::list_refresh_table_states_response::RefreshTableState;
71use risingwave_pb::meta::list_streaming_job_states_response::StreamingJobState;
72use risingwave_pb::meta::list_table_fragments_response::TableFragmentInfo;
73use risingwave_pb::meta::{
74    EventLog, FragmentDistribution, ObjectDependency as PbObjectDependency, PbTableParallelism,
75    PbThrottleTarget, RecoveryStatus, RefreshRequest, RefreshResponse, SystemParams,
76    list_sink_log_store_tables_response,
77};
78use risingwave_pb::secret::PbSecretRef;
79use risingwave_pb::stream_plan::StreamFragmentGraph;
80use risingwave_pb::user::alter_default_privilege_request::Operation as AlterDefaultPrivilegeOperation;
81use risingwave_pb::user::update_user_request::UpdateField;
82use risingwave_pb::user::{GrantPrivilege, UserInfo};
83use risingwave_rpc_client::error::Result as RpcResult;
84use tempfile::{Builder, NamedTempFile};
85
86use crate::FrontendOpts;
87use crate::catalog::catalog_service::CatalogWriter;
88use crate::catalog::root_catalog::Catalog;
89use crate::catalog::{DatabaseId, FragmentId, SchemaId, SecretId, SinkId};
90use crate::error::{ErrorCode, Result, RwError};
91use crate::handler::RwPgResponse;
92use crate::meta_client::FrontendMetaClient;
93use crate::scheduler::HummockSnapshotManagerRef;
94use crate::session::{AuthContext, FrontendEnv, SessionImpl};
95use crate::user::UserId;
96use crate::user::user_manager::UserInfoManager;
97use crate::user::user_service::UserInfoWriter;
98
99/// An embedded frontend without starting meta and without starting frontend as a tcp server.
100pub struct LocalFrontend {
101    pub opts: FrontendOpts,
102    env: FrontendEnv,
103}
104
105impl SessionManager for LocalFrontend {
106    type Error = RwError;
107    type Session = SessionImpl;
108
109    fn create_dummy_session(
110        &self,
111        _database_id: DatabaseId,
112    ) -> std::result::Result<Arc<Self::Session>, Self::Error> {
113        unreachable!()
114    }
115
116    fn connect(
117        &self,
118        _database: &str,
119        _user_name: &str,
120        _peer_addr: AddressRef,
121    ) -> std::result::Result<Arc<Self::Session>, Self::Error> {
122        Ok(self.session_ref())
123    }
124
125    fn cancel_queries_in_session(&self, _session_id: SessionId) {
126        unreachable!()
127    }
128
129    fn cancel_creating_jobs_in_session(&self, _session_id: SessionId) {
130        unreachable!()
131    }
132
133    fn end_session(&self, _session: &Self::Session) {
134        unreachable!()
135    }
136}
137
138impl LocalFrontend {
139    #[expect(clippy::unused_async)]
140    pub async fn new(opts: FrontendOpts) -> Self {
141        let env = FrontendEnv::mock();
142        Self { opts, env }
143    }
144
145    #[expect(clippy::unused_async)]
146    pub async fn with_frontend_config(opts: FrontendOpts, frontend_config: FrontendConfig) -> Self {
147        let mut env = FrontendEnv::mock();
148        env.set_frontend_config_for_test(frontend_config);
149        Self { opts, env }
150    }
151
152    pub async fn run_sql(
153        &self,
154        sql: impl Into<String>,
155    ) -> std::result::Result<RwPgResponse, Box<dyn std::error::Error + Send + Sync>> {
156        let sql: Arc<str> = Arc::from(sql.into());
157        Box::pin(self.session_ref().run_statement(sql, vec![])).await
158    }
159
160    pub async fn run_sql_with_session(
161        &self,
162        session_ref: Arc<SessionImpl>,
163        sql: impl Into<String>,
164    ) -> std::result::Result<RwPgResponse, Box<dyn std::error::Error + Send + Sync>> {
165        let sql: Arc<str> = Arc::from(sql.into());
166        Box::pin(session_ref.run_statement(sql, vec![])).await
167    }
168
169    pub async fn run_user_sql(
170        &self,
171        sql: impl Into<String>,
172        database: String,
173        user_name: String,
174        user_id: UserId,
175    ) -> std::result::Result<RwPgResponse, Box<dyn std::error::Error + Send + Sync>> {
176        let sql: Arc<str> = Arc::from(sql.into());
177        Box::pin(
178            self.session_user_ref(database, user_name, user_id)
179                .run_statement(sql, vec![]),
180        )
181        .await
182    }
183
184    pub async fn query_formatted_result(&self, sql: impl Into<String>) -> Vec<String> {
185        let mut rsp = self.run_sql(sql).await.unwrap();
186        let mut res = vec![];
187        #[for_await]
188        for row_set in rsp.values_stream() {
189            for row in row_set.unwrap() {
190                res.push(format!("{:?}", row));
191            }
192        }
193        res
194    }
195
196    pub async fn get_explain_output(&self, sql: impl Into<String>) -> String {
197        let mut rsp = self.run_sql(sql).await.unwrap();
198        assert_eq!(rsp.stmt_type(), StatementType::EXPLAIN);
199        let mut res = String::new();
200        #[for_await]
201        for row_set in rsp.values_stream() {
202            for row in row_set.unwrap() {
203                let row: Row = row;
204                let row = row.values()[0].as_ref().unwrap();
205                res += std::str::from_utf8(row).unwrap();
206                res += "\n";
207            }
208        }
209        res
210    }
211
212    /// Creates a new session
213    pub fn session_ref(&self) -> Arc<SessionImpl> {
214        self.session_user_ref(
215            DEFAULT_DATABASE_NAME.to_owned(),
216            DEFAULT_SUPER_USER.to_owned(),
217            DEFAULT_SUPER_USER_ID,
218        )
219    }
220
221    pub fn session_user_ref(
222        &self,
223        database: String,
224        user_name: String,
225        user_id: UserId,
226    ) -> Arc<SessionImpl> {
227        Arc::new(SessionImpl::new(
228            self.env.clone(),
229            AuthContext::new(database, user_name, user_id),
230            UserAuthenticator::None,
231            // Local Frontend use a non-sense id.
232            (0, 0),
233            Address::Tcp(SocketAddr::new(
234                IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
235                6666,
236            ))
237            .into(),
238            Default::default(),
239        ))
240    }
241}
242
243pub async fn get_explain_output(mut rsp: RwPgResponse) -> String {
244    if rsp.stmt_type() != StatementType::EXPLAIN {
245        panic!("RESPONSE INVALID: {rsp:?}");
246    }
247    let mut res = String::new();
248    #[for_await]
249    for row_set in rsp.values_stream() {
250        for row in row_set.unwrap() {
251            let row: Row = row;
252            let row = row.values()[0].as_ref().unwrap();
253            res += std::str::from_utf8(row).unwrap();
254            res += "\n";
255        }
256    }
257    res
258}
259
260pub struct MockCatalogWriter {
261    catalog: Arc<RwLock<Catalog>>,
262    id: AtomicU32,
263    table_id_to_schema_id: RwLock<HashMap<u32, SchemaId>>,
264    schema_id_to_database_id: RwLock<HashMap<SchemaId, DatabaseId>>,
265    hummock_snapshot_manager: HummockSnapshotManagerRef,
266}
267
268#[async_trait::async_trait]
269impl CatalogWriter for MockCatalogWriter {
270    async fn create_database(
271        &self,
272        db_name: &str,
273        owner: UserId,
274        resource_group: &str,
275        barrier_interval_ms: Option<u32>,
276        checkpoint_frequency: Option<u64>,
277    ) -> Result<()> {
278        let database_id = DatabaseId::new(self.gen_id());
279        self.catalog.write().create_database(&PbDatabase {
280            name: db_name.to_owned(),
281            id: database_id,
282            owner,
283            resource_group: resource_group.to_owned(),
284            barrier_interval_ms,
285            checkpoint_frequency,
286        });
287        self.create_schema(database_id, DEFAULT_SCHEMA_NAME, owner)
288            .await?;
289        self.create_schema(database_id, PG_CATALOG_SCHEMA_NAME, owner)
290            .await?;
291        self.create_schema(database_id, RW_CATALOG_SCHEMA_NAME, owner)
292            .await?;
293        Ok(())
294    }
295
296    async fn create_schema(
297        &self,
298        db_id: DatabaseId,
299        schema_name: &str,
300        owner: UserId,
301    ) -> Result<()> {
302        let id = self.gen_id();
303        self.catalog.write().create_schema(&PbSchema {
304            id,
305            name: schema_name.to_owned(),
306            database_id: db_id,
307            owner,
308        });
309        self.add_schema_id(id, db_id);
310        Ok(())
311    }
312
313    async fn create_materialized_view(
314        &self,
315        mut table: PbTable,
316        _graph: StreamFragmentGraph,
317        dependencies: HashSet<ObjectId>,
318        _resource_type: streaming_job_resource_type::ResourceType,
319        _if_not_exists: bool,
320        _refresh_interval_sec: Option<u64>,
321    ) -> Result<()> {
322        table.id = self.gen_id();
323        table.stream_job_status = PbStreamJobStatus::Created as _;
324        table.maybe_vnode_count = VnodeCount::for_test().to_protobuf();
325        self.catalog.write().create_table(&table);
326        self.add_table_or_source_id(table.id.as_raw_id(), table.schema_id, table.database_id);
327        self.insert_object_dependencies(table.id.as_object_id(), dependencies);
328        self.hummock_snapshot_manager.add_table_for_test(table.id);
329        Ok(())
330    }
331
332    async fn replace_materialized_view(
333        &self,
334        mut table: PbTable,
335        _graph: StreamFragmentGraph,
336    ) -> Result<()> {
337        table.stream_job_status = PbStreamJobStatus::Created as _;
338        assert_eq!(table.vnode_count(), VirtualNode::COUNT_FOR_TEST);
339        self.catalog.write().update_table(&table);
340        Ok(())
341    }
342
343    async fn create_view(&self, mut view: PbView, dependencies: HashSet<ObjectId>) -> Result<()> {
344        view.id = self.gen_id();
345        self.catalog.write().create_view(&view);
346        self.add_table_or_source_id(view.id.as_raw_id(), view.schema_id, view.database_id);
347        self.insert_object_dependencies(view.id.as_object_id(), dependencies);
348        Ok(())
349    }
350
351    async fn create_table(
352        &self,
353        source: Option<PbSource>,
354        mut table: PbTable,
355        graph: StreamFragmentGraph,
356        _job_type: PbTableJobType,
357        if_not_exists: bool,
358        dependencies: HashSet<ObjectId>,
359    ) -> Result<()> {
360        if let Some(source) = source {
361            let source_id = self.create_source_inner(source)?;
362            table.optional_associated_source_id = Some(source_id.into());
363        }
364        self.create_materialized_view(
365            table,
366            graph,
367            dependencies,
368            streaming_job_resource_type::ResourceType::Regular(true),
369            if_not_exists,
370            None,
371        )
372        .await?;
373        Ok(())
374    }
375
376    async fn replace_table(
377        &self,
378        _source: Option<PbSource>,
379        mut table: PbTable,
380        _graph: StreamFragmentGraph,
381        _job_type: TableJobType,
382    ) -> Result<()> {
383        table.stream_job_status = PbStreamJobStatus::Created as _;
384        assert_eq!(table.vnode_count(), VirtualNode::COUNT_FOR_TEST);
385        self.catalog.write().update_table(&table);
386        Ok(())
387    }
388
389    async fn replace_source(&self, source: PbSource, _graph: StreamFragmentGraph) -> Result<()> {
390        self.catalog.write().update_source(&source);
391        Ok(())
392    }
393
394    async fn create_source(
395        &self,
396        source: PbSource,
397        _graph: Option<StreamFragmentGraph>,
398        _if_not_exists: bool,
399    ) -> Result<()> {
400        self.create_source_inner(source).map(|_| ())
401    }
402
403    async fn create_sink(
404        &self,
405        sink: PbSink,
406        graph: StreamFragmentGraph,
407        dependencies: HashSet<ObjectId>,
408        _resource_type: streaming_job_resource_type::ResourceType,
409        _if_not_exists: bool,
410        _since_timestamp_epoch: Option<u64>,
411    ) -> Result<()> {
412        let sink_id = self.create_sink_inner(sink, graph)?;
413        self.insert_object_dependencies(sink_id.as_object_id(), dependencies);
414        Ok(())
415    }
416
417    async fn replace_sink(
418        &self,
419        old_sink_id: SinkId,
420        sink: PbSink,
421        graph: StreamFragmentGraph,
422        _dependencies: HashSet<ObjectId>,
423        _resource_type: streaming_job_resource_type::ResourceType,
424    ) -> Result<()> {
425        let (database_id, schema_id) = self.drop_table_or_sink_id(old_sink_id.as_raw_id());
426        self.catalog
427            .write()
428            .drop_sink(database_id, schema_id, old_sink_id);
429        self.create_sink_inner(sink, graph)?;
430        Ok(())
431    }
432
433    async fn create_subscription(&self, subscription: PbSubscription) -> Result<()> {
434        self.create_subscription_inner(subscription)
435    }
436
437    async fn create_index(
438        &self,
439        mut index: PbIndex,
440        mut index_table: PbTable,
441        _graph: StreamFragmentGraph,
442        _resource_type: streaming_job_resource_type::ResourceType,
443        _if_not_exists: bool,
444    ) -> Result<()> {
445        index_table.id = self.gen_id();
446        index_table.stream_job_status = PbStreamJobStatus::Created as _;
447        index_table.maybe_vnode_count = VnodeCount::for_test().to_protobuf();
448        self.catalog.write().create_table(&index_table);
449        self.add_table_or_index_id(
450            index_table.id.as_raw_id(),
451            index_table.schema_id,
452            index_table.database_id,
453        );
454
455        index.id = index_table.id.as_raw_id().into();
456        index.index_table_id = index_table.id;
457        self.catalog.write().create_index(&index);
458        Ok(())
459    }
460
461    async fn create_function(&self, mut function: PbFunction) -> Result<()> {
462        function.id = self.gen_id();
463        self.catalog.write().create_function(&function);
464        Ok(())
465    }
466
467    async fn create_connection(
468        &self,
469        _connection_name: String,
470        _database_id: DatabaseId,
471        _schema_id: SchemaId,
472        _owner_id: UserId,
473        _connection: create_connection_request::Payload,
474    ) -> Result<()> {
475        unreachable!()
476    }
477
478    async fn create_secret(
479        &self,
480        _secret_name: String,
481        _database_id: DatabaseId,
482        _schema_id: SchemaId,
483        _owner_id: UserId,
484        _payload: Vec<u8>,
485    ) -> Result<()> {
486        unreachable!()
487    }
488
489    async fn comment_on(&self, _comment: PbComment) -> Result<()> {
490        unreachable!()
491    }
492
493    async fn drop_table(
494        &self,
495        source_id: Option<SourceId>,
496        table_id: TableId,
497        cascade: bool,
498    ) -> Result<()> {
499        if cascade {
500            return Err(ErrorCode::NotSupported(
501                "drop cascade in MockCatalogWriter is unsupported".to_owned(),
502                "use drop instead".to_owned(),
503            )
504            .into());
505        }
506        if let Some(source_id) = source_id {
507            self.drop_table_or_source_id(source_id.as_raw_id());
508        }
509        let (database_id, schema_id) = self.drop_table_or_source_id(table_id.as_raw_id());
510        let indexes =
511            self.catalog
512                .read()
513                .get_all_indexes_related_to_object(database_id, schema_id, table_id);
514        for index in indexes {
515            self.drop_index(index.id, cascade).await?;
516        }
517        self.catalog
518            .write()
519            .drop_table(database_id, schema_id, table_id);
520        if let Some(source_id) = source_id {
521            self.catalog
522                .write()
523                .drop_source(database_id, schema_id, source_id);
524        }
525        Ok(())
526    }
527
528    async fn drop_view(&self, _view_id: ViewId, _cascade: bool) -> Result<()> {
529        unreachable!()
530    }
531
532    async fn drop_materialized_view(&self, table_id: TableId, cascade: bool) -> Result<()> {
533        if cascade {
534            return Err(ErrorCode::NotSupported(
535                "drop cascade in MockCatalogWriter is unsupported".to_owned(),
536                "use drop instead".to_owned(),
537            )
538            .into());
539        }
540        let (database_id, schema_id) = self.drop_table_or_source_id(table_id.as_raw_id());
541        let indexes =
542            self.catalog
543                .read()
544                .get_all_indexes_related_to_object(database_id, schema_id, table_id);
545        for index in indexes {
546            self.drop_index(index.id, cascade).await?;
547        }
548        self.catalog
549            .write()
550            .drop_table(database_id, schema_id, table_id);
551        Ok(())
552    }
553
554    async fn drop_source(&self, source_id: SourceId, cascade: bool) -> Result<()> {
555        if cascade {
556            return Err(ErrorCode::NotSupported(
557                "drop cascade in MockCatalogWriter is unsupported".to_owned(),
558                "use drop instead".to_owned(),
559            )
560            .into());
561        }
562        let (database_id, schema_id) = self.drop_table_or_source_id(source_id.as_raw_id());
563        self.catalog
564            .write()
565            .drop_source(database_id, schema_id, source_id);
566        Ok(())
567    }
568
569    async fn reset_source(&self, _source_id: SourceId) -> Result<()> {
570        Ok(())
571    }
572
573    async fn drop_sink(&self, sink_id: SinkId, cascade: bool) -> Result<()> {
574        if cascade {
575            return Err(ErrorCode::NotSupported(
576                "drop cascade in MockCatalogWriter is unsupported".to_owned(),
577                "use drop instead".to_owned(),
578            )
579            .into());
580        }
581        let (database_id, schema_id) = self.drop_table_or_sink_id(sink_id.as_raw_id());
582        self.catalog
583            .write()
584            .drop_sink(database_id, schema_id, sink_id);
585        Ok(())
586    }
587
588    async fn drop_subscription(
589        &self,
590        subscription_id: SubscriptionId,
591        cascade: bool,
592    ) -> Result<()> {
593        if cascade {
594            return Err(ErrorCode::NotSupported(
595                "drop cascade in MockCatalogWriter is unsupported".to_owned(),
596                "use drop instead".to_owned(),
597            )
598            .into());
599        }
600        let (database_id, schema_id) =
601            self.drop_table_or_subscription_id(subscription_id.as_raw_id());
602        self.catalog
603            .write()
604            .drop_subscription(database_id, schema_id, subscription_id);
605        Ok(())
606    }
607
608    async fn drop_index(&self, index_id: IndexId, cascade: bool) -> Result<()> {
609        if cascade {
610            return Err(ErrorCode::NotSupported(
611                "drop cascade in MockCatalogWriter is unsupported".to_owned(),
612                "use drop instead".to_owned(),
613            )
614            .into());
615        }
616        let &schema_id = self
617            .table_id_to_schema_id
618            .read()
619            .get(&index_id.as_raw_id())
620            .unwrap();
621        let database_id = self.get_database_id_by_schema(schema_id);
622
623        let index = {
624            let catalog_reader = self.catalog.read();
625            let schema_catalog = catalog_reader
626                .get_schema_by_id(database_id, schema_id)
627                .unwrap();
628            schema_catalog.get_index_by_id(index_id).unwrap().clone()
629        };
630
631        let index_table_id = index.index_table().id;
632        let (database_id, schema_id) = self.drop_table_or_index_id(index_id.as_raw_id());
633        self.catalog
634            .write()
635            .drop_index(database_id, schema_id, index_id);
636        self.catalog
637            .write()
638            .drop_table(database_id, schema_id, index_table_id);
639        Ok(())
640    }
641
642    async fn drop_function(&self, _function_id: FunctionId, _cascade: bool) -> Result<()> {
643        unreachable!()
644    }
645
646    async fn drop_connection(&self, _connection_id: ConnectionId, _cascade: bool) -> Result<()> {
647        unreachable!()
648    }
649
650    async fn drop_secret(&self, _secret_id: SecretId, _cascade: bool) -> Result<()> {
651        unreachable!()
652    }
653
654    async fn drop_database(&self, database_id: DatabaseId) -> Result<()> {
655        self.catalog.write().drop_database(database_id);
656        Ok(())
657    }
658
659    async fn drop_schema(&self, schema_id: SchemaId, _cascade: bool) -> Result<()> {
660        let database_id = self.drop_schema_id(schema_id);
661        self.catalog.write().drop_schema(database_id, schema_id);
662        Ok(())
663    }
664
665    async fn alter_name(
666        &self,
667        object_id: alter_name_request::Object,
668        object_name: &str,
669    ) -> Result<()> {
670        match object_id {
671            alter_name_request::Object::DatabaseId(database_id) => {
672                let mut database = self
673                    .catalog
674                    .read()
675                    .get_database_by_id(database_id)?
676                    .to_prost();
677                database.name = object_name.to_owned();
678                self.catalog.write().update_database(&database);
679                Ok(())
680            }
681            alter_name_request::Object::TableId(table_id) => {
682                self.catalog
683                    .write()
684                    .alter_table_name_by_id(table_id, object_name);
685                Ok(())
686            }
687            _ => {
688                unimplemented!()
689            }
690        }
691    }
692
693    async fn alter_source(&self, source: PbSource) -> Result<()> {
694        self.catalog.write().update_source(&source);
695        Ok(())
696    }
697
698    async fn alter_owner(&self, object: Object, owner_id: UserId) -> Result<()> {
699        for database in self.catalog.read().iter_databases() {
700            for schema in database.iter_schemas() {
701                match object {
702                    Object::TableId(table_id) => {
703                        if let Some(table) = schema.get_created_table_by_id(TableId::from(table_id))
704                        {
705                            let mut pb_table = table.to_prost();
706                            pb_table.owner = owner_id;
707                            self.catalog.write().update_table(&pb_table);
708                            return Ok(());
709                        }
710                    }
711                    _ => unreachable!(),
712                }
713            }
714        }
715
716        Err(ErrorCode::ItemNotFound(format!("object not found: {:?}", object)).into())
717    }
718
719    async fn alter_subscription_retention(
720        &self,
721        subscription_id: SubscriptionId,
722        retention_seconds: u64,
723        definition: String,
724    ) -> Result<()> {
725        for database in self.catalog.read().iter_databases() {
726            for schema in database.iter_schemas() {
727                if let Some(subscription) = schema.get_subscription_by_id(subscription_id) {
728                    let mut pb_subscription = subscription.to_proto();
729                    pb_subscription.retention_seconds = retention_seconds;
730                    pb_subscription.definition = definition;
731                    self.catalog.write().update_subscription(&pb_subscription);
732                    return Ok(());
733                }
734            }
735        }
736
737        Err(
738            ErrorCode::ItemNotFound(format!("subscription not found: {:?}", subscription_id))
739                .into(),
740        )
741    }
742
743    async fn alter_set_schema(
744        &self,
745        object: alter_set_schema_request::Object,
746        new_schema_id: SchemaId,
747    ) -> Result<()> {
748        match object {
749            alter_set_schema_request::Object::TableId(table_id) => {
750                let mut pb_table = {
751                    let reader = self.catalog.read();
752                    let table = reader.get_any_table_by_id(table_id)?.to_owned();
753                    table.to_prost()
754                };
755                pb_table.schema_id = new_schema_id;
756                self.catalog.write().update_table(&pb_table);
757                self.table_id_to_schema_id
758                    .write()
759                    .insert(table_id.as_raw_id(), new_schema_id);
760                Ok(())
761            }
762            _ => unreachable!(),
763        }
764    }
765
766    async fn alter_parallelism(
767        &self,
768        _job_id: JobId,
769        _parallelism: PbTableParallelism,
770        _adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
771        _deferred: bool,
772    ) -> Result<()> {
773        todo!()
774    }
775
776    async fn alter_backfill_parallelism(
777        &self,
778        _job_id: JobId,
779        _parallelism: Option<PbTableParallelism>,
780        _adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
781        _deferred: bool,
782    ) -> Result<()> {
783        todo!()
784    }
785
786    async fn alter_config(
787        &self,
788        _job_id: JobId,
789        _entries_to_add: HashMap<String, String>,
790        _keys_to_remove: Vec<String>,
791    ) -> Result<()> {
792        todo!()
793    }
794
795    async fn alter_swap_rename(&self, _object: alter_swap_rename_request::Object) -> Result<()> {
796        todo!()
797    }
798
799    async fn alter_secret(
800        &self,
801        _secret_id: SecretId,
802        _secret_name: String,
803        _database_id: DatabaseId,
804        _schema_id: SchemaId,
805        _owner_id: UserId,
806        _payload: Vec<u8>,
807    ) -> Result<()> {
808        unreachable!()
809    }
810
811    async fn alter_resource_group(
812        &self,
813        _job_id: JobId,
814        _resource_group: Option<String>,
815        _deferred: bool,
816    ) -> Result<()> {
817        todo!()
818    }
819
820    async fn alter_database_resource_group(
821        &self,
822        database_id: DatabaseId,
823        resource_group: Option<String>,
824        _deferred: bool,
825    ) -> Result<()> {
826        let mut pb_database = {
827            let reader = self.catalog.read();
828            let database = reader.get_database_by_id(database_id)?.to_owned();
829            database.to_prost()
830        };
831        pb_database.resource_group =
832            resource_group.unwrap_or_else(|| DEFAULT_RESOURCE_GROUP.to_owned());
833        self.catalog.write().update_database(&pb_database);
834        Ok(())
835    }
836
837    async fn alter_database_param(
838        &self,
839        database_id: DatabaseId,
840        param: AlterDatabaseParam,
841    ) -> Result<()> {
842        let mut pb_database = {
843            let reader = self.catalog.read();
844            let database = reader.get_database_by_id(database_id)?.to_owned();
845            database.to_prost()
846        };
847        match param {
848            AlterDatabaseParam::BarrierIntervalMs(interval) => {
849                pb_database.barrier_interval_ms = interval;
850            }
851            AlterDatabaseParam::CheckpointFrequency(frequency) => {
852                pb_database.checkpoint_frequency = frequency;
853            }
854        }
855        self.catalog.write().update_database(&pb_database);
856        Ok(())
857    }
858
859    async fn create_iceberg_table(
860        &self,
861        _table_job_info: PbTableJobInfo,
862        _sink_job_info: PbSinkJobInfo,
863        _iceberg_source: PbSource,
864        _if_not_exists: bool,
865    ) -> Result<()> {
866        todo!()
867    }
868
869    async fn wait(&self, _job_id: Option<JobId>) -> Result<()> {
870        Ok(())
871    }
872}
873
874impl MockCatalogWriter {
875    pub fn new(
876        catalog: Arc<RwLock<Catalog>>,
877        hummock_snapshot_manager: HummockSnapshotManagerRef,
878    ) -> Self {
879        catalog.write().create_database(&PbDatabase {
880            id: 0.into(),
881            name: DEFAULT_DATABASE_NAME.to_owned(),
882            owner: DEFAULT_SUPER_USER_ID,
883            resource_group: DEFAULT_RESOURCE_GROUP.to_owned(),
884            barrier_interval_ms: None,
885            checkpoint_frequency: None,
886        });
887        catalog.write().create_schema(&PbSchema {
888            id: 1.into(),
889            name: DEFAULT_SCHEMA_NAME.to_owned(),
890            database_id: 0.into(),
891            owner: DEFAULT_SUPER_USER_ID,
892        });
893        catalog.write().create_schema(&PbSchema {
894            id: 2.into(),
895            name: PG_CATALOG_SCHEMA_NAME.to_owned(),
896            database_id: 0.into(),
897            owner: DEFAULT_SUPER_USER_ID,
898        });
899        catalog.write().create_schema(&PbSchema {
900            id: 3.into(),
901            name: RW_CATALOG_SCHEMA_NAME.to_owned(),
902            database_id: 0.into(),
903            owner: DEFAULT_SUPER_USER_ID,
904        });
905        let mut map: HashMap<SchemaId, DatabaseId> = HashMap::new();
906        map.insert(1_u32.into(), 0_u32.into());
907        map.insert(2_u32.into(), 0_u32.into());
908        map.insert(3_u32.into(), 0_u32.into());
909        Self {
910            catalog,
911            id: AtomicU32::new(3),
912            table_id_to_schema_id: Default::default(),
913            schema_id_to_database_id: RwLock::new(map),
914            hummock_snapshot_manager,
915        }
916    }
917
918    fn gen_id<T: From<u32>>(&self) -> T {
919        // Since the 0 value is `dev` schema and database, so jump out the 0 value.
920        (self.id.fetch_add(1, Ordering::SeqCst) + 1).into()
921    }
922
923    fn add_table_or_source_id(&self, table_id: u32, schema_id: SchemaId, _database_id: DatabaseId) {
924        self.table_id_to_schema_id
925            .write()
926            .insert(table_id, schema_id);
927    }
928
929    fn drop_table_or_source_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
930        let schema_id = self
931            .table_id_to_schema_id
932            .write()
933            .remove(&table_id)
934            .unwrap();
935        (self.get_database_id_by_schema(schema_id), schema_id)
936    }
937
938    fn add_table_or_sink_id(&self, table_id: u32, schema_id: SchemaId, _database_id: DatabaseId) {
939        self.table_id_to_schema_id
940            .write()
941            .insert(table_id, schema_id);
942    }
943
944    fn add_table_or_subscription_id(
945        &self,
946        table_id: u32,
947        schema_id: SchemaId,
948        _database_id: DatabaseId,
949    ) {
950        self.table_id_to_schema_id
951            .write()
952            .insert(table_id, schema_id);
953    }
954
955    fn add_table_or_index_id(&self, table_id: u32, schema_id: SchemaId, _database_id: DatabaseId) {
956        self.table_id_to_schema_id
957            .write()
958            .insert(table_id, schema_id);
959    }
960
961    fn drop_table_or_sink_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
962        let schema_id = self
963            .table_id_to_schema_id
964            .write()
965            .remove(&table_id)
966            .unwrap();
967        (self.get_database_id_by_schema(schema_id), schema_id)
968    }
969
970    fn drop_table_or_subscription_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
971        let schema_id = self
972            .table_id_to_schema_id
973            .write()
974            .remove(&table_id)
975            .unwrap();
976        (self.get_database_id_by_schema(schema_id), schema_id)
977    }
978
979    fn drop_table_or_index_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
980        let schema_id = self
981            .table_id_to_schema_id
982            .write()
983            .remove(&table_id)
984            .unwrap();
985        (self.get_database_id_by_schema(schema_id), schema_id)
986    }
987
988    fn add_schema_id(&self, schema_id: SchemaId, database_id: DatabaseId) {
989        self.schema_id_to_database_id
990            .write()
991            .insert(schema_id, database_id);
992    }
993
994    fn drop_schema_id(&self, schema_id: SchemaId) -> DatabaseId {
995        self.schema_id_to_database_id
996            .write()
997            .remove(&schema_id)
998            .unwrap()
999    }
1000
1001    fn create_source_inner(&self, mut source: PbSource) -> Result<SourceId> {
1002        source.id = self.gen_id();
1003        self.catalog.write().create_source(&source);
1004        self.add_table_or_source_id(source.id.as_raw_id(), source.schema_id, source.database_id);
1005        Ok(source.id)
1006    }
1007
1008    fn create_sink_inner(&self, mut sink: PbSink, _graph: StreamFragmentGraph) -> Result<SinkId> {
1009        sink.id = self.gen_id();
1010        sink.stream_job_status = PbStreamJobStatus::Created as _;
1011        self.catalog.write().create_sink(&sink);
1012        self.add_table_or_sink_id(sink.id.as_raw_id(), sink.schema_id, sink.database_id);
1013        Ok(sink.id)
1014    }
1015
1016    fn create_subscription_inner(&self, mut subscription: PbSubscription) -> Result<()> {
1017        subscription.id = self.gen_id();
1018        self.catalog.write().create_subscription(&subscription);
1019        self.add_table_or_subscription_id(
1020            subscription.id.as_raw_id(),
1021            subscription.schema_id,
1022            subscription.database_id,
1023        );
1024        Ok(())
1025    }
1026
1027    fn get_database_id_by_schema(&self, schema_id: SchemaId) -> DatabaseId {
1028        *self
1029            .schema_id_to_database_id
1030            .read()
1031            .get(&schema_id)
1032            .unwrap()
1033    }
1034
1035    fn get_object_type(&self, object_id: ObjectId) -> PbObjectType {
1036        let catalog = self.catalog.read();
1037        for database in catalog.iter_databases() {
1038            for schema in database.iter_schemas() {
1039                if let Some(table) = schema.get_created_table_by_id(object_id.as_table_id()) {
1040                    return if table.is_mview() {
1041                        PbObjectType::Mview
1042                    } else {
1043                        PbObjectType::Table
1044                    };
1045                }
1046                if schema.get_source_by_id(object_id.as_source_id()).is_some() {
1047                    return PbObjectType::Source;
1048                }
1049                if schema.get_view_by_id(object_id.as_view_id()).is_some() {
1050                    return PbObjectType::View;
1051                }
1052                if schema.get_index_by_id(object_id.as_index_id()).is_some() {
1053                    return PbObjectType::Index;
1054                }
1055            }
1056        }
1057        PbObjectType::Unspecified
1058    }
1059
1060    fn insert_object_dependencies(&self, object_id: ObjectId, dependencies: HashSet<ObjectId>) {
1061        if dependencies.is_empty() {
1062            return;
1063        }
1064        let dependencies = dependencies
1065            .into_iter()
1066            .map(|referenced_object_id| PbObjectDependency {
1067                object_id,
1068                referenced_object_id,
1069                referenced_object_type: self.get_object_type(referenced_object_id) as i32,
1070            })
1071            .collect();
1072        self.catalog
1073            .write()
1074            .insert_object_dependencies(dependencies);
1075    }
1076}
1077
1078pub struct MockUserInfoWriter {
1079    id: AtomicU32,
1080    user_info: Arc<RwLock<UserInfoManager>>,
1081}
1082
1083#[async_trait::async_trait]
1084impl UserInfoWriter for MockUserInfoWriter {
1085    async fn create_user(&self, user: UserInfo) -> Result<()> {
1086        let mut user = user;
1087        user.id = self.gen_id().into();
1088        self.user_info.write().create_user(user);
1089        Ok(())
1090    }
1091
1092    async fn drop_user(&self, id: UserId) -> Result<()> {
1093        self.user_info.write().drop_user(id);
1094        Ok(())
1095    }
1096
1097    async fn update_user(
1098        &self,
1099        update_user: UserInfo,
1100        update_fields: Vec<UpdateField>,
1101    ) -> Result<()> {
1102        let mut lock = self.user_info.write();
1103        let id = update_user.get_id();
1104        let Some(old_name) = lock.get_user_name_by_id(id) else {
1105            return Ok(());
1106        };
1107        let mut user_info = lock.get_user_by_name(&old_name).unwrap().to_prost();
1108        update_fields.into_iter().for_each(|field| match field {
1109            UpdateField::Super => user_info.is_super = update_user.is_super,
1110            UpdateField::Login => user_info.can_login = update_user.can_login,
1111            UpdateField::CreateDb => user_info.can_create_db = update_user.can_create_db,
1112            UpdateField::CreateUser => user_info.can_create_user = update_user.can_create_user,
1113            UpdateField::AuthInfo => user_info.auth_info.clone_from(&update_user.auth_info),
1114            UpdateField::Rename => user_info.name.clone_from(&update_user.name),
1115            UpdateField::Admin => user_info.is_admin = update_user.is_admin,
1116            UpdateField::Unspecified => unreachable!(),
1117        });
1118        lock.update_user(update_user);
1119        Ok(())
1120    }
1121
1122    /// In `MockUserInfoWriter`, we don't support expand privilege with `GrantAllTables` and
1123    /// `GrantAllSources` when grant privilege to user.
1124    async fn grant_privilege(
1125        &self,
1126        users: Vec<UserId>,
1127        privileges: Vec<GrantPrivilege>,
1128        with_grant_option: bool,
1129        _grantor: UserId,
1130    ) -> Result<()> {
1131        let privileges = privileges
1132            .into_iter()
1133            .map(|mut p| {
1134                p.action_with_opts
1135                    .iter_mut()
1136                    .for_each(|ao| ao.with_grant_option = with_grant_option);
1137                p
1138            })
1139            .collect::<Vec<_>>();
1140        for user_id in users {
1141            if let Some(u) = self.user_info.write().get_user_mut(user_id) {
1142                u.extend_privileges(privileges.clone());
1143            }
1144        }
1145        Ok(())
1146    }
1147
1148    /// In `MockUserInfoWriter`, we don't support expand privilege with `RevokeAllTables` and
1149    /// `RevokeAllSources` when revoke privilege from user.
1150    async fn revoke_privilege(
1151        &self,
1152        users: Vec<UserId>,
1153        privileges: Vec<GrantPrivilege>,
1154        _granted_by: UserId,
1155        _revoke_by: UserId,
1156        revoke_grant_option: bool,
1157        _cascade: bool,
1158    ) -> Result<()> {
1159        for user_id in users {
1160            if let Some(u) = self.user_info.write().get_user_mut(user_id) {
1161                u.revoke_privileges(privileges.clone(), revoke_grant_option);
1162            }
1163        }
1164        Ok(())
1165    }
1166
1167    async fn alter_default_privilege(
1168        &self,
1169        _users: Vec<UserId>,
1170        _database_id: DatabaseId,
1171        _schemas: Vec<SchemaId>,
1172        _operation: AlterDefaultPrivilegeOperation,
1173        _operated_by: UserId,
1174    ) -> Result<()> {
1175        todo!()
1176    }
1177}
1178
1179impl MockUserInfoWriter {
1180    pub fn new(user_info: Arc<RwLock<UserInfoManager>>) -> Self {
1181        user_info.write().create_user(UserInfo {
1182            id: DEFAULT_SUPER_USER_ID,
1183            name: DEFAULT_SUPER_USER.to_owned(),
1184            is_super: true,
1185            can_create_db: true,
1186            can_create_user: true,
1187            can_login: true,
1188            ..Default::default()
1189        });
1190        user_info.write().create_user(UserInfo {
1191            id: DEFAULT_SUPER_USER_FOR_ADMIN_ID,
1192            name: DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
1193            is_super: true,
1194            can_create_db: true,
1195            can_create_user: true,
1196            can_login: true,
1197            is_admin: true,
1198            ..Default::default()
1199        });
1200        Self {
1201            user_info,
1202            id: AtomicU32::new(NON_RESERVED_USER_ID.as_raw_id()),
1203        }
1204    }
1205
1206    fn gen_id(&self) -> u32 {
1207        self.id.fetch_add(1, Ordering::SeqCst)
1208    }
1209}
1210
1211pub struct MockFrontendMetaClient {}
1212
1213#[async_trait::async_trait]
1214impl FrontendMetaClient for MockFrontendMetaClient {
1215    async fn try_unregister(&self) {}
1216
1217    async fn flush(&self, _database_id: DatabaseId) -> RpcResult<HummockVersionId> {
1218        Ok(INVALID_VERSION_ID)
1219    }
1220
1221    async fn cancel_creating_jobs(&self, _infos: PbJobs) -> RpcResult<Vec<u32>> {
1222        Ok(vec![])
1223    }
1224
1225    async fn list_table_fragments(
1226        &self,
1227        _table_ids: &[JobId],
1228    ) -> RpcResult<HashMap<JobId, TableFragmentInfo>> {
1229        Ok(HashMap::default())
1230    }
1231
1232    async fn list_streaming_job_states(&self) -> RpcResult<Vec<StreamingJobState>> {
1233        Ok(vec![])
1234    }
1235
1236    async fn list_fragment_distribution(
1237        &self,
1238        _include_node: bool,
1239    ) -> RpcResult<Vec<FragmentDistribution>> {
1240        Ok(vec![])
1241    }
1242
1243    async fn list_creating_fragment_distribution(&self) -> RpcResult<Vec<FragmentDistribution>> {
1244        Ok(vec![])
1245    }
1246
1247    async fn list_actor_states(&self) -> RpcResult<Vec<ActorState>> {
1248        Ok(vec![])
1249    }
1250
1251    async fn list_actor_splits(&self) -> RpcResult<Vec<ActorSplit>> {
1252        Ok(vec![])
1253    }
1254
1255    async fn list_meta_snapshots(&self) -> RpcResult<Vec<MetaSnapshotMetadata>> {
1256        Ok(vec![])
1257    }
1258
1259    async fn list_sink_log_store_tables(
1260        &self,
1261    ) -> RpcResult<Vec<list_sink_log_store_tables_response::SinkLogStoreTable>> {
1262        Ok(vec![])
1263    }
1264
1265    async fn set_system_param(
1266        &self,
1267        _param: String,
1268        _value: Option<String>,
1269    ) -> RpcResult<Option<SystemParamsReader>> {
1270        Ok(Some(SystemParams::default().into()))
1271    }
1272
1273    async fn clear_file_cache(
1274        &self,
1275        _clear_meta_cache: bool,
1276        _clear_data_cache: bool,
1277    ) -> RpcResult<()> {
1278        Ok(())
1279    }
1280
1281    async fn get_session_params(&self) -> RpcResult<SessionConfig> {
1282        Ok(Default::default())
1283    }
1284
1285    async fn set_session_param(&self, _param: String, _value: Option<String>) -> RpcResult<String> {
1286        Ok("".to_owned())
1287    }
1288
1289    async fn get_ddl_progress(&self) -> RpcResult<Vec<DdlProgress>> {
1290        Ok(vec![])
1291    }
1292
1293    async fn get_tables(
1294        &self,
1295        _table_ids: Vec<crate::catalog::TableId>,
1296        _include_dropped_tables: bool,
1297    ) -> RpcResult<HashMap<crate::catalog::TableId, Table>> {
1298        Ok(HashMap::new())
1299    }
1300
1301    async fn list_hummock_pinned_versions(&self) -> RpcResult<Vec<(WorkerId, HummockVersionId)>> {
1302        unimplemented!()
1303    }
1304
1305    async fn list_refresh_table_states(&self) -> RpcResult<Vec<RefreshTableState>> {
1306        unimplemented!()
1307    }
1308
1309    async fn get_hummock_current_version(&self) -> RpcResult<HummockVersion> {
1310        Ok(HummockVersion::default())
1311    }
1312
1313    async fn get_hummock_checkpoint_version(&self) -> RpcResult<HummockVersion> {
1314        unimplemented!()
1315    }
1316
1317    async fn list_version_deltas(&self) -> RpcResult<Vec<HummockVersionDelta>> {
1318        unimplemented!()
1319    }
1320
1321    async fn list_branched_objects(&self) -> RpcResult<Vec<BranchedObject>> {
1322        unimplemented!()
1323    }
1324
1325    async fn list_hummock_compaction_group_configs(&self) -> RpcResult<Vec<CompactionGroupInfo>> {
1326        unimplemented!()
1327    }
1328
1329    async fn list_hummock_active_write_limits(
1330        &self,
1331    ) -> RpcResult<HashMap<CompactionGroupId, WriteLimit>> {
1332        unimplemented!()
1333    }
1334
1335    async fn list_hummock_meta_configs(&self) -> RpcResult<HashMap<String, String>> {
1336        unimplemented!()
1337    }
1338
1339    async fn list_event_log(&self) -> RpcResult<Vec<EventLog>> {
1340        Ok(vec![])
1341    }
1342
1343    async fn list_compact_task_assignment(&self) -> RpcResult<Vec<CompactTaskAssignment>> {
1344        unimplemented!()
1345    }
1346
1347    async fn list_all_nodes(&self) -> RpcResult<Vec<WorkerNode>> {
1348        Ok(vec![])
1349    }
1350
1351    async fn list_compact_task_progress(&self) -> RpcResult<Vec<CompactTaskProgress>> {
1352        unimplemented!()
1353    }
1354
1355    async fn recover(&self) -> RpcResult<()> {
1356        unimplemented!()
1357    }
1358
1359    async fn backup_meta(&self, _remarks: Option<String>) -> RpcResult<u64> {
1360        unimplemented!()
1361    }
1362
1363    async fn get_backup_job_status(
1364        &self,
1365        _job_id: u64,
1366    ) -> RpcResult<(risingwave_pb::backup_service::BackupJobStatus, String)> {
1367        unimplemented!()
1368    }
1369
1370    async fn delete_meta_snapshot(&self, _snapshot_ids: &[u64]) -> RpcResult<()> {
1371        unimplemented!()
1372    }
1373
1374    async fn apply_throttle(
1375        &self,
1376        _throttle_target: PbThrottleTarget,
1377        _throttle_type: risingwave_pb::common::PbThrottleType,
1378        _id: u32,
1379        _rate_limit: Option<u32>,
1380    ) -> RpcResult<()> {
1381        unimplemented!()
1382    }
1383
1384    async fn alter_fragment_parallelism(
1385        &self,
1386        _fragment_ids: Vec<FragmentId>,
1387        _parallelism: Option<PbTableParallelism>,
1388    ) -> RpcResult<()> {
1389        unimplemented!()
1390    }
1391
1392    async fn get_cluster_recovery_status(&self) -> RpcResult<RecoveryStatus> {
1393        Ok(RecoveryStatus::StatusRunning)
1394    }
1395
1396    async fn get_cluster_limits(&self) -> RpcResult<Vec<ClusterLimit>> {
1397        Ok(vec![])
1398    }
1399
1400    async fn list_rate_limits(&self) -> RpcResult<Vec<RateLimitInfo>> {
1401        Ok(vec![])
1402    }
1403
1404    async fn list_cdc_progress(&self) -> RpcResult<HashMap<JobId, PbCdcProgress>> {
1405        Ok(HashMap::default())
1406    }
1407
1408    async fn get_meta_store_endpoint(&self) -> RpcResult<String> {
1409        unimplemented!()
1410    }
1411
1412    async fn alter_sink_props(
1413        &self,
1414        _sink_id: SinkId,
1415        _changed_props: BTreeMap<String, String>,
1416        _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1417        _connector_conn_ref: Option<ConnectionId>,
1418    ) -> RpcResult<()> {
1419        unimplemented!()
1420    }
1421
1422    async fn alter_iceberg_table_props(
1423        &self,
1424        _table_id: TableId,
1425        _sink_id: SinkId,
1426        _source_id: SourceId,
1427        _changed_props: BTreeMap<String, String>,
1428        _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1429        _connector_conn_ref: Option<ConnectionId>,
1430    ) -> RpcResult<()> {
1431        unimplemented!()
1432    }
1433
1434    async fn alter_source_connector_props(
1435        &self,
1436        _source_id: SourceId,
1437        _changed_props: BTreeMap<String, String>,
1438        _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1439        _connector_conn_ref: Option<ConnectionId>,
1440    ) -> RpcResult<()> {
1441        unimplemented!()
1442    }
1443
1444    async fn alter_connection_connector_props(
1445        &self,
1446        _connection_id: u32,
1447        _changed_props: BTreeMap<String, String>,
1448        _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1449    ) -> RpcResult<()> {
1450        Ok(())
1451    }
1452
1453    async fn list_hosted_iceberg_tables(&self) -> RpcResult<Vec<IcebergTable>> {
1454        unimplemented!()
1455    }
1456
1457    async fn list_iceberg_compaction_status(&self) -> RpcResult<Vec<IcebergCompactionStatus>> {
1458        Ok(vec![])
1459    }
1460
1461    async fn get_fragment_by_id(
1462        &self,
1463        _fragment_id: FragmentId,
1464    ) -> RpcResult<Option<FragmentDistribution>> {
1465        unimplemented!()
1466    }
1467
1468    async fn get_fragment_vnodes(
1469        &self,
1470        _fragment_id: FragmentId,
1471    ) -> RpcResult<Vec<(ActorId, Vec<u32>)>> {
1472        unimplemented!()
1473    }
1474
1475    async fn get_actor_vnodes(&self, _actor_id: ActorId) -> RpcResult<Vec<u32>> {
1476        unimplemented!()
1477    }
1478
1479    fn worker_id(&self) -> WorkerId {
1480        0.into()
1481    }
1482
1483    async fn set_sync_log_store_aligned(&self, _job_id: JobId, _aligned: bool) -> RpcResult<()> {
1484        Ok(())
1485    }
1486
1487    async fn compact_iceberg_table(&self, _sink_id: SinkId) -> RpcResult<IcebergCompactionTaskId> {
1488        Ok(1.into())
1489    }
1490
1491    async fn rewrite_iceberg_table_manifests(&self, _sink_id: SinkId) -> RpcResult<()> {
1492        Ok(())
1493    }
1494
1495    async fn expire_iceberg_table_snapshots(&self, _sink_id: SinkId) -> RpcResult<()> {
1496        Ok(())
1497    }
1498
1499    async fn remove_iceberg_table_orphan_files(
1500        &self,
1501        _sink_id: SinkId,
1502    ) -> RpcResult<RemoveIcebergTableOrphanFilesResponse> {
1503        Ok(RemoveIcebergTableOrphanFilesResponse {
1504            status: None,
1505            orphan_file_count: 0,
1506        })
1507    }
1508
1509    async fn refresh(&self, _request: RefreshRequest) -> RpcResult<RefreshResponse> {
1510        Ok(RefreshResponse { status: None })
1511    }
1512
1513    fn cluster_id(&self) -> &str {
1514        "test-cluster-uuid"
1515    }
1516
1517    async fn list_unmigrated_tables(&self) -> RpcResult<HashMap<crate::catalog::TableId, String>> {
1518        unimplemented!()
1519    }
1520
1521    async fn get_hummock_table_change_log(
1522        &self,
1523        _start_epoch_inclusive: Option<u64>,
1524        _end_epoch_inclusive: Option<u64>,
1525        _table_ids: Option<HashSet<TableId>>,
1526        _exclude_empty: bool,
1527        _limit: Option<u32>,
1528    ) -> RpcResult<TableChangeLogs> {
1529        Ok(HashMap::default())
1530    }
1531
1532    async fn update_compaction_config(
1533        &self,
1534        _compaction_group_ids: Vec<risingwave_hummock_sdk::CompactionGroupId>,
1535        _configs: Vec<PbMutableConfig>,
1536    ) -> RpcResult<()> {
1537        Ok(())
1538    }
1539}
1540
1541#[cfg(test)]
1542pub static PROTO_FILE_DATA: &str = r#"
1543    syntax = "proto3";
1544    package test;
1545    message TestRecord {
1546      int32 id = 1;
1547      Country country = 3;
1548      int64 zipcode = 4;
1549      float rate = 5;
1550    }
1551    message TestRecordAlterType {
1552        string id = 1;
1553        Country country = 3;
1554        int32 zipcode = 4;
1555        float rate = 5;
1556      }
1557    message TestRecordExt {
1558      int32 id = 1;
1559      Country country = 3;
1560      int64 zipcode = 4;
1561      float rate = 5;
1562      string name = 6;
1563    }
1564    message Country {
1565      string address = 1;
1566      City city = 2;
1567      string zipcode = 3;
1568    }
1569    message City {
1570      string address = 1;
1571      string zipcode = 2;
1572    }"#;
1573
1574/// Returns the file.
1575/// (`NamedTempFile` will automatically delete the file when it goes out of scope.)
1576pub fn create_proto_file(proto_data: &str) -> NamedTempFile {
1577    let in_file = Builder::new()
1578        .prefix("temp")
1579        .suffix(".proto")
1580        .rand_bytes(8)
1581        .tempfile()
1582        .unwrap();
1583
1584    let out_file = Builder::new()
1585        .prefix("temp")
1586        .suffix(".pb")
1587        .rand_bytes(8)
1588        .tempfile()
1589        .unwrap();
1590
1591    let mut file = in_file.as_file();
1592    file.write_all(proto_data.as_ref())
1593        .expect("writing binary to test file");
1594    file.flush().expect("flush temp file failed");
1595    let include_path = in_file
1596        .path()
1597        .parent()
1598        .unwrap()
1599        .to_string_lossy()
1600        .into_owned();
1601    let out_path = out_file.path().to_string_lossy().into_owned();
1602    let in_path = in_file.path().to_string_lossy().into_owned();
1603    let mut compile = std::process::Command::new("protoc");
1604
1605    let out = compile
1606        .arg("--include_imports")
1607        .arg("-I")
1608        .arg(include_path)
1609        .arg(format!("--descriptor_set_out={}", out_path))
1610        .arg(in_path)
1611        .output()
1612        .expect("failed to compile proto");
1613    if !out.status.success() {
1614        panic!("compile proto failed \n output: {:?}", out);
1615    }
1616    out_file
1617}