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