use gpui_kit::component::table::ColumnSort;
use crate::db::query::{Cell, QueryResult};
use crate::db::{
Engine, RowKey, keyword_literal, placeholder, quote_identifier, text_type, typed_placeholder,
};
use crate::ui::data_grid::StagedRow;
use crate::ui::filter_bar::{FilterSpec, Operator};
use super::TableView;
impl TableView {
fn target(&self) -> String {
let engine = self.connection.config.engine;
match &self.object.schema {
Some(schema) => format!(
"{}.{}",
quote_identifier(schema, engine),
quote_identifier(&self.object.name, engine)
),
None => quote_identifier(&self.object.name, engine),
}
}
#[cfg(test)]
pub fn query(&self, cx: &gpui_kit::App) -> String {
self.query_with_params(cx).0
}
pub fn query_with_params(&self, cx: &gpui_kit::App) -> (String, Vec<Cell>) {
self.select_sql(true, cx)
}
pub(super) fn select_sql(&self, paged: bool, cx: &gpui_kit::App) -> (String, Vec<Cell>) {
let engine = self.connection.config.engine;
let target = self.target();
let mut sql = match &self.row_key {
Some(RowKey::RowId(id)) => format!("select {id}, * from {target}"),
_ => format!("select * from {target}"),
};
let (conditions, params) = self.where_clause(cx);
if !conditions.is_empty() {
sql.push_str(&format!(" where {}", conditions.join(" and ")));
}
if let Some((column, sort)) = &self.sort {
let direction = match sort {
ColumnSort::Descending => "desc",
_ => "asc",
};
sql.push_str(&format!(
" order by {} {direction}",
quote_identifier(column, engine)
));
}
if paged {
sql.push_str(&format!(
" limit {} offset {}",
self.limit,
self.page * self.limit
));
}
(sql, params)
}
fn where_clause(&self, cx: &gpui_kit::App) -> (Vec<String>, Vec<Cell>) {
let engine = self.connection.config.engine;
let mut params: Vec<Cell> = Vec::new();
let mut conditions = Vec::new();
for spec in self.filters.read(cx).specs(cx) {
let Some(condition) = self.condition(&spec, engine, &mut params) else {
continue;
};
conditions.push(condition);
}
(conditions, params)
}
fn condition(
&self,
spec: &FilterSpec,
engine: Engine,
params: &mut Vec<Cell>,
) -> Option<String> {
let column = quote_identifier(&spec.column, engine);
let type_name = self
.columns
.iter()
.position(|name| *name == spec.column)
.and_then(|index| self.column_types.get(index).cloned())
.unwrap_or_default();
Some(match spec.operator {
Operator::IsNull => format!("{column} is null"),
Operator::IsNotNull => format!("{column} is not null"),
Operator::In => format!("{column} in ({})", spec.value),
Operator::NotIn => format!("{column} not in ({})", spec.value),
Operator::Like | Operator::NotLike => {
params.push(Some(spec.value.clone()));
let keyword = if spec.operator == Operator::Like {
"like"
} else {
"not like"
};
format!(
"cast({column} as {}) {keyword} {}",
text_type(engine),
placeholder(engine, params.len())
)
}
operator => {
params.push(Some(spec.value.clone()));
format!(
"{column} {} {}",
operator.label(),
typed_placeholder(engine, params.len(), &type_name)
)
}
})
}
pub(super) fn take_key_column(&mut self, result: &mut QueryResult) {
self.key_values.clear();
self.key_type = None;
if !matches!(self.row_key, Some(RowKey::RowId(_))) {
return;
}
let (values, key_type) = take_row_id(result);
self.key_values = values;
self.key_type = key_type;
}
fn key_for(&self, row: usize, cx: &gpui_kit::App) -> Option<Vec<(String, String, Cell)>> {
let engine = self.connection.config.engine;
match self.row_key.as_ref()? {
RowKey::Columns(columns) => {
let loaded = self.grid.read(cx).baseline_row(row, cx)?;
columns
.iter()
.map(|column| {
let index = self.columns.iter().position(|name| name == column)?;
let value = loaded.get(index)?.clone();
value.as_ref()?;
Some((
quote_identifier(column, engine),
self.column_types.get(index).cloned().unwrap_or_default(),
value,
))
})
.collect()
}
RowKey::RowId(id) => {
let value = self.key_values.get(row)?.clone();
value.as_ref()?;
Some(vec![(
id.to_string(),
self.key_type.clone().unwrap_or_default(),
value,
)])
}
RowKey::Unavailable(_) => None,
}
}
pub(super) fn update_statement(
&self,
staged: &StagedRow,
cx: &gpui_kit::App,
) -> Option<(String, Vec<Cell>)> {
if staged.cells.is_empty() {
return None;
}
let engine = self.connection.config.engine;
let key = self.key_for(staged.row, cx)?;
let mut params: Vec<Cell> = Vec::with_capacity(staged.cells.len() + key.len());
let assignments: Vec<String> = staged
.cells
.iter()
.map(|(column, value)| {
let name = self.columns.get(*column).cloned().unwrap_or_default();
if let Some(keyword) = value.as_deref().and_then(keyword_literal) {
return format!("{} = {keyword}", quote_identifier(&name, engine));
}
params.push(value.clone());
let type_name = self.column_types.get(*column).cloned().unwrap_or_default();
format!(
"{} = {}",
quote_identifier(&name, engine),
typed_placeholder(engine, params.len(), &type_name)
)
})
.collect();
let conditions: Vec<String> = key
.into_iter()
.map(|(left, type_name, value)| {
params.push(value);
format!(
"{left} = {}",
typed_placeholder(engine, params.len(), &type_name)
)
})
.collect();
Some((
format!(
"update {} set {} where {}",
self.target(),
assignments.join(", "),
conditions.join(" and ")
),
params,
))
}
pub(super) fn insert_statement(&self, cells: &[(usize, Cell)]) -> Option<(String, Vec<Cell>)> {
if cells.is_empty() {
return None;
}
let engine = self.connection.config.engine;
let mut params: Vec<Cell> = Vec::with_capacity(cells.len());
let mut columns = Vec::with_capacity(cells.len());
let mut values = Vec::with_capacity(cells.len());
for (column, value) in cells {
let name = self.columns.get(*column).cloned().unwrap_or_default();
if name.is_empty() {
return None;
}
columns.push(quote_identifier(&name, engine));
if let Some(keyword) = value.as_deref().and_then(keyword_literal) {
values.push(keyword.to_string());
continue;
}
let type_name = self.column_types.get(*column).cloned().unwrap_or_default();
params.push(value.clone());
values.push(typed_placeholder(engine, params.len(), &type_name));
}
Some((
format!(
"insert into {} ({}) values ({})",
self.target(),
columns.join(", "),
values.join(", ")
),
params,
))
}
pub(super) fn binary_select(
&self,
row: usize,
column: &str,
cx: &gpui_kit::App,
) -> Option<(String, Vec<Cell>)> {
let engine = self.connection.config.engine;
let key = self.key_for(row, cx)?;
let mut params: Vec<Cell> = Vec::with_capacity(key.len());
let conditions: Vec<String> = key
.into_iter()
.enumerate()
.map(|(index, (left, type_name, value))| {
params.push(value);
format!(
"{left} = {}",
typed_placeholder(engine, index + 1, &type_name)
)
})
.collect();
Some((
format!(
"select {} from {} where {}",
quote_identifier(column, engine),
self.target(),
conditions.join(" and ")
),
params,
))
}
pub(super) fn delete_statement(
&self,
row: usize,
cx: &gpui_kit::App,
) -> Option<(String, Vec<Cell>)> {
let engine = self.connection.config.engine;
let key = self.key_for(row, cx)?;
let mut params: Vec<Cell> = Vec::with_capacity(key.len());
let conditions: Vec<String> = key
.into_iter()
.enumerate()
.map(|(index, (left, type_name, value))| {
params.push(value);
format!(
"{left} = {}",
typed_placeholder(engine, index + 1, &type_name)
)
})
.collect();
Some((
format!(
"delete from {} where {}",
self.target(),
conditions.join(" and ")
),
params,
))
}
pub(super) fn touches_key(&self, staged: &StagedRow) -> bool {
let Some(RowKey::Columns(key)) = &self.row_key else {
return false;
};
staged.cells.iter().any(|(column, _)| {
self.columns
.get(*column)
.is_some_and(|name| key.contains(name))
})
}
}
pub(super) fn take_row_id(result: &mut QueryResult) -> (Vec<Cell>, Option<String>) {
let mut values = Vec::new();
if result.columns.is_empty() {
return (values, None);
}
result.columns.remove(0);
let key_type = if result.column_types.is_empty() {
None
} else {
Some(result.column_types.remove(0))
};
for row in &mut result.rows {
if row.is_empty() {
values.push(None);
continue;
}
values.push(row.remove(0));
}
(values, key_type)
}