use std::collections::HashMap;
use std::sync::Arc;
use glaredb_error::{DbError, Result};
use glaredb_parser::ast;
use tracing::error;
use super::ResolveContext;
use super::resolved_table::{
ResolvedTableOrCteReference,
ResolvedTableReference,
ResolvedViewReference,
UnresolvedTableReference,
};
use crate::catalog::context::{DatabaseContext, SYSTEM_CATALOG};
use crate::catalog::database::Database;
use crate::catalog::entry::{CatalogEntry, CatalogEntryInner, CatalogEntryType};
use crate::catalog::memory::MemorySchema;
use crate::catalog::system::DEFAULT_SCHEMA;
use crate::catalog::{Catalog, Schema};
use crate::expr;
use crate::functions::function_set::TableFunctionSet;
use crate::functions::table::TableFunctionInput;
use crate::functions::table::scan::ScanContext;
use crate::runtime::system::SystemRuntime;
pub fn create_user_facing_resolve_err(
schema_ent: Option<&MemorySchema>,
object_types: &[CatalogEntryType],
name: &str,
) -> DbError {
let similar = match schema_ent {
Some(schema_ent) => match schema_ent.find_similar_entry(object_types, name) {
Ok(maybe_similar) => maybe_similar,
Err(e) => {
error!(%e, %name, "failed to find similar entry to include in error message");
None
}
},
None => None,
};
let formatted_object_types = object_types
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>()
.join(" or ");
match similar {
Some(similar) => DbError::new(format!(
"Cannot resolve {} with name '{}', did you mean '{}'?",
formatted_object_types, name, similar.name,
)),
None => DbError::new(format!(
"Cannot resolve {} with name '{}'",
formatted_object_types, name
)),
}
}
#[derive(Debug)]
pub enum MaybeResolvedTable {
Resolved(ResolvedTableOrCteReference),
UnresolvedWithCatalog(UnresolvedTableReference),
Unresolved,
}
#[derive(Debug)]
pub struct NormalResolver<'a, R: SystemRuntime> {
pub context: &'a DatabaseContext,
pub runtime: &'a R,
}
impl<'a, R> NormalResolver<'a, R>
where
R: SystemRuntime,
{
pub fn new(context: &'a DatabaseContext, runtime: &'a R) -> Self {
NormalResolver { context, runtime }
}
pub fn resolve_table_function(
&self,
reference: &ast::ObjectReference,
) -> Result<Option<&'static TableFunctionSet>> {
let [catalog, schema, name] = match reference.0.len() {
1 => [
SYSTEM_CATALOG.to_string(),
DEFAULT_SCHEMA.to_string(),
reference.0[0].as_normalized_string(),
],
2 => {
let name = reference.0[1].as_normalized_string();
let schema = reference.0[0].as_normalized_string();
[SYSTEM_CATALOG.to_string(), schema, name]
}
3 => {
let name = reference.0[2].as_normalized_string();
let schema = reference.0[1].as_normalized_string();
let catalog = reference.0[0].as_normalized_string();
[catalog, schema, name]
}
_ => {
return Err(DbError::new(
"Unexpected number of identifiers in table function reference",
));
}
};
let schema_ent = match self
.context
.require_get_database(&catalog)?
.catalog
.get_schema(&schema)?
{
Some(ent) => ent,
None => return Ok(None),
};
match schema_ent.get_table_function(&name)? {
Some(entry) => Ok(Some(entry.try_as_table_function_entry()?.function)),
_ => Ok(None),
}
}
pub fn require_resolve_table_function(
&self,
reference: &ast::ObjectReference,
) -> Result<&'static TableFunctionSet> {
self.resolve_table_function(reference)?.ok_or_else(|| {
DbError::new(format!(
"Missing table function for reference '{}'",
reference
))
})
}
pub async fn resolve_table_or_cte(
&self,
reference: &ast::ObjectReference,
resolve_context: &ResolveContext,
) -> Result<MaybeResolvedTable> {
let [catalog, schema, table] = match reference.0.len() {
1 => {
let name = reference.0[0].as_normalized_string();
if let Some(cte) = resolve_context.find_cte(&name) {
return Ok(MaybeResolvedTable::Resolved(
ResolvedTableOrCteReference::Cte(cte.name.clone()),
));
}
["temp".to_string(), "temp".to_string(), name]
}
2 => {
let table = reference.0[1].as_normalized_string();
let schema = reference.0[0].as_normalized_string();
["temp".to_string(), schema, table]
}
3 => {
let table = reference.0[2].as_normalized_string();
let schema = reference.0[1].as_normalized_string();
let catalog = reference.0[0].as_normalized_string();
[catalog, schema, table]
}
_ => {
return Err(DbError::new(
"Unexpected number of identifiers in table reference",
));
}
};
let database = self.context.require_get_database(&catalog)?;
if let Some(entry) = self.resolve_from_memory_catalog(database, &schema, &table)? {
match &entry.entry {
CatalogEntryInner::Table(table_ent) => {
let inputs = TableFunctionInput {
positional: vec![
expr::lit(catalog.clone()).into(),
expr::lit(schema.clone()).into(),
expr::lit(table.clone()).into(),
],
named: HashMap::new(),
};
let scan_context = ScanContext {
dispatch: self.runtime.filesystem_dispatch(),
database_context: self.context,
};
let planned =
expr::bind_table_scan_function(table_ent.function, scan_context, inputs)
.await?;
return Ok(MaybeResolvedTable::Resolved(
ResolvedTableOrCteReference::Table(ResolvedTableReference {
catalog,
schema,
entry,
scan_function: planned,
}),
));
}
CatalogEntryInner::View(_) => {
return Ok(MaybeResolvedTable::Resolved(
ResolvedTableOrCteReference::View(ResolvedViewReference {
catalog,
schema,
entry,
}),
));
}
_ => {
return Err(DbError::new(format!(
"Unexpected catalog entry type: {:?}",
entry.entry_type()
)));
}
}
}
Ok(MaybeResolvedTable::UnresolvedWithCatalog(
UnresolvedTableReference {
catalog: catalog.to_string(),
reference: reference.clone(),
attach_info: database.attach_info.clone(),
},
))
}
pub fn require_resolve_function_for_path(
&self,
path: &str,
) -> Result<&'static TableFunctionSet> {
let schema_ent = self
.context
.require_get_database(SYSTEM_CATALOG)?
.catalog
.require_get_schema(DEFAULT_SCHEMA)?;
let ent = schema_ent
.get_inferred_table_function(path)?
.ok_or_else(|| {
DbError::new(format!(
"Could not find a suitable table function to use for path '{path}'"
))
})?;
let func = ent.try_as_table_function_entry()?.function;
Ok(func)
}
fn resolve_from_memory_catalog(
&self,
database: &Database,
schema: &str,
table: &str,
) -> Result<Option<Arc<CatalogEntry>>> {
let schema_ent = match database.catalog.get_schema(schema)? {
Some(ent) => ent,
None => return Ok(None),
};
schema_ent.get_table_or_view(table)
}
pub async fn require_resolve_table_or_cte(
&self,
reference: &ast::ObjectReference,
resolve_context: &ResolveContext,
) -> Result<ResolvedTableOrCteReference> {
match self
.resolve_table_or_cte(reference, resolve_context)
.await?
{
MaybeResolvedTable::Resolved(table) => Ok(table),
_ => Err(DbError::new(format!(
"Missing table or view for reference '{}'",
reference
))),
}
}
}