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, 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
98pub 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 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 (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, _function: PbFunction) -> Result<()> {
461 unreachable!()
462 }
463
464 async fn create_connection(
465 &self,
466 _connection_name: String,
467 _database_id: DatabaseId,
468 _schema_id: SchemaId,
469 _owner_id: UserId,
470 _connection: create_connection_request::Payload,
471 ) -> Result<()> {
472 unreachable!()
473 }
474
475 async fn create_secret(
476 &self,
477 _secret_name: String,
478 _database_id: DatabaseId,
479 _schema_id: SchemaId,
480 _owner_id: UserId,
481 _payload: Vec<u8>,
482 ) -> Result<()> {
483 unreachable!()
484 }
485
486 async fn comment_on(&self, _comment: PbComment) -> Result<()> {
487 unreachable!()
488 }
489
490 async fn drop_table(
491 &self,
492 source_id: Option<SourceId>,
493 table_id: TableId,
494 cascade: bool,
495 ) -> Result<()> {
496 if cascade {
497 return Err(ErrorCode::NotSupported(
498 "drop cascade in MockCatalogWriter is unsupported".to_owned(),
499 "use drop instead".to_owned(),
500 )
501 .into());
502 }
503 if let Some(source_id) = source_id {
504 self.drop_table_or_source_id(source_id.as_raw_id());
505 }
506 let (database_id, schema_id) = self.drop_table_or_source_id(table_id.as_raw_id());
507 let indexes =
508 self.catalog
509 .read()
510 .get_all_indexes_related_to_object(database_id, schema_id, table_id);
511 for index in indexes {
512 self.drop_index(index.id, cascade).await?;
513 }
514 self.catalog
515 .write()
516 .drop_table(database_id, schema_id, table_id);
517 if let Some(source_id) = source_id {
518 self.catalog
519 .write()
520 .drop_source(database_id, schema_id, source_id);
521 }
522 Ok(())
523 }
524
525 async fn drop_view(&self, _view_id: ViewId, _cascade: bool) -> Result<()> {
526 unreachable!()
527 }
528
529 async fn drop_materialized_view(&self, table_id: TableId, cascade: bool) -> Result<()> {
530 if cascade {
531 return Err(ErrorCode::NotSupported(
532 "drop cascade in MockCatalogWriter is unsupported".to_owned(),
533 "use drop instead".to_owned(),
534 )
535 .into());
536 }
537 let (database_id, schema_id) = self.drop_table_or_source_id(table_id.as_raw_id());
538 let indexes =
539 self.catalog
540 .read()
541 .get_all_indexes_related_to_object(database_id, schema_id, table_id);
542 for index in indexes {
543 self.drop_index(index.id, cascade).await?;
544 }
545 self.catalog
546 .write()
547 .drop_table(database_id, schema_id, table_id);
548 Ok(())
549 }
550
551 async fn drop_source(&self, source_id: SourceId, cascade: bool) -> Result<()> {
552 if cascade {
553 return Err(ErrorCode::NotSupported(
554 "drop cascade in MockCatalogWriter is unsupported".to_owned(),
555 "use drop instead".to_owned(),
556 )
557 .into());
558 }
559 let (database_id, schema_id) = self.drop_table_or_source_id(source_id.as_raw_id());
560 self.catalog
561 .write()
562 .drop_source(database_id, schema_id, source_id);
563 Ok(())
564 }
565
566 async fn reset_source(&self, _source_id: SourceId) -> Result<()> {
567 Ok(())
568 }
569
570 async fn drop_sink(&self, sink_id: SinkId, cascade: bool) -> Result<()> {
571 if cascade {
572 return Err(ErrorCode::NotSupported(
573 "drop cascade in MockCatalogWriter is unsupported".to_owned(),
574 "use drop instead".to_owned(),
575 )
576 .into());
577 }
578 let (database_id, schema_id) = self.drop_table_or_sink_id(sink_id.as_raw_id());
579 self.catalog
580 .write()
581 .drop_sink(database_id, schema_id, sink_id);
582 Ok(())
583 }
584
585 async fn drop_subscription(
586 &self,
587 subscription_id: SubscriptionId,
588 cascade: bool,
589 ) -> Result<()> {
590 if cascade {
591 return Err(ErrorCode::NotSupported(
592 "drop cascade in MockCatalogWriter is unsupported".to_owned(),
593 "use drop instead".to_owned(),
594 )
595 .into());
596 }
597 let (database_id, schema_id) =
598 self.drop_table_or_subscription_id(subscription_id.as_raw_id());
599 self.catalog
600 .write()
601 .drop_subscription(database_id, schema_id, subscription_id);
602 Ok(())
603 }
604
605 async fn drop_index(&self, index_id: IndexId, cascade: bool) -> Result<()> {
606 if cascade {
607 return Err(ErrorCode::NotSupported(
608 "drop cascade in MockCatalogWriter is unsupported".to_owned(),
609 "use drop instead".to_owned(),
610 )
611 .into());
612 }
613 let &schema_id = self
614 .table_id_to_schema_id
615 .read()
616 .get(&index_id.as_raw_id())
617 .unwrap();
618 let database_id = self.get_database_id_by_schema(schema_id);
619
620 let index = {
621 let catalog_reader = self.catalog.read();
622 let schema_catalog = catalog_reader
623 .get_schema_by_id(database_id, schema_id)
624 .unwrap();
625 schema_catalog.get_index_by_id(index_id).unwrap().clone()
626 };
627
628 let index_table_id = index.index_table().id;
629 let (database_id, schema_id) = self.drop_table_or_index_id(index_id.as_raw_id());
630 self.catalog
631 .write()
632 .drop_index(database_id, schema_id, index_id);
633 self.catalog
634 .write()
635 .drop_table(database_id, schema_id, index_table_id);
636 Ok(())
637 }
638
639 async fn drop_function(&self, _function_id: FunctionId, _cascade: bool) -> Result<()> {
640 unreachable!()
641 }
642
643 async fn drop_connection(&self, _connection_id: ConnectionId, _cascade: bool) -> Result<()> {
644 unreachable!()
645 }
646
647 async fn drop_secret(&self, _secret_id: SecretId, _cascade: bool) -> Result<()> {
648 unreachable!()
649 }
650
651 async fn drop_database(&self, database_id: DatabaseId) -> Result<()> {
652 self.catalog.write().drop_database(database_id);
653 Ok(())
654 }
655
656 async fn drop_schema(&self, schema_id: SchemaId, _cascade: bool) -> Result<()> {
657 let database_id = self.drop_schema_id(schema_id);
658 self.catalog.write().drop_schema(database_id, schema_id);
659 Ok(())
660 }
661
662 async fn alter_name(
663 &self,
664 object_id: alter_name_request::Object,
665 object_name: &str,
666 ) -> Result<()> {
667 match object_id {
668 alter_name_request::Object::DatabaseId(database_id) => {
669 let mut database = self
670 .catalog
671 .read()
672 .get_database_by_id(database_id)?
673 .to_prost();
674 database.name = object_name.to_owned();
675 self.catalog.write().update_database(&database);
676 Ok(())
677 }
678 alter_name_request::Object::TableId(table_id) => {
679 self.catalog
680 .write()
681 .alter_table_name_by_id(table_id, object_name);
682 Ok(())
683 }
684 _ => {
685 unimplemented!()
686 }
687 }
688 }
689
690 async fn alter_source(&self, source: PbSource) -> Result<()> {
691 self.catalog.write().update_source(&source);
692 Ok(())
693 }
694
695 async fn alter_owner(&self, object: Object, owner_id: UserId) -> Result<()> {
696 for database in self.catalog.read().iter_databases() {
697 for schema in database.iter_schemas() {
698 match object {
699 Object::TableId(table_id) => {
700 if let Some(table) = schema.get_created_table_by_id(TableId::from(table_id))
701 {
702 let mut pb_table = table.to_prost();
703 pb_table.owner = owner_id;
704 self.catalog.write().update_table(&pb_table);
705 return Ok(());
706 }
707 }
708 _ => unreachable!(),
709 }
710 }
711 }
712
713 Err(ErrorCode::ItemNotFound(format!("object not found: {:?}", object)).into())
714 }
715
716 async fn alter_subscription_retention(
717 &self,
718 subscription_id: SubscriptionId,
719 retention_seconds: u64,
720 definition: String,
721 ) -> Result<()> {
722 for database in self.catalog.read().iter_databases() {
723 for schema in database.iter_schemas() {
724 if let Some(subscription) = schema.get_subscription_by_id(subscription_id) {
725 let mut pb_subscription = subscription.to_proto();
726 pb_subscription.retention_seconds = retention_seconds;
727 pb_subscription.definition = definition;
728 self.catalog.write().update_subscription(&pb_subscription);
729 return Ok(());
730 }
731 }
732 }
733
734 Err(
735 ErrorCode::ItemNotFound(format!("subscription not found: {:?}", subscription_id))
736 .into(),
737 )
738 }
739
740 async fn alter_set_schema(
741 &self,
742 object: alter_set_schema_request::Object,
743 new_schema_id: SchemaId,
744 ) -> Result<()> {
745 match object {
746 alter_set_schema_request::Object::TableId(table_id) => {
747 let mut pb_table = {
748 let reader = self.catalog.read();
749 let table = reader.get_any_table_by_id(table_id)?.to_owned();
750 table.to_prost()
751 };
752 pb_table.schema_id = new_schema_id;
753 self.catalog.write().update_table(&pb_table);
754 self.table_id_to_schema_id
755 .write()
756 .insert(table_id.as_raw_id(), new_schema_id);
757 Ok(())
758 }
759 _ => unreachable!(),
760 }
761 }
762
763 async fn alter_parallelism(
764 &self,
765 _job_id: JobId,
766 _parallelism: PbTableParallelism,
767 _adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
768 _deferred: bool,
769 ) -> Result<()> {
770 todo!()
771 }
772
773 async fn alter_backfill_parallelism(
774 &self,
775 _job_id: JobId,
776 _parallelism: Option<PbTableParallelism>,
777 _adaptive_parallelism_strategy: Option<AdaptiveParallelismStrategy>,
778 _deferred: bool,
779 ) -> Result<()> {
780 todo!()
781 }
782
783 async fn alter_config(
784 &self,
785 _job_id: JobId,
786 _entries_to_add: HashMap<String, String>,
787 _keys_to_remove: Vec<String>,
788 ) -> Result<()> {
789 todo!()
790 }
791
792 async fn alter_swap_rename(&self, _object: alter_swap_rename_request::Object) -> Result<()> {
793 todo!()
794 }
795
796 async fn alter_secret(
797 &self,
798 _secret_id: SecretId,
799 _secret_name: String,
800 _database_id: DatabaseId,
801 _schema_id: SchemaId,
802 _owner_id: UserId,
803 _payload: Vec<u8>,
804 ) -> Result<()> {
805 unreachable!()
806 }
807
808 async fn alter_resource_group(
809 &self,
810 _job_id: JobId,
811 _resource_group: Option<String>,
812 _deferred: bool,
813 ) -> Result<()> {
814 todo!()
815 }
816
817 async fn alter_database_resource_group(
818 &self,
819 database_id: DatabaseId,
820 resource_group: Option<String>,
821 _deferred: bool,
822 ) -> Result<()> {
823 let mut pb_database = {
824 let reader = self.catalog.read();
825 let database = reader.get_database_by_id(database_id)?.to_owned();
826 database.to_prost()
827 };
828 pb_database.resource_group =
829 resource_group.unwrap_or_else(|| DEFAULT_RESOURCE_GROUP.to_owned());
830 self.catalog.write().update_database(&pb_database);
831 Ok(())
832 }
833
834 async fn alter_database_param(
835 &self,
836 database_id: DatabaseId,
837 param: AlterDatabaseParam,
838 ) -> Result<()> {
839 let mut pb_database = {
840 let reader = self.catalog.read();
841 let database = reader.get_database_by_id(database_id)?.to_owned();
842 database.to_prost()
843 };
844 match param {
845 AlterDatabaseParam::BarrierIntervalMs(interval) => {
846 pb_database.barrier_interval_ms = interval;
847 }
848 AlterDatabaseParam::CheckpointFrequency(frequency) => {
849 pb_database.checkpoint_frequency = frequency;
850 }
851 }
852 self.catalog.write().update_database(&pb_database);
853 Ok(())
854 }
855
856 async fn create_iceberg_table(
857 &self,
858 _table_job_info: PbTableJobInfo,
859 _sink_job_info: PbSinkJobInfo,
860 _iceberg_source: PbSource,
861 _if_not_exists: bool,
862 ) -> Result<()> {
863 todo!()
864 }
865
866 async fn wait(&self, _job_id: Option<JobId>) -> Result<()> {
867 Ok(())
868 }
869}
870
871impl MockCatalogWriter {
872 pub fn new(
873 catalog: Arc<RwLock<Catalog>>,
874 hummock_snapshot_manager: HummockSnapshotManagerRef,
875 ) -> Self {
876 catalog.write().create_database(&PbDatabase {
877 id: 0.into(),
878 name: DEFAULT_DATABASE_NAME.to_owned(),
879 owner: DEFAULT_SUPER_USER_ID,
880 resource_group: DEFAULT_RESOURCE_GROUP.to_owned(),
881 barrier_interval_ms: None,
882 checkpoint_frequency: None,
883 });
884 catalog.write().create_schema(&PbSchema {
885 id: 1.into(),
886 name: DEFAULT_SCHEMA_NAME.to_owned(),
887 database_id: 0.into(),
888 owner: DEFAULT_SUPER_USER_ID,
889 });
890 catalog.write().create_schema(&PbSchema {
891 id: 2.into(),
892 name: PG_CATALOG_SCHEMA_NAME.to_owned(),
893 database_id: 0.into(),
894 owner: DEFAULT_SUPER_USER_ID,
895 });
896 catalog.write().create_schema(&PbSchema {
897 id: 3.into(),
898 name: RW_CATALOG_SCHEMA_NAME.to_owned(),
899 database_id: 0.into(),
900 owner: DEFAULT_SUPER_USER_ID,
901 });
902 let mut map: HashMap<SchemaId, DatabaseId> = HashMap::new();
903 map.insert(1_u32.into(), 0_u32.into());
904 map.insert(2_u32.into(), 0_u32.into());
905 map.insert(3_u32.into(), 0_u32.into());
906 Self {
907 catalog,
908 id: AtomicU32::new(3),
909 table_id_to_schema_id: Default::default(),
910 schema_id_to_database_id: RwLock::new(map),
911 hummock_snapshot_manager,
912 }
913 }
914
915 fn gen_id<T: From<u32>>(&self) -> T {
916 (self.id.fetch_add(1, Ordering::SeqCst) + 1).into()
918 }
919
920 fn add_table_or_source_id(&self, table_id: u32, schema_id: SchemaId, _database_id: DatabaseId) {
921 self.table_id_to_schema_id
922 .write()
923 .insert(table_id, schema_id);
924 }
925
926 fn drop_table_or_source_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
927 let schema_id = self
928 .table_id_to_schema_id
929 .write()
930 .remove(&table_id)
931 .unwrap();
932 (self.get_database_id_by_schema(schema_id), schema_id)
933 }
934
935 fn add_table_or_sink_id(&self, table_id: u32, schema_id: SchemaId, _database_id: DatabaseId) {
936 self.table_id_to_schema_id
937 .write()
938 .insert(table_id, schema_id);
939 }
940
941 fn add_table_or_subscription_id(
942 &self,
943 table_id: u32,
944 schema_id: SchemaId,
945 _database_id: DatabaseId,
946 ) {
947 self.table_id_to_schema_id
948 .write()
949 .insert(table_id, schema_id);
950 }
951
952 fn add_table_or_index_id(&self, table_id: u32, schema_id: SchemaId, _database_id: DatabaseId) {
953 self.table_id_to_schema_id
954 .write()
955 .insert(table_id, schema_id);
956 }
957
958 fn drop_table_or_sink_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
959 let schema_id = self
960 .table_id_to_schema_id
961 .write()
962 .remove(&table_id)
963 .unwrap();
964 (self.get_database_id_by_schema(schema_id), schema_id)
965 }
966
967 fn drop_table_or_subscription_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
968 let schema_id = self
969 .table_id_to_schema_id
970 .write()
971 .remove(&table_id)
972 .unwrap();
973 (self.get_database_id_by_schema(schema_id), schema_id)
974 }
975
976 fn drop_table_or_index_id(&self, table_id: u32) -> (DatabaseId, SchemaId) {
977 let schema_id = self
978 .table_id_to_schema_id
979 .write()
980 .remove(&table_id)
981 .unwrap();
982 (self.get_database_id_by_schema(schema_id), schema_id)
983 }
984
985 fn add_schema_id(&self, schema_id: SchemaId, database_id: DatabaseId) {
986 self.schema_id_to_database_id
987 .write()
988 .insert(schema_id, database_id);
989 }
990
991 fn drop_schema_id(&self, schema_id: SchemaId) -> DatabaseId {
992 self.schema_id_to_database_id
993 .write()
994 .remove(&schema_id)
995 .unwrap()
996 }
997
998 fn create_source_inner(&self, mut source: PbSource) -> Result<SourceId> {
999 source.id = self.gen_id();
1000 self.catalog.write().create_source(&source);
1001 self.add_table_or_source_id(source.id.as_raw_id(), source.schema_id, source.database_id);
1002 Ok(source.id)
1003 }
1004
1005 fn create_sink_inner(&self, mut sink: PbSink, _graph: StreamFragmentGraph) -> Result<SinkId> {
1006 sink.id = self.gen_id();
1007 sink.stream_job_status = PbStreamJobStatus::Created as _;
1008 self.catalog.write().create_sink(&sink);
1009 self.add_table_or_sink_id(sink.id.as_raw_id(), sink.schema_id, sink.database_id);
1010 Ok(sink.id)
1011 }
1012
1013 fn create_subscription_inner(&self, mut subscription: PbSubscription) -> Result<()> {
1014 subscription.id = self.gen_id();
1015 self.catalog.write().create_subscription(&subscription);
1016 self.add_table_or_subscription_id(
1017 subscription.id.as_raw_id(),
1018 subscription.schema_id,
1019 subscription.database_id,
1020 );
1021 Ok(())
1022 }
1023
1024 fn get_database_id_by_schema(&self, schema_id: SchemaId) -> DatabaseId {
1025 *self
1026 .schema_id_to_database_id
1027 .read()
1028 .get(&schema_id)
1029 .unwrap()
1030 }
1031
1032 fn get_object_type(&self, object_id: ObjectId) -> PbObjectType {
1033 let catalog = self.catalog.read();
1034 for database in catalog.iter_databases() {
1035 for schema in database.iter_schemas() {
1036 if let Some(table) = schema.get_created_table_by_id(object_id.as_table_id()) {
1037 return if table.is_mview() {
1038 PbObjectType::Mview
1039 } else {
1040 PbObjectType::Table
1041 };
1042 }
1043 if schema.get_source_by_id(object_id.as_source_id()).is_some() {
1044 return PbObjectType::Source;
1045 }
1046 if schema.get_view_by_id(object_id.as_view_id()).is_some() {
1047 return PbObjectType::View;
1048 }
1049 if schema.get_index_by_id(object_id.as_index_id()).is_some() {
1050 return PbObjectType::Index;
1051 }
1052 }
1053 }
1054 PbObjectType::Unspecified
1055 }
1056
1057 fn insert_object_dependencies(&self, object_id: ObjectId, dependencies: HashSet<ObjectId>) {
1058 if dependencies.is_empty() {
1059 return;
1060 }
1061 let dependencies = dependencies
1062 .into_iter()
1063 .map(|referenced_object_id| PbObjectDependency {
1064 object_id,
1065 referenced_object_id,
1066 referenced_object_type: self.get_object_type(referenced_object_id) as i32,
1067 })
1068 .collect();
1069 self.catalog
1070 .write()
1071 .insert_object_dependencies(dependencies);
1072 }
1073}
1074
1075pub struct MockUserInfoWriter {
1076 id: AtomicU32,
1077 user_info: Arc<RwLock<UserInfoManager>>,
1078}
1079
1080#[async_trait::async_trait]
1081impl UserInfoWriter for MockUserInfoWriter {
1082 async fn create_user(&self, user: UserInfo) -> Result<()> {
1083 let mut user = user;
1084 user.id = self.gen_id().into();
1085 self.user_info.write().create_user(user);
1086 Ok(())
1087 }
1088
1089 async fn drop_user(&self, id: UserId) -> Result<()> {
1090 self.user_info.write().drop_user(id);
1091 Ok(())
1092 }
1093
1094 async fn update_user(
1095 &self,
1096 update_user: UserInfo,
1097 update_fields: Vec<UpdateField>,
1098 ) -> Result<()> {
1099 let mut lock = self.user_info.write();
1100 let id = update_user.get_id();
1101 let Some(old_name) = lock.get_user_name_by_id(id) else {
1102 return Ok(());
1103 };
1104 let mut user_info = lock.get_user_by_name(&old_name).unwrap().to_prost();
1105 update_fields.into_iter().for_each(|field| match field {
1106 UpdateField::Super => user_info.is_super = update_user.is_super,
1107 UpdateField::Login => user_info.can_login = update_user.can_login,
1108 UpdateField::CreateDb => user_info.can_create_db = update_user.can_create_db,
1109 UpdateField::CreateUser => user_info.can_create_user = update_user.can_create_user,
1110 UpdateField::AuthInfo => user_info.auth_info.clone_from(&update_user.auth_info),
1111 UpdateField::Rename => user_info.name.clone_from(&update_user.name),
1112 UpdateField::Admin => user_info.is_admin = update_user.is_admin,
1113 UpdateField::Unspecified => unreachable!(),
1114 });
1115 lock.update_user(update_user);
1116 Ok(())
1117 }
1118
1119 async fn grant_privilege(
1122 &self,
1123 users: Vec<UserId>,
1124 privileges: Vec<GrantPrivilege>,
1125 with_grant_option: bool,
1126 _grantor: UserId,
1127 ) -> Result<()> {
1128 let privileges = privileges
1129 .into_iter()
1130 .map(|mut p| {
1131 p.action_with_opts
1132 .iter_mut()
1133 .for_each(|ao| ao.with_grant_option = with_grant_option);
1134 p
1135 })
1136 .collect::<Vec<_>>();
1137 for user_id in users {
1138 if let Some(u) = self.user_info.write().get_user_mut(user_id) {
1139 u.extend_privileges(privileges.clone());
1140 }
1141 }
1142 Ok(())
1143 }
1144
1145 async fn revoke_privilege(
1148 &self,
1149 users: Vec<UserId>,
1150 privileges: Vec<GrantPrivilege>,
1151 _granted_by: UserId,
1152 _revoke_by: UserId,
1153 revoke_grant_option: bool,
1154 _cascade: bool,
1155 ) -> Result<()> {
1156 for user_id in users {
1157 if let Some(u) = self.user_info.write().get_user_mut(user_id) {
1158 u.revoke_privileges(privileges.clone(), revoke_grant_option);
1159 }
1160 }
1161 Ok(())
1162 }
1163
1164 async fn alter_default_privilege(
1165 &self,
1166 _users: Vec<UserId>,
1167 _database_id: DatabaseId,
1168 _schemas: Vec<SchemaId>,
1169 _operation: AlterDefaultPrivilegeOperation,
1170 _operated_by: UserId,
1171 ) -> Result<()> {
1172 todo!()
1173 }
1174}
1175
1176impl MockUserInfoWriter {
1177 pub fn new(user_info: Arc<RwLock<UserInfoManager>>) -> Self {
1178 user_info.write().create_user(UserInfo {
1179 id: DEFAULT_SUPER_USER_ID,
1180 name: DEFAULT_SUPER_USER.to_owned(),
1181 is_super: true,
1182 can_create_db: true,
1183 can_create_user: true,
1184 can_login: true,
1185 ..Default::default()
1186 });
1187 user_info.write().create_user(UserInfo {
1188 id: DEFAULT_SUPER_USER_FOR_ADMIN_ID,
1189 name: DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
1190 is_super: true,
1191 can_create_db: true,
1192 can_create_user: true,
1193 can_login: true,
1194 is_admin: true,
1195 ..Default::default()
1196 });
1197 Self {
1198 user_info,
1199 id: AtomicU32::new(NON_RESERVED_USER_ID.as_raw_id()),
1200 }
1201 }
1202
1203 fn gen_id(&self) -> u32 {
1204 self.id.fetch_add(1, Ordering::SeqCst)
1205 }
1206}
1207
1208pub struct MockFrontendMetaClient {}
1209
1210#[async_trait::async_trait]
1211impl FrontendMetaClient for MockFrontendMetaClient {
1212 async fn try_unregister(&self) {}
1213
1214 async fn flush(&self, _database_id: DatabaseId) -> RpcResult<HummockVersionId> {
1215 Ok(INVALID_VERSION_ID)
1216 }
1217
1218 async fn cancel_creating_jobs(&self, _infos: PbJobs) -> RpcResult<Vec<u32>> {
1219 Ok(vec![])
1220 }
1221
1222 async fn list_table_fragments(
1223 &self,
1224 _table_ids: &[JobId],
1225 ) -> RpcResult<HashMap<JobId, TableFragmentInfo>> {
1226 Ok(HashMap::default())
1227 }
1228
1229 async fn list_streaming_job_states(&self) -> RpcResult<Vec<StreamingJobState>> {
1230 Ok(vec![])
1231 }
1232
1233 async fn list_fragment_distribution(
1234 &self,
1235 _include_node: bool,
1236 ) -> RpcResult<Vec<FragmentDistribution>> {
1237 Ok(vec![])
1238 }
1239
1240 async fn list_creating_fragment_distribution(&self) -> RpcResult<Vec<FragmentDistribution>> {
1241 Ok(vec![])
1242 }
1243
1244 async fn list_actor_states(&self) -> RpcResult<Vec<ActorState>> {
1245 Ok(vec![])
1246 }
1247
1248 async fn list_actor_splits(&self) -> RpcResult<Vec<ActorSplit>> {
1249 Ok(vec![])
1250 }
1251
1252 async fn list_meta_snapshots(&self) -> RpcResult<Vec<MetaSnapshotMetadata>> {
1253 Ok(vec![])
1254 }
1255
1256 async fn list_sink_log_store_tables(
1257 &self,
1258 ) -> RpcResult<Vec<list_sink_log_store_tables_response::SinkLogStoreTable>> {
1259 Ok(vec![])
1260 }
1261
1262 async fn set_system_param(
1263 &self,
1264 _param: String,
1265 _value: Option<String>,
1266 ) -> RpcResult<Option<SystemParamsReader>> {
1267 Ok(Some(SystemParams::default().into()))
1268 }
1269
1270 async fn get_session_params(&self) -> RpcResult<SessionConfig> {
1271 Ok(Default::default())
1272 }
1273
1274 async fn set_session_param(&self, _param: String, _value: Option<String>) -> RpcResult<String> {
1275 Ok("".to_owned())
1276 }
1277
1278 async fn get_ddl_progress(&self) -> RpcResult<Vec<DdlProgress>> {
1279 Ok(vec![])
1280 }
1281
1282 async fn get_tables(
1283 &self,
1284 _table_ids: Vec<crate::catalog::TableId>,
1285 _include_dropped_tables: bool,
1286 ) -> RpcResult<HashMap<crate::catalog::TableId, Table>> {
1287 Ok(HashMap::new())
1288 }
1289
1290 async fn list_hummock_pinned_versions(&self) -> RpcResult<Vec<(WorkerId, HummockVersionId)>> {
1291 unimplemented!()
1292 }
1293
1294 async fn list_refresh_table_states(&self) -> RpcResult<Vec<RefreshTableState>> {
1295 unimplemented!()
1296 }
1297
1298 async fn get_hummock_current_version(&self) -> RpcResult<HummockVersion> {
1299 Ok(HummockVersion::default())
1300 }
1301
1302 async fn get_hummock_checkpoint_version(&self) -> RpcResult<HummockVersion> {
1303 unimplemented!()
1304 }
1305
1306 async fn list_version_deltas(&self) -> RpcResult<Vec<HummockVersionDelta>> {
1307 unimplemented!()
1308 }
1309
1310 async fn list_branched_objects(&self) -> RpcResult<Vec<BranchedObject>> {
1311 unimplemented!()
1312 }
1313
1314 async fn list_hummock_compaction_group_configs(&self) -> RpcResult<Vec<CompactionGroupInfo>> {
1315 unimplemented!()
1316 }
1317
1318 async fn list_hummock_active_write_limits(
1319 &self,
1320 ) -> RpcResult<HashMap<CompactionGroupId, WriteLimit>> {
1321 unimplemented!()
1322 }
1323
1324 async fn list_hummock_meta_configs(&self) -> RpcResult<HashMap<String, String>> {
1325 unimplemented!()
1326 }
1327
1328 async fn list_event_log(&self) -> RpcResult<Vec<EventLog>> {
1329 Ok(vec![])
1330 }
1331
1332 async fn list_compact_task_assignment(&self) -> RpcResult<Vec<CompactTaskAssignment>> {
1333 unimplemented!()
1334 }
1335
1336 async fn list_all_nodes(&self) -> RpcResult<Vec<WorkerNode>> {
1337 Ok(vec![])
1338 }
1339
1340 async fn list_compact_task_progress(&self) -> RpcResult<Vec<CompactTaskProgress>> {
1341 unimplemented!()
1342 }
1343
1344 async fn recover(&self) -> RpcResult<()> {
1345 unimplemented!()
1346 }
1347
1348 async fn backup_meta(&self, _remarks: Option<String>) -> RpcResult<u64> {
1349 unimplemented!()
1350 }
1351
1352 async fn get_backup_job_status(
1353 &self,
1354 _job_id: u64,
1355 ) -> RpcResult<(risingwave_pb::backup_service::BackupJobStatus, String)> {
1356 unimplemented!()
1357 }
1358
1359 async fn delete_meta_snapshot(&self, _snapshot_ids: &[u64]) -> RpcResult<()> {
1360 unimplemented!()
1361 }
1362
1363 async fn apply_throttle(
1364 &self,
1365 _throttle_target: PbThrottleTarget,
1366 _throttle_type: risingwave_pb::common::PbThrottleType,
1367 _id: u32,
1368 _rate_limit: Option<u32>,
1369 ) -> RpcResult<()> {
1370 unimplemented!()
1371 }
1372
1373 async fn alter_fragment_parallelism(
1374 &self,
1375 _fragment_ids: Vec<FragmentId>,
1376 _parallelism: Option<PbTableParallelism>,
1377 ) -> RpcResult<()> {
1378 unimplemented!()
1379 }
1380
1381 async fn get_cluster_recovery_status(&self) -> RpcResult<RecoveryStatus> {
1382 Ok(RecoveryStatus::StatusRunning)
1383 }
1384
1385 async fn get_cluster_limits(&self) -> RpcResult<Vec<ClusterLimit>> {
1386 Ok(vec![])
1387 }
1388
1389 async fn list_rate_limits(&self) -> RpcResult<Vec<RateLimitInfo>> {
1390 Ok(vec![])
1391 }
1392
1393 async fn list_cdc_progress(&self) -> RpcResult<HashMap<JobId, PbCdcProgress>> {
1394 Ok(HashMap::default())
1395 }
1396
1397 async fn get_meta_store_endpoint(&self) -> RpcResult<String> {
1398 unimplemented!()
1399 }
1400
1401 async fn alter_sink_props(
1402 &self,
1403 _sink_id: SinkId,
1404 _changed_props: BTreeMap<String, String>,
1405 _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1406 _connector_conn_ref: Option<ConnectionId>,
1407 ) -> RpcResult<()> {
1408 unimplemented!()
1409 }
1410
1411 async fn alter_iceberg_table_props(
1412 &self,
1413 _table_id: TableId,
1414 _sink_id: SinkId,
1415 _source_id: SourceId,
1416 _changed_props: BTreeMap<String, String>,
1417 _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1418 _connector_conn_ref: Option<ConnectionId>,
1419 ) -> RpcResult<()> {
1420 unimplemented!()
1421 }
1422
1423 async fn alter_source_connector_props(
1424 &self,
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_connection_connector_props(
1434 &self,
1435 _connection_id: u32,
1436 _changed_props: BTreeMap<String, String>,
1437 _changed_secret_refs: BTreeMap<String, PbSecretRef>,
1438 ) -> RpcResult<()> {
1439 Ok(())
1440 }
1441
1442 async fn list_hosted_iceberg_tables(&self) -> RpcResult<Vec<IcebergTable>> {
1443 unimplemented!()
1444 }
1445
1446 async fn list_iceberg_compaction_status(&self) -> RpcResult<Vec<IcebergCompactionStatus>> {
1447 Ok(vec![])
1448 }
1449
1450 async fn get_fragment_by_id(
1451 &self,
1452 _fragment_id: FragmentId,
1453 ) -> RpcResult<Option<FragmentDistribution>> {
1454 unimplemented!()
1455 }
1456
1457 async fn get_fragment_vnodes(
1458 &self,
1459 _fragment_id: FragmentId,
1460 ) -> RpcResult<Vec<(ActorId, Vec<u32>)>> {
1461 unimplemented!()
1462 }
1463
1464 async fn get_actor_vnodes(&self, _actor_id: ActorId) -> RpcResult<Vec<u32>> {
1465 unimplemented!()
1466 }
1467
1468 fn worker_id(&self) -> WorkerId {
1469 0.into()
1470 }
1471
1472 async fn set_sync_log_store_aligned(&self, _job_id: JobId, _aligned: bool) -> RpcResult<()> {
1473 Ok(())
1474 }
1475
1476 async fn compact_iceberg_table(&self, _sink_id: SinkId) -> RpcResult<IcebergCompactionTaskId> {
1477 Ok(1.into())
1478 }
1479
1480 async fn rewrite_iceberg_table_manifests(&self, _sink_id: SinkId) -> RpcResult<()> {
1481 Ok(())
1482 }
1483
1484 async fn expire_iceberg_table_snapshots(&self, _sink_id: SinkId) -> RpcResult<()> {
1485 Ok(())
1486 }
1487
1488 async fn refresh(&self, _request: RefreshRequest) -> RpcResult<RefreshResponse> {
1489 Ok(RefreshResponse { status: None })
1490 }
1491
1492 fn cluster_id(&self) -> &str {
1493 "test-cluster-uuid"
1494 }
1495
1496 async fn list_unmigrated_tables(&self) -> RpcResult<HashMap<crate::catalog::TableId, String>> {
1497 unimplemented!()
1498 }
1499
1500 async fn get_hummock_table_change_log(
1501 &self,
1502 _start_epoch_inclusive: Option<u64>,
1503 _end_epoch_inclusive: Option<u64>,
1504 _table_ids: Option<HashSet<TableId>>,
1505 _exclude_empty: bool,
1506 _limit: Option<u32>,
1507 ) -> RpcResult<TableChangeLogs> {
1508 Ok(HashMap::default())
1509 }
1510
1511 async fn update_compaction_config(
1512 &self,
1513 _compaction_group_ids: Vec<risingwave_hummock_sdk::CompactionGroupId>,
1514 _configs: Vec<PbMutableConfig>,
1515 ) -> RpcResult<()> {
1516 Ok(())
1517 }
1518}
1519
1520#[cfg(test)]
1521pub static PROTO_FILE_DATA: &str = r#"
1522 syntax = "proto3";
1523 package test;
1524 message TestRecord {
1525 int32 id = 1;
1526 Country country = 3;
1527 int64 zipcode = 4;
1528 float rate = 5;
1529 }
1530 message TestRecordAlterType {
1531 string id = 1;
1532 Country country = 3;
1533 int32 zipcode = 4;
1534 float rate = 5;
1535 }
1536 message TestRecordExt {
1537 int32 id = 1;
1538 Country country = 3;
1539 int64 zipcode = 4;
1540 float rate = 5;
1541 string name = 6;
1542 }
1543 message Country {
1544 string address = 1;
1545 City city = 2;
1546 string zipcode = 3;
1547 }
1548 message City {
1549 string address = 1;
1550 string zipcode = 2;
1551 }"#;
1552
1553pub fn create_proto_file(proto_data: &str) -> NamedTempFile {
1556 let in_file = Builder::new()
1557 .prefix("temp")
1558 .suffix(".proto")
1559 .rand_bytes(8)
1560 .tempfile()
1561 .unwrap();
1562
1563 let out_file = Builder::new()
1564 .prefix("temp")
1565 .suffix(".pb")
1566 .rand_bytes(8)
1567 .tempfile()
1568 .unwrap();
1569
1570 let mut file = in_file.as_file();
1571 file.write_all(proto_data.as_ref())
1572 .expect("writing binary to test file");
1573 file.flush().expect("flush temp file failed");
1574 let include_path = in_file
1575 .path()
1576 .parent()
1577 .unwrap()
1578 .to_string_lossy()
1579 .into_owned();
1580 let out_path = out_file.path().to_string_lossy().into_owned();
1581 let in_path = in_file.path().to_string_lossy().into_owned();
1582 let mut compile = std::process::Command::new("protoc");
1583
1584 let out = compile
1585 .arg("--include_imports")
1586 .arg("-I")
1587 .arg(include_path)
1588 .arg(format!("--descriptor_set_out={}", out_path))
1589 .arg(in_path)
1590 .output()
1591 .expect("failed to compile proto");
1592 if !out.status.success() {
1593 panic!("compile proto failed \n output: {:?}", out);
1594 }
1595 out_file
1596}