1use 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
99pub 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 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 (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 (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 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 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
1574pub 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}