use squawk_syntax::ast;
fn has_foreign_key_constraint(create_table: &ast::CreateTable) -> bool {
if let Some(table_arg_list) = create_table.table_arg_list() {
for arg in table_arg_list.args() {
match arg {
ast::TableArg::TableConstraint(ast::TableConstraint::ForeignKeyConstraint(_)) => {
return true;
}
ast::TableArg::Column(column) => {
if column
.constraints()
.any(|c| matches!(c, ast::ColumnConstraint::ReferencesConstraint(_)))
{
return true;
}
}
_ => (),
}
}
}
false
}
pub fn possibly_slow_stmt(stmt: &ast::Stmt) -> bool {
match stmt {
ast::Stmt::CreateTable(create_table) => has_foreign_key_constraint(create_table),
| ast::Stmt::AlterAggregate(_)
| ast::Stmt::AlterCollation(_)
| ast::Stmt::AlterConversion(_)
| ast::Stmt::AlterDatabase(_)
| ast::Stmt::AlterDefaultPrivileges(_)
| ast::Stmt::AlterDomain(_)
| ast::Stmt::AlterEventTrigger(_)
| ast::Stmt::AlterExtension(_)
| ast::Stmt::AlterForeignDataWrapper(_)
| ast::Stmt::AlterForeignTable(_)
| ast::Stmt::AlterFunction(_)
| ast::Stmt::AlterGroup(_)
| ast::Stmt::AlterIndex(_)
| ast::Stmt::AlterLanguage(_)
| ast::Stmt::AlterLargeObject(_)
| ast::Stmt::AlterMaterializedView(_)
| ast::Stmt::AlterOperator(_)
| ast::Stmt::AlterOperatorClass(_)
| ast::Stmt::AlterOperatorFamily(_)
| ast::Stmt::AlterPolicy(_)
| ast::Stmt::AlterPropertyGraph(_)
| ast::Stmt::AlterProcedure(_)
| ast::Stmt::AlterPublication(_)
| ast::Stmt::AlterRole(_)
| ast::Stmt::AlterRoutine(_)
| ast::Stmt::AlterRule(_)
| ast::Stmt::AlterSchema(_)
| ast::Stmt::AlterSequence(_)
| ast::Stmt::AlterServer(_)
| ast::Stmt::AlterStatistics(_)
| ast::Stmt::AlterSubscription(_)
| ast::Stmt::AlterSystem(_)
| ast::Stmt::AlterTable(_)
| ast::Stmt::AlterTablespace(_)
| ast::Stmt::AlterTextSearchConfiguration(_)
| ast::Stmt::AlterTextSearchDictionary(_)
| ast::Stmt::AlterTextSearchParser(_)
| ast::Stmt::AlterTextSearchTemplate(_)
| ast::Stmt::AlterTrigger(_)
| ast::Stmt::AlterType(_)
| ast::Stmt::AlterUser(_)
| ast::Stmt::AlterUserMapping(_)
| ast::Stmt::AlterView(_)
| ast::Stmt::CreateAccessMethod(_)
| ast::Stmt::CreateAggregate(_)
| ast::Stmt::CreateCast(_)
| ast::Stmt::CreateCollation(_)
| ast::Stmt::CreateConversion(_)
| ast::Stmt::CreateDatabase(_)
| ast::Stmt::CreateDomain(_)
| ast::Stmt::CreateEventTrigger(_)
| ast::Stmt::CreateExtension(_)
| ast::Stmt::CreateForeignDataWrapper(_)
| ast::Stmt::CreateForeignTable(_)
| ast::Stmt::CreateFunction(_)
| ast::Stmt::CreateGroup(_)
| ast::Stmt::CreateIndex(_)
| ast::Stmt::CreateLanguage(_)
| ast::Stmt::CreateMaterializedView(_)
| ast::Stmt::CreateOperator(_)
| ast::Stmt::CreateOperatorClass(_)
| ast::Stmt::CreateOperatorFamily(_)
| ast::Stmt::CreatePolicy(_)
| ast::Stmt::CreatePropertyGraph(_)
| ast::Stmt::CreateProcedure(_)
| ast::Stmt::CreatePublication(_)
| ast::Stmt::CreateRole(_)
| ast::Stmt::CreateRule(_)
| ast::Stmt::CreateSchema(_)
| ast::Stmt::CreateSequence(_)
| ast::Stmt::CreateServer(_)
| ast::Stmt::CreateStatistics(_)
| ast::Stmt::CreateSubscription(_)
| ast::Stmt::CreateTableAs(_)
| ast::Stmt::CreateTablespace(_)
| ast::Stmt::CreateTextSearchConfiguration(_)
| ast::Stmt::CreateTextSearchDictionary(_)
| ast::Stmt::CreateTextSearchParser(_)
| ast::Stmt::CreateTextSearchTemplate(_)
| ast::Stmt::CreateTransform(_)
| ast::Stmt::CreateTrigger(_)
| ast::Stmt::CreateType(_)
| ast::Stmt::CreateUser(_)
| ast::Stmt::CreateUserMapping(_)
| ast::Stmt::CreateView(_)
| ast::Stmt::DropAccessMethod(_)
| ast::Stmt::DropAggregate(_)
| ast::Stmt::DropCast(_)
| ast::Stmt::DropCollation(_)
| ast::Stmt::DropConversion(_)
| ast::Stmt::DropDatabase(_)
| ast::Stmt::DropDomain(_)
| ast::Stmt::DropEventTrigger(_)
| ast::Stmt::DropExtension(_)
| ast::Stmt::DropForeignDataWrapper(_)
| ast::Stmt::DropForeignTable(_)
| ast::Stmt::DropFunction(_)
| ast::Stmt::DropGroup(_)
| ast::Stmt::DropIndex(_)
| ast::Stmt::DropLanguage(_)
| ast::Stmt::DropMaterializedView(_)
| ast::Stmt::DropOperator(_)
| ast::Stmt::DropOperatorClass(_)
| ast::Stmt::DropOperatorFamily(_)
| ast::Stmt::DropOwned(_)
| ast::Stmt::DropPolicy(_)
| ast::Stmt::DropPropertyGraph(_)
| ast::Stmt::DropProcedure(_)
| ast::Stmt::DropPublication(_)
| ast::Stmt::DropRole(_)
| ast::Stmt::DropRoutine(_)
| ast::Stmt::DropRule(_)
| ast::Stmt::DropSchema(_)
| ast::Stmt::DropSequence(_)
| ast::Stmt::DropServer(_)
| ast::Stmt::DropStatistics(_)
| ast::Stmt::DropSubscription(_)
| ast::Stmt::DropTable(_)
| ast::Stmt::DropTablespace(_)
| ast::Stmt::DropTextSearchConfig(_)
| ast::Stmt::DropTextSearchDict(_)
| ast::Stmt::DropTextSearchParser(_)
| ast::Stmt::DropTextSearchTemplate(_)
| ast::Stmt::DropTransform(_)
| ast::Stmt::DropTrigger(_)
| ast::Stmt::DropType(_)
| ast::Stmt::DropUser(_)
| ast::Stmt::DropUserMapping(_)
| ast::Stmt::DropView(_)
| ast::Stmt::Cluster(_)
| ast::Stmt::CommentOn(_)
| ast::Stmt::ImportForeignSchema(_)
| ast::Stmt::Load(_)
| ast::Stmt::Lock(_)
| ast::Stmt::Refresh(_)
| ast::Stmt::Reindex(_)
| ast::Stmt::Truncate(_)
| ast::Stmt::Vacuum(_)
=> true,
ast::Stmt::Analyze(_)
| ast::Stmt::Begin(_)
| ast::Stmt::Call(_)
| ast::Stmt::Checkpoint(_)
| ast::Stmt::Close(_)
| ast::Stmt::Commit(_)
| ast::Stmt::Copy(_)
| ast::Stmt::Deallocate(_)
| ast::Stmt::Declare(_)
| ast::Stmt::Delete(_)
| ast::Stmt::Discard(_)
| ast::Stmt::Do(_)
| ast::Stmt::Execute(_)
| ast::Stmt::Explain(_)
| ast::Stmt::Fetch(_)
| ast::Stmt::Grant(_)
| ast::Stmt::Insert(_)
| ast::Stmt::Listen(_)
| ast::Stmt::Merge(_)
| ast::Stmt::Move(_)
| ast::Stmt::Notify(_)
| ast::Stmt::ParenSelect(_)
| ast::Stmt::Prepare(_)
| ast::Stmt::PrepareTransaction(_)
| ast::Stmt::Reassign(_)
| ast::Stmt::ReleaseSavepoint(_)
| ast::Stmt::Reset(_)
| ast::Stmt::Revoke(_)
| ast::Stmt::Repack(_)
| ast::Stmt::Rollback(_)
| ast::Stmt::Savepoint(_)
| ast::Stmt::SecurityLabel(_)
| ast::Stmt::Select(_)
| ast::Stmt::SelectInto(_)
| ast::Stmt::Set(_)
| ast::Stmt::SetConstraints(_)
| ast::Stmt::SetRole(_)
| ast::Stmt::SetSessionAuth(_)
| ast::Stmt::ResetSessionAuth(_)
| ast::Stmt::SetTransaction(_)
| ast::Stmt::Show(_)
| ast::Stmt::Table(_)
| ast::Stmt::Unlisten(_)
| ast::Stmt::Update(_)
| ast::Stmt::Values(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use squawk_syntax::SourceFile;
#[test]
fn alter_table() {
let sql = "ALTER TABLE users ADD COLUMN email TEXT;";
let file = SourceFile::parse(sql);
let stmts = file.tree().stmts().next().unwrap();
assert!(possibly_slow_stmt(&stmts));
}
#[test]
fn select() {
let sql = "select 1;";
let file = SourceFile::parse(sql);
let stmts = file.tree().stmts().next().unwrap();
assert!(!possibly_slow_stmt(&stmts));
}
#[test]
fn create_table_without_foreign_key() {
let sql = "create table foo (id integer generated by default as identity primary key);";
let file = SourceFile::parse(sql);
let stmts = file.tree().stmts().next().unwrap();
assert!(!possibly_slow_stmt(&stmts));
}
#[test]
fn create_table_with_foreign_key() {
let sql = "create table foo (id integer, user_id integer references users(id));";
let file = SourceFile::parse(sql);
let stmts = file.tree().stmts().next().unwrap();
assert!(possibly_slow_stmt(&stmts));
}
#[test]
fn create_table_with_table_level_foreign_key() {
let sql = "create table foo (id integer, user_id integer, foreign key (user_id) references users(id));";
let file = SourceFile::parse(sql);
let stmts = file.tree().stmts().next().unwrap();
assert!(possibly_slow_stmt(&stmts));
}
}