use rupg_common::{Error, Result, SqlState};
use rupg_sql::nodes::{
AlterTableCmd, AlterTableStmt, AlterTableType, ConstrType, Constraint, CreateStmt, Node,
ObjectType, RangeVar,
};
use rupg_sql::{Lexer, character, token};
use super::table::constraint_attrs;
use crate::coerce::AtOpt;
use crate::typename::place;
#[derive(Debug)]
pub struct SchemaElements {
pub owner: u32,
pub schema: String,
pub elements: Vec<(Node, String)>,
}
pub(super) fn schema_elements(
elements: &[Node],
schema: &str,
text: &str,
) -> Result<Vec<(Node, String)>> {
let texts = element_texts(text, elements.len());
let mut groups: [Vec<(Node, String)>; 7] = Default::default();
for (element, text) in elements.iter().zip(texts) {
let (group, relation) = match element {
Node::CreateSeqStmt(stmt) => (0, stmt.sequence.as_deref()),
Node::CreateStmt(stmt) => (1, stmt.relation.as_deref()),
Node::ViewStmt(stmt) => (2, stmt.view.as_deref()),
Node::IndexStmt(stmt) => (3, stmt.relation.as_deref()),
Node::CreateTrigStmt(stmt) => (4, stmt.relation.as_deref()),
Node::GrantStmt(_) => (5, None),
_ => return Err(Error::internal("unrecognized node type in CREATE SCHEMA")),
};
if let Some(relation) = relation {
check_schema_name(relation, schema)?;
}
let element = match element {
Node::CreateStmt(stmt) => {
let mut keys = Vec::new();
let table = without_foreign_keys(stmt, &mut keys)?;
groups[6].extend(keys.into_iter().map(|key| (key, String::new())));
Node::CreateStmt(Box::new(table))
}
element => element.clone(),
};
groups[group].push((element, text));
}
Ok(groups.into_iter().flatten().collect())
}
fn check_schema_name(relation: &RangeVar, schema: &str) -> Result<()> {
let at = place(relation.location);
if let Some(name) = relation.schemaname.as_deref()
&& name != schema
{
return Err(Error::new(
SqlState::INVALID_SCHEMA_DEFINITION,
format!(
"CREATE specifies a schema ({name}) different from the one being created ({schema})"
),
)
.at_opt(at));
}
if relation.relpersistence == b't' {
return Err(Error::new(
SqlState::INVALID_TABLE_DEFINITION,
"cannot create temporary relation in non-temporary schema",
)
.at_opt(at));
}
Ok(())
}
fn without_foreign_keys(stmt: &CreateStmt, keys: &mut Vec<Node>) -> Result<CreateStmt> {
let add = |con: Constraint| {
Node::AlterTableStmt(Box::new(AlterTableStmt {
relation: stmt.relation.clone(),
cmds: vec![Some(Node::AlterTableCmd(Box::new(AlterTableCmd {
subtype: AlterTableType::AT_AddConstraint,
def: Some(Node::Constraint(Box::new(con))),
..AlterTableCmd::default()
})))],
objtype: ObjectType::OBJECT_TABLE,
missing_ok: false,
}))
};
let mut table = stmt.clone();
table.tableElts.clear();
for element in &stmt.tableElts {
match element {
Some(Node::Constraint(con)) if con.contype == ConstrType::CONSTR_FOREIGN => {
keys.push(add((**con).clone()));
}
Some(Node::ColumnDef(def)) => {
let mut cons: Vec<Constraint> = def
.constraints
.iter()
.flatten()
.filter_map(|n| match n {
Node::Constraint(c) => Some((**c).clone()),
_ => None,
})
.collect();
constraint_attrs(&mut cons)?;
let mut after_key = false;
let mut kept = Vec::new();
for mut con in cons {
match con.contype {
ConstrType::CONSTR_FOREIGN => {
let name = def.colname.as_deref().unwrap_or_default();
con.fk_attrs = vec![Some(Node::String(name.into()))];
keys.push(add(con));
after_key = true;
}
ConstrType::CONSTR_ATTR_DEFERRABLE
| ConstrType::CONSTR_ATTR_NOT_DEFERRABLE
| ConstrType::CONSTR_ATTR_DEFERRED
| ConstrType::CONSTR_ATTR_IMMEDIATE
| ConstrType::CONSTR_ATTR_ENFORCED
| ConstrType::CONSTR_ATTR_NOT_ENFORCED => {
if !after_key {
kept.push(con);
}
}
_ => {
after_key = false;
kept.push(con);
}
}
}
let mut def = (**def).clone();
def.constraints =
kept.into_iter().map(|con| Some(Node::Constraint(Box::new(con)))).collect();
table.tableElts.push(Some(Node::ColumnDef(Box::new(def))));
}
element => table.tableElts.push(element.clone()),
}
}
Ok(table)
}
fn element_texts(text: &str, count: usize) -> Vec<String> {
let (open, close, comma) = (character(b'('), character(b')'), character(b','));
let mut lexer = Lexer::new(text);
let mut starts = Vec::new();
let mut depth = 0usize;
let mut previous = None;
while let Ok(word) = lexer.next_token() {
let kind = Some(word.kind);
if word.kind == 0 {
break;
} else if kind == open {
depth += 1;
} else if kind == close {
depth = depth.saturating_sub(1);
} else if depth == 0 && previous.is_some() {
let create =
word.kind == token::CREATE && previous != Some(token::GRANT) && previous != comma;
let grant = word.kind == token::GRANT && previous != Some(token::WITH);
if create || grant {
starts.push(word.start);
}
}
previous = kind;
}
if starts.len() != count {
return vec![text.to_string(); count];
}
let ends = starts.iter().skip(1).copied().chain([text.len()]);
starts.iter().zip(ends).map(|(&start, end)| text[start..end].trim_end().to_string()).collect()
}