1use std::collections::{HashMap, HashSet, VecDeque};
21use std::fmt::Debug;
22use std::sync::Arc;
23use std::sync::RwLock;
24
25use async_trait::async_trait;
26use datafusion::catalog::{CatalogProvider, MemorySchemaProvider, SchemaProvider};
27use datafusion::common::{plan_datafusion_err, Column};
28use datafusion::datasource::{TableProvider, TableType};
29use datafusion::error::Result as DFResult;
30use datafusion::execution::SessionState;
31use datafusion::logical_expr::{expr_fn::cast, Expr, LogicalPlan, LogicalPlanBuilder};
32use datafusion::sql::planner::IdentNormalizer;
33use datafusion::sql::sqlparser::ast::{Ident, ObjectName, Query, Statement, Visit, Visitor};
34use datafusion::sql::sqlparser::dialect::GenericDialect;
35use datafusion::sql::sqlparser::parser::Parser;
36use paimon::catalog::{Catalog, Identifier, View};
37
38use crate::error::to_datafusion_error;
39use crate::runtime::{await_with_runtime, block_on_with_runtime};
40use crate::system_tables;
41use crate::table::PaimonTableProvider;
42use crate::{BlobReaderRegistry, DynamicOptions};
43
44pub(crate) type SessionStateProvider = Arc<dyn Fn() -> Option<SessionState> + Send + Sync>;
45
46pub struct PaimonCatalogProvider {
52 catalog_name: Option<String>,
53 catalog: Arc<dyn Catalog>,
55 dynamic_options: DynamicOptions,
57 temp_tables: Arc<RwLock<HashMap<String, Arc<MemorySchemaProvider>>>>,
65 blob_reader_registry: BlobReaderRegistry,
66 session_state: Option<SessionStateProvider>,
67 schema_force_view_types: bool,
68}
69
70impl Debug for PaimonCatalogProvider {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.debug_struct("PaimonCatalogProvider").finish()
73 }
74}
75
76impl PaimonCatalogProvider {
77 pub fn new(
79 catalog_name: Option<String>,
80 catalog: Arc<dyn Catalog>,
81 dynamic_options: DynamicOptions,
82 blob_reader_registry: BlobReaderRegistry,
83 session_state: Option<SessionStateProvider>,
84 ) -> Self {
85 PaimonCatalogProvider {
86 catalog_name,
87 catalog,
88 dynamic_options,
89 temp_tables: Arc::new(RwLock::new(HashMap::new())),
90 blob_reader_registry,
91 session_state,
92 schema_force_view_types: true,
93 }
94 }
95
96 pub fn with_schema_force_view_types(mut self, schema_force_view_types: bool) -> Self {
102 self.schema_force_view_types = schema_force_view_types;
103 self
104 }
105}
106
107impl CatalogProvider for PaimonCatalogProvider {
108 fn schema_names(&self) -> Vec<String> {
109 let catalog = Arc::clone(&self.catalog);
110 block_on_with_runtime(
111 async move {
112 catalog.list_databases().await.unwrap_or_else(|e| {
113 log::error!("failed to list databases: {e}");
114 vec![]
115 })
116 },
117 "paimon catalog access thread panicked",
118 )
119 }
120
121 fn schema(&self, name: &str) -> Option<Arc<dyn SchemaProvider>> {
122 let catalog = Arc::clone(&self.catalog);
123 let dynamic_options = Arc::clone(&self.dynamic_options);
124 let blob_reader_registry = self.blob_reader_registry.clone();
125 let catalog_name = self.catalog_name.clone();
126 let session_state = self.session_state.clone();
127 let schema_force_view_types = self.schema_force_view_types;
128 let name = name.to_string();
129
130 let temp_provider = {
131 let databases = self.temp_tables.read().unwrap_or_else(|e| e.into_inner());
132 databases.get(&name).cloned()
133 };
134
135 block_on_with_runtime(
136 async move {
137 match catalog.get_database(&name).await {
138 Ok(_) => Some(Arc::new(
139 PaimonSchemaProvider::new(
140 catalog_name,
141 Arc::clone(&catalog),
142 name,
143 dynamic_options,
144 temp_provider,
145 blob_reader_registry,
146 session_state,
147 )
148 .with_schema_force_view_types(schema_force_view_types),
149 ) as Arc<dyn SchemaProvider>),
150 Err(paimon::Error::DatabaseNotExist { .. }) => {
151 if temp_provider.is_some() {
152 Some(Arc::new(
153 PaimonSchemaProvider::new(
154 catalog_name,
155 Arc::clone(&catalog),
156 name,
157 dynamic_options,
158 temp_provider,
159 blob_reader_registry,
160 session_state,
161 )
162 .with_schema_force_view_types(schema_force_view_types),
163 ) as Arc<dyn SchemaProvider>)
164 } else {
165 None
166 }
167 }
168 Err(e) => {
169 log::error!("failed to get database '{}': {e}", name);
170 None
171 }
172 }
173 },
174 "paimon catalog access thread panicked",
175 )
176 }
177
178 fn register_schema(
179 &self,
180 name: &str,
181 _schema: Arc<dyn SchemaProvider>,
182 ) -> DFResult<Option<Arc<dyn SchemaProvider>>> {
183 let catalog = Arc::clone(&self.catalog);
184 let dynamic_options = Arc::clone(&self.dynamic_options);
185 let blob_reader_registry = self.blob_reader_registry.clone();
186 let catalog_name = self.catalog_name.clone();
187 let session_state = self.session_state.clone();
188 let schema_force_view_types = self.schema_force_view_types;
189 let name = name.to_string();
190 block_on_with_runtime(
191 async move {
192 catalog
193 .create_database(&name, false, HashMap::new())
194 .await
195 .map_err(to_datafusion_error)?;
196 Ok(Some(Arc::new(
197 PaimonSchemaProvider::new(
198 catalog_name,
199 Arc::clone(&catalog),
200 name,
201 dynamic_options,
202 None,
203 blob_reader_registry,
204 session_state,
205 )
206 .with_schema_force_view_types(schema_force_view_types),
207 ) as Arc<dyn SchemaProvider>))
208 },
209 "paimon catalog access thread panicked",
210 )
211 }
212
213 fn deregister_schema(
214 &self,
215 name: &str,
216 cascade: bool,
217 ) -> DFResult<Option<Arc<dyn SchemaProvider>>> {
218 let catalog = Arc::clone(&self.catalog);
219 let dynamic_options = Arc::clone(&self.dynamic_options);
220 let blob_reader_registry = self.blob_reader_registry.clone();
221 let catalog_name = self.catalog_name.clone();
222 let session_state = self.session_state.clone();
223 let schema_force_view_types = self.schema_force_view_types;
224 let name = name.to_string();
225 block_on_with_runtime(
226 async move {
227 catalog
228 .drop_database(&name, false, cascade)
229 .await
230 .map_err(to_datafusion_error)?;
231 Ok(Some(Arc::new(
232 PaimonSchemaProvider::new(
233 catalog_name,
234 Arc::clone(&catalog),
235 name,
236 dynamic_options,
237 None,
238 blob_reader_registry,
239 session_state,
240 )
241 .with_schema_force_view_types(schema_force_view_types),
242 ) as Arc<dyn SchemaProvider>))
243 },
244 "paimon catalog access thread panicked",
245 )
246 }
247}
248
249impl PaimonCatalogProvider {
250 pub fn register_temp_table(
256 &self,
257 database: &str,
258 table_name: &str,
259 table: Arc<dyn TableProvider>,
260 ) -> DFResult<()> {
261 let catalog = Arc::clone(&self.catalog);
263 let db = database.to_string();
264 let tbl = table_name.to_string();
265 let identifier = Identifier::new(db, tbl);
266 if let Ok(true) = block_on_with_runtime(
267 async move {
268 match catalog.get_table(&identifier).await {
269 Ok(_) => Ok::<bool, paimon::Error>(true),
270 Err(paimon::Error::TableNotExist { .. }) => Ok(false),
271 Err(_) => Ok(false),
272 }
273 },
274 "paimon catalog access thread panicked",
275 ) {
276 log::warn!(
277 "Temporary table '{database}.{table_name}' shadows an existing Paimon table"
278 );
279 }
280
281 let mut databases = self.temp_tables.write().unwrap_or_else(|e| e.into_inner());
283 let mem_database = databases
284 .entry(database.to_string())
285 .or_insert_with(|| Arc::new(MemorySchemaProvider::new()));
286
287 let old = mem_database.register_table(table_name.to_string(), table)?;
289 if old.is_some() {
290 return Err(plan_datafusion_err!(
291 "Temporary table '{database}.{table_name}' already exists"
292 ));
293 }
294 Ok(())
295 }
296
297 pub fn deregister_temp_table(
299 &self,
300 database: &str,
301 table_name: &str,
302 ) -> DFResult<Option<Arc<dyn TableProvider>>> {
303 let databases = self.temp_tables.read().unwrap_or_else(|e| e.into_inner());
304 let mem_database = databases
305 .get(database)
306 .ok_or_else(|| plan_datafusion_err!("Unknown temp database '{database}'"))?;
307 mem_database.deregister_table(table_name)
308 }
309
310 pub fn has_temp_table_database(&self, name: &str) -> bool {
312 self.temp_tables
313 .read()
314 .unwrap_or_else(|e| e.into_inner())
315 .contains_key(name)
316 }
317
318 pub fn temp_table_exist(&self, database: &str, table_name: &str) -> bool {
320 let databases = self.temp_tables.read().unwrap_or_else(|e| e.into_inner());
321 databases
322 .get(database)
323 .is_some_and(|db| db.table_exist(table_name))
324 }
325}
326
327pub struct PaimonSchemaProvider {
332 catalog_name: Option<String>,
333 catalog: Arc<dyn Catalog>,
335 database: String,
337 dynamic_options: DynamicOptions,
339 temp_provider: Option<Arc<MemorySchemaProvider>>,
341 catalog_table_types: RwLock<HashMap<String, TableType>>,
343 blob_reader_registry: BlobReaderRegistry,
344 session_state: Option<SessionStateProvider>,
345 schema_force_view_types: bool,
346}
347
348impl Debug for PaimonSchemaProvider {
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350 f.debug_struct("PaimonSchemaProvider")
351 .field("database", &self.database)
352 .field("has_temp_provider", &self.temp_provider.is_some())
353 .finish()
354 }
355}
356
357impl PaimonSchemaProvider {
358 pub fn new(
360 catalog_name: Option<String>,
361 catalog: Arc<dyn Catalog>,
362 database: String,
363 dynamic_options: DynamicOptions,
364 temp_provider: Option<Arc<MemorySchemaProvider>>,
365 blob_reader_registry: BlobReaderRegistry,
366 session_state: Option<SessionStateProvider>,
367 ) -> Self {
368 PaimonSchemaProvider {
369 catalog_name,
370 catalog,
371 database,
372 dynamic_options,
373 temp_provider,
374 catalog_table_types: RwLock::new(HashMap::new()),
375 blob_reader_registry,
376 session_state,
377 schema_force_view_types: true,
378 }
379 }
380
381 fn with_schema_force_view_types(mut self, schema_force_view_types: bool) -> Self {
382 self.schema_force_view_types = schema_force_view_types;
383 self
384 }
385}
386
387#[async_trait]
388impl SchemaProvider for PaimonSchemaProvider {
389 fn table_names(&self) -> Vec<String> {
390 let catalog = Arc::clone(&self.catalog);
391 let database = self.database.clone();
392 let (mut names, views) = block_on_with_runtime(
393 {
394 let db = database.clone();
395 async move {
396 let names = match catalog.list_tables(&db).await {
397 Ok(names) => names,
398 Err(e) => {
399 log::error!("failed to list tables in '{}': {e}", db);
400 vec![]
401 }
402 };
403 let views = match catalog.list_views(&db).await {
404 Ok(views) => views,
405 Err(paimon::Error::Unsupported { .. }) => vec![],
406 Err(error) => {
407 log::error!("failed to list views in '{}': {error}", db);
408 vec![]
409 }
410 };
411 (names, views)
412 }
413 },
414 "paimon catalog access thread panicked",
415 );
416
417 let mut catalog_table_types = HashMap::with_capacity(names.len() + views.len());
418 for name in &names {
419 catalog_table_types.insert(name.clone(), TableType::Base);
420 }
421 for view in views {
422 catalog_table_types
423 .entry(view.clone())
424 .or_insert(TableType::View);
425 names.push(view);
426 }
427 *self
428 .catalog_table_types
429 .write()
430 .unwrap_or_else(|e| e.into_inner()) = catalog_table_types;
431
432 if let Some(temp) = &self.temp_provider {
433 names.extend(temp.table_names());
434 }
435
436 let mut seen = std::collections::HashSet::new();
437 names.retain(|name| seen.insert(name.clone()));
438
439 names
440 }
441
442 async fn table(&self, name: &str) -> DFResult<Option<Arc<dyn TableProvider>>> {
443 if let Some(temp) = &self.temp_provider {
444 if let Some(table) = temp.table(name).await? {
445 return Ok(Some(table));
446 }
447 }
448
449 let object = system_tables::parse_object_name_for_datafusion(name)?;
450 if let Some(system_name) = object.system_table().map(str::to_string) {
451 return await_with_runtime(system_tables::load(
452 Arc::clone(&self.catalog),
453 self.database.clone(),
454 object,
455 system_name,
456 ))
457 .await;
458 }
459
460 let catalog = Arc::clone(&self.catalog);
461 let dynamic_options = Arc::clone(&self.dynamic_options);
462 let blob_reader_registry = self.blob_reader_registry.clone();
463 let catalog_name = self.catalog_name.clone();
464 let session_state = self.session_state.clone();
465 let schema_force_view_types = self.schema_force_view_types;
466 let identifier = Identifier::new(self.database.clone(), object.table().to_string());
467 let branch = object.branch().map(str::to_string);
468 await_with_runtime(async move {
469 match catalog.get_table(&identifier).await {
470 Ok(mut table) => {
471 if let Some(branch) = branch.as_deref() {
472 table = table
473 .copy_with_branch(branch)
474 .await
475 .map_err(to_datafusion_error)?;
476 }
477 let opts = dynamic_options.read().unwrap().clone();
478 let provider = if opts.is_empty() {
479 PaimonTableProvider::try_new_with_blob_reader_registry(
480 table,
481 blob_reader_registry,
482 )?
483 } else {
484 let table_definition = crate::table::build_table_definition(&table).ok();
485 let table = table
489 .copy_with_time_travel(opts)
490 .await
491 .map_err(to_datafusion_error)?;
492 PaimonTableProvider::try_new_with_blob_reader_registry_and_definition(
493 table,
494 blob_reader_registry,
495 table_definition,
496 )?
497 }
498 .with_schema_force_view_types(schema_force_view_types)?;
499 Ok(Some(Arc::new(provider) as Arc<dyn TableProvider>))
500 }
501 Err(paimon::Error::TableNotExist { .. }) => {
502 if branch.is_some() {
503 return Ok(None);
504 }
505 if session_state
509 .as_ref()
510 .and_then(|provider| provider())
511 .is_some_and(|state| state.table_functions().contains_key(identifier.object()))
512 {
513 return Ok(None);
514 }
515 let view = match catalog.get_view(&identifier).await {
516 Ok(view) => view,
517 Err(paimon::Error::ViewNotExist { .. })
518 | Err(paimon::Error::Unsupported { .. }) => return Ok(None),
519 Err(error) => return Err(to_datafusion_error(error)),
520 };
521 let catalog_name = catalog_name.ok_or_else(|| {
522 plan_datafusion_err!(
523 "REST catalog view '{}' requires a session-aware catalog provider",
524 identifier.full_name()
525 )
526 })?;
527 validate_view_dependencies(&catalog, &catalog_name, &view).await?;
528 let mut state = session_state
529 .and_then(|provider| provider())
530 .ok_or_else(|| {
531 plan_datafusion_err!(
532 "DataFusion session is unavailable while planning REST catalog view '{}'",
533 identifier.full_name()
534 )
535 })?;
536 state.config_mut().options_mut().catalog.default_catalog =
537 catalog_name.clone();
538 state.config_mut().options_mut().catalog.default_schema =
539 identifier.database().to_string();
540 let catalogs =
541 HashMap::from([(catalog_name.clone(), Arc::clone(&catalog))]);
542 let query = crate::sql_function::expand_sql(
543 view.query_for("datafusion"),
544 &catalogs,
545 &catalog_name,
546 identifier.database(),
547 )
548 .await?;
549 let plan = state.create_logical_plan(&query).await?;
550 let plan = enforce_view_schema(plan, &view)?;
551 Ok(Some(Arc::new(datafusion::datasource::ViewTable::new(
552 plan,
553 Some(query),
554 )) as Arc<dyn TableProvider>))
555 }
556 Err(e) => Err(to_datafusion_error(e)),
557 }
558 })
559 .await
560 }
561
562 async fn table_type(&self, name: &str) -> DFResult<Option<TableType>> {
563 if let Some(temp) = &self.temp_provider {
564 if let Some(table_type) = temp.table_type(name).await? {
565 return Ok(Some(table_type));
566 }
567 }
568
569 if let Some(table_type) = self
570 .catalog_table_types
571 .read()
572 .unwrap_or_else(|e| e.into_inner())
573 .get(name)
574 {
575 return Ok(Some(*table_type));
576 }
577
578 self.table(name)
579 .await
580 .map(|table| table.map(|table| table.table_type()))
581 }
582
583 fn table_exist(&self, name: &str) -> bool {
584 if let Some(temp) = &self.temp_provider {
585 if temp.table_exist(name) {
586 return true;
587 }
588 }
589
590 let object = match system_tables::parse_object_name_for_datafusion(name) {
591 Ok(object) => object,
592 Err(e) => {
593 log::error!("failed to parse Paimon object name '{name}': {e}");
594 return false;
595 }
596 };
597 if let Some(system_name) = object.system_table() {
598 if !system_tables::is_registered(system_name) {
599 return false;
600 }
601 }
602
603 let catalog = Arc::clone(&self.catalog);
604 let identifier = Identifier::new(self.database.clone(), object.table().to_string());
605 let branch = object.branch().map(str::to_string);
606 let is_branches_table = object
607 .system_table()
608 .is_some_and(|name| name.eq_ignore_ascii_case("branches"));
609 block_on_with_runtime(
610 async move {
611 match catalog.get_table(&identifier).await {
612 Ok(table) => {
613 if let Some(branch) = branch.as_deref() {
614 if is_branches_table {
615 return true;
616 }
617 table.copy_with_branch(branch).await.is_ok()
618 } else {
619 true
620 }
621 }
622 Err(paimon::Error::TableNotExist { .. }) => {
623 if branch.is_some() {
624 return false;
625 }
626 match catalog.get_view(&identifier).await {
627 Ok(_) => true,
628 Err(paimon::Error::ViewNotExist { .. })
629 | Err(paimon::Error::Unsupported { .. }) => false,
630 Err(error) => {
631 log::error!("failed to check view '{}': {error}", identifier);
632 false
633 }
634 }
635 }
636 Err(e) => {
637 log::error!("failed to check table '{}': {e}", identifier);
638 false
639 }
640 }
641 },
642 "paimon catalog access thread panicked",
643 )
644 }
645
646 fn register_table(
647 &self,
648 _name: String,
649 table: Arc<dyn TableProvider>,
650 ) -> DFResult<Option<Arc<dyn TableProvider>>> {
651 Ok(Some(table))
654 }
655
656 fn deregister_table(&self, name: &str) -> DFResult<Option<Arc<dyn TableProvider>>> {
657 let catalog = Arc::clone(&self.catalog);
658 let identifier = Identifier::new(self.database.clone(), name);
659 block_on_with_runtime(
660 async move {
661 let table = match catalog.get_table(&identifier).await {
663 Ok(t) => t,
664 Err(paimon::Error::TableNotExist { .. }) => return Ok(None),
665 Err(e) => return Err(to_datafusion_error(e)),
666 };
667 let provider = PaimonTableProvider::try_new(table)?;
668 catalog
669 .drop_table(&identifier, false)
670 .await
671 .map_err(to_datafusion_error)?;
672 Ok(Some(Arc::new(provider) as Arc<dyn TableProvider>))
673 },
674 "paimon catalog access thread panicked",
675 )
676 }
677}
678
679fn enforce_view_schema(plan: LogicalPlan, view: &View) -> DFResult<LogicalPlan> {
680 let declared_fields = view.schema().fields();
681 let actual_fields = plan.schema().fields();
682 if actual_fields.len() != declared_fields.len() {
683 return Err(plan_datafusion_err!(
684 "REST catalog view '{}' declares {} fields but its query returns {}",
685 view.full_name(),
686 declared_fields.len(),
687 actual_fields.len()
688 ));
689 }
690
691 let expressions = declared_fields
692 .iter()
693 .enumerate()
694 .map(|(index, declared)| {
695 let (qualifier, actual) = plan.schema().qualified_field(index);
696 let column = match qualifier {
697 Some(qualifier) => Column::new(Some(qualifier.clone()), actual.name()),
698 None => Column::new_unqualified(actual.name()),
699 };
700 let target_type = paimon::arrow::paimon_type_to_arrow(declared.data_type())
701 .map_err(to_datafusion_error)?;
702 Ok(cast(Expr::Column(column), target_type).alias(declared.name()))
703 })
704 .collect::<DFResult<Vec<_>>>()?;
705
706 LogicalPlanBuilder::from(plan).project(expressions)?.build()
707}
708
709const MAX_VIEW_DEPENDENCIES: usize = 64;
710
711async fn validate_view_dependencies(
712 catalog: &Arc<dyn Catalog>,
713 catalog_name: &str,
714 root: &View,
715) -> DFResult<()> {
716 let mut queue = VecDeque::from([root.clone()]);
717 let mut loaded = HashSet::from([root.identifier().clone()]);
718 let mut dependencies = HashMap::<Identifier, Vec<Identifier>>::new();
719
720 while let Some(view) = queue.pop_front() {
721 let candidates = view_relation_identifiers(&view, catalog_name)?;
722 let mut view_dependencies = Vec::new();
723 for identifier in candidates {
724 match catalog.get_table(&identifier).await {
725 Ok(_) => continue,
726 Err(paimon::Error::TableNotExist { .. })
727 | Err(paimon::Error::Unsupported { .. }) => {}
728 Err(error) => return Err(to_datafusion_error(error)),
729 }
730
731 let dependency = match catalog.get_view(&identifier).await {
732 Ok(view) => view,
733 Err(paimon::Error::ViewNotExist { .. })
734 | Err(paimon::Error::Unsupported { .. }) => continue,
735 Err(error) => return Err(to_datafusion_error(error)),
736 };
737 view_dependencies.push(identifier.clone());
738 if loaded.insert(identifier) {
739 if loaded.len() > MAX_VIEW_DEPENDENCIES {
740 return Err(plan_datafusion_err!(
741 "REST catalog view '{}' exceeds the dependency limit of {}",
742 root.full_name(),
743 MAX_VIEW_DEPENDENCIES
744 ));
745 }
746 queue.push_back(dependency);
747 }
748 }
749 dependencies.insert(view.identifier().clone(), view_dependencies);
750
751 if let Some(cycle) = find_view_dependency_cycle(&dependencies) {
752 let path = cycle
753 .iter()
754 .map(Identifier::full_name)
755 .collect::<Vec<_>>()
756 .join(" -> ");
757 return Err(plan_datafusion_err!(
758 "recursive REST catalog view dependency detected: {path}"
759 ));
760 }
761 }
762 Ok(())
763}
764
765fn view_relation_identifiers(view: &View, catalog_name: &str) -> DFResult<Vec<Identifier>> {
766 let statements =
767 Parser::parse_sql(&GenericDialect {}, view.query_for("datafusion")).map_err(|error| {
768 plan_datafusion_err!(
769 "Invalid SQL for REST catalog view '{}': {error}",
770 view.full_name()
771 )
772 })?;
773 if statements.len() != 1 {
774 return Err(plan_datafusion_err!(
775 "REST catalog view '{}' must contain exactly one SQL statement",
776 view.full_name()
777 ));
778 }
779 if !matches!(statements.first(), Some(Statement::Query(_))) {
780 return Err(plan_datafusion_err!(
781 "REST catalog view '{}' must contain a single read-only query",
782 view.full_name()
783 ));
784 }
785
786 let mut visitor = ViewRelationVisitor::new(catalog_name, view.identifier().database());
787 let _: std::ops::ControlFlow<()> = statements.visit(&mut visitor);
788 Ok(visitor.identifiers)
789}
790
791type SqlIdentifierKey = String;
792
793struct QueryCteScope {
794 visible: HashSet<SqlIdentifierKey>,
795 cte_query_visibility: HashMap<usize, HashSet<SqlIdentifierKey>>,
796}
797
798struct ViewRelationVisitor<'a> {
799 catalog_name: &'a str,
800 current_database: &'a str,
801 scopes: Vec<QueryCteScope>,
802 identifiers: Vec<Identifier>,
803}
804
805impl<'a> ViewRelationVisitor<'a> {
806 fn new(catalog_name: &'a str, current_database: &'a str) -> Self {
807 Self {
808 catalog_name,
809 current_database,
810 scopes: Vec::new(),
811 identifiers: Vec::new(),
812 }
813 }
814}
815
816impl Visitor for ViewRelationVisitor<'_> {
817 type Break = ();
818
819 fn pre_visit_query(&mut self, query: &Query) -> std::ops::ControlFlow<Self::Break> {
820 let query_address = query as *const Query as usize;
821 let inherited = self
822 .scopes
823 .last()
824 .map(|scope| {
825 scope
826 .cte_query_visibility
827 .get(&query_address)
828 .unwrap_or(&scope.visible)
829 .clone()
830 })
831 .unwrap_or_default();
832 let mut visible = inherited.clone();
833 let mut cte_query_visibility = HashMap::new();
834
835 if let Some(with) = &query.with {
836 let local_ctes = with
837 .cte_tables
838 .iter()
839 .map(|cte| sql_identifier_key(&cte.alias.name))
840 .collect::<Vec<_>>();
841 if with.recursive {
842 visible.extend(local_ctes);
843 for cte in &with.cte_tables {
844 cte_query_visibility
845 .insert(cte.query.as_ref() as *const Query as usize, visible.clone());
846 }
847 } else {
848 for (cte, alias) in with.cte_tables.iter().zip(local_ctes) {
849 cte_query_visibility
850 .insert(cte.query.as_ref() as *const Query as usize, visible.clone());
851 visible.insert(alias);
852 }
853 }
854 }
855
856 self.scopes.push(QueryCteScope {
857 visible,
858 cte_query_visibility,
859 });
860 std::ops::ControlFlow::Continue(())
861 }
862
863 fn post_visit_query(&mut self, _query: &Query) -> std::ops::ControlFlow<Self::Break> {
864 self.scopes.pop();
865 std::ops::ControlFlow::Continue(())
866 }
867
868 fn pre_visit_relation(&mut self, relation: &ObjectName) -> std::ops::ControlFlow<Self::Break> {
869 let is_cte = match relation.0.as_slice() {
870 [part] => part.as_ident().is_some_and(|identifier| {
871 self.scopes
872 .last()
873 .is_some_and(|scope| scope.visible.contains(&sql_identifier_key(identifier)))
874 }),
875 _ => false,
876 };
877 if !is_cte {
878 if let Some(identifier) =
879 relation_identifier(relation, self.catalog_name, self.current_database)
880 {
881 self.identifiers.push(identifier);
882 }
883 }
884 std::ops::ControlFlow::Continue(())
885 }
886}
887
888fn sql_identifier_key(identifier: &Ident) -> SqlIdentifierKey {
889 IdentNormalizer::default().normalize(identifier.clone())
890}
891
892fn relation_identifier(
893 relation: &ObjectName,
894 catalog_name: &str,
895 current_database: &str,
896) -> Option<Identifier> {
897 let parts = relation
898 .0
899 .iter()
900 .map(|part| part.as_ident().map(sql_identifier_key))
901 .collect::<Option<Vec<_>>>()?;
902 match parts.as_slice() {
903 [object] => Some(Identifier::new(current_database, object.as_str())),
904 [database, object] => Some(Identifier::new(database.as_str(), object.as_str())),
905 [catalog, database, object] if catalog == catalog_name => {
906 Some(Identifier::new(database.as_str(), object.as_str()))
907 }
908 _ => None,
909 }
910}
911
912fn find_view_dependency_cycle(
913 dependencies: &HashMap<Identifier, Vec<Identifier>>,
914) -> Option<Vec<Identifier>> {
915 fn visit(
916 identifier: &Identifier,
917 dependencies: &HashMap<Identifier, Vec<Identifier>>,
918 finished: &mut HashSet<Identifier>,
919 path: &mut Vec<Identifier>,
920 ) -> Option<Vec<Identifier>> {
921 if let Some(start) = path.iter().position(|entry| entry == identifier) {
922 let mut cycle = path[start..].to_vec();
923 cycle.push(identifier.clone());
924 return Some(cycle);
925 }
926 if finished.contains(identifier) {
927 return None;
928 }
929
930 path.push(identifier.clone());
931 if let Some(next_identifiers) = dependencies.get(identifier) {
932 for next in next_identifiers {
933 if let Some(cycle) = visit(next, dependencies, finished, path) {
934 return Some(cycle);
935 }
936 }
937 }
938 path.pop();
939 finished.insert(identifier.clone());
940 None
941 }
942
943 let mut finished = HashSet::new();
944 for identifier in dependencies.keys() {
945 if let Some(cycle) = visit(identifier, dependencies, &mut finished, &mut Vec::new()) {
946 return Some(cycle);
947 }
948 }
949 None
950}
951
952#[cfg(test)]
953mod tests {
954 use super::*;
955
956 #[test]
957 fn relation_identifiers_follow_datafusion_normalization() {
958 let relation = ObjectName(vec![
959 datafusion::sql::sqlparser::ast::ObjectNamePart::Identifier(Ident::new("PAIMON")),
960 datafusion::sql::sqlparser::ast::ObjectNamePart::Identifier(Ident::new("DEFAULT")),
961 datafusion::sql::sqlparser::ast::ObjectNamePart::Identifier(Ident::new("ANSWER_VIEW")),
962 ]);
963
964 assert_eq!(
965 relation_identifier(&relation, "paimon", "unused"),
966 Some(Identifier::new("default", "answer_view"))
967 );
968 }
969}