use crate::core::failed;
use crate::event::Event;
use crate::failure::FailureCode;
use crate::view::ViewDef;
impl crate::core::Core {
pub fn native_view_result(&self, name: &str) -> Result<crate::sql_types::SqlResult, String> {
let db = self
.state
.database
.as_ref()
.ok_or_else(|| "no database is open".to_owned())?;
let view = db
.native_views
.iter()
.find(|view| view.name.eq_ignore_ascii_case(name))
.ok_or_else(|| format!("native view {name:?} not found"))?;
let rows = self
.port
.read_rows(
&view.name,
crate::port::RowRange {
start: 0,
len: usize::MAX,
},
)
.map_err(|error| error.to_string())?;
Ok(crate::sql_types::SqlResult {
columns: view.fields.iter().map(|field| field.name.clone()).collect(),
rows,
})
}
pub(crate) fn view_command(&mut self, action: crate::view::ViewCommand) -> Vec<Event> {
match action {
crate::view::ViewCommand::Create { name, sql } => self.create_view(&name, &sql),
crate::view::ViewCommand::SetSql { name, sql } => self.set_view_sql(&name, &sql),
crate::view::ViewCommand::Delete { name } => self.delete_view(&name),
}
}
pub(crate) fn create_view(&mut self, name: &str, sql: &str) -> Vec<Event> {
if self.state.database.is_none() {
return failed(
FailureCode::NoDatabase,
"no database is open — open a database first",
);
}
let trimmed = name.trim();
if trimmed.is_empty() {
return failed(
FailureCode::InvalidInput,
crate::schema::SchemaError::InvalidName {
detail: "names must not be empty or whitespace-only".to_owned(),
}
.to_string(),
);
}
let view = ViewDef::new(trimmed, sql.trim());
if !view.is_select() {
return failed(
FailureCode::InvalidInput,
format!("view {trimmed:?} must be a SELECT statement"),
);
}
let db = self.state.database.as_ref().expect("checked above");
if db
.tables
.iter()
.any(|table| table.name.eq_ignore_ascii_case(trimmed))
|| db
.views
.iter()
.any(|existing| existing.name.eq_ignore_ascii_case(trimmed))
|| db
.native_views
.iter()
.any(|existing| existing.name.eq_ignore_ascii_case(trimmed))
{
return failed(
FailureCode::Duplicate,
format!("duplicate view name {trimmed:?}"),
);
}
let canonical = trimmed.to_owned();
self.state
.database
.as_mut()
.expect("checked above")
.views
.push(ViewDef::new(canonical.clone(), sql.trim()));
if self.port.supports_native_views()
&& let Err(error) = self.port.create_native_view(&canonical, sql.trim())
{
self.state
.database
.as_mut()
.expect("checked above")
.views
.pop();
return failed(FailureCode::Port, error.to_string());
}
vec![Event::ViewCreated { name: canonical }]
}
pub(crate) fn set_view_sql(&mut self, name: &str, sql: &str) -> Vec<Event> {
let Some(db) = self.state.database.as_mut() else {
return failed(
FailureCode::NoDatabase,
"no database is open — open a database first",
);
};
let updated = ViewDef::new(name, sql.trim());
if !updated.is_select() {
return failed(
FailureCode::InvalidInput,
format!("view {name:?} must be a SELECT statement"),
);
}
let Some(view) = db
.views
.iter_mut()
.find(|view| view.name.eq_ignore_ascii_case(name))
else {
return failed(FailureCode::NotFound, format!("view {name:?} not found"));
};
let old_sql = view.sql.clone();
view.sql = sql.trim().to_owned();
let canonical = view.name.clone();
if self.port.supports_native_views() {
let applied = self
.port
.drop_native_view(&canonical)
.and_then(|()| self.port.create_native_view(&canonical, sql.trim()));
if let Err(error) = applied {
if let Some(view) = self
.state
.database
.as_mut()
.expect("checked above")
.views
.iter_mut()
.find(|view| view.name == canonical)
{
view.sql = old_sql.clone();
}
let _ = self.port.create_native_view(&canonical, &old_sql);
return failed(FailureCode::Port, error.to_string());
}
}
vec![Event::ViewChanged { name: canonical }]
}
pub(crate) fn delete_view(&mut self, name: &str) -> Vec<Event> {
let canonical = {
let Some(db) = self.state.database.as_ref() else {
return failed(
FailureCode::NoDatabase,
"no database is open — open a database first",
);
};
let Some(view) = db
.views
.iter()
.find(|view| view.name.eq_ignore_ascii_case(name))
else {
return failed(FailureCode::NotFound, format!("view {name:?} not found"));
};
view.name.clone()
};
if self.port.supports_native_views()
&& let Err(error) = self.port.drop_native_view(&canonical)
{
return failed(FailureCode::Port, error.to_string());
}
let Some(db) = self.state.database.as_mut() else {
return failed(
FailureCode::NoDatabase,
"no database is open — open a database first",
);
};
let Some(position) = db.views.iter().position(|view| view.name == canonical) else {
return failed(FailureCode::NotFound, format!("view {name:?} not found"));
};
db.views.remove(position);
let dependent: Vec<crate::ids::TabId> = self
.state
.tabs
.iter()
.filter(|tab| {
tab.object.kind == crate::ObjectKind::View
&& tab.object.name.eq_ignore_ascii_case(&canonical)
})
.map(|tab| tab.id)
.collect();
let mut events: Vec<Event> = Vec::with_capacity(dependent.len() + 1);
for tab_id in dependent {
self.remove_tab(tab_id);
events.push(Event::TabClosed { tab_id });
}
events.push(Event::ViewDeleted { name: canonical });
events
}
}