1use squawk_syntax::ast;
2
3fn has_foreign_key_constraint(create_table: &ast::CreateTable) -> bool {
4 if let Some(table_arg_list) = create_table.table_arg_list() {
5 for arg in table_arg_list.args() {
6 match arg {
7 ast::TableArg::TableConstraint(ast::TableConstraint::ForeignKeyConstraint(_)) => {
8 return true;
9 }
10 ast::TableArg::Column(column) => {
11 if column.clauses().any(|c| {
12 matches!(
13 c,
14 ast::ColumnClause::ColumnConstraint(
15 ast::ColumnConstraint::ReferencesConstraint(_)
16 )
17 )
18 }) {
19 return true;
20 }
21 }
22 _ => (),
23 }
24 }
25 }
26 false
27}
28
29pub fn possibly_slow_stmt(stmt: &ast::Stmt) -> bool {
31 match stmt {
37 ast::Stmt::CreateTable(create_table) => has_foreign_key_constraint(create_table),
39 | ast::Stmt::AlterAggregate(_)
40 | ast::Stmt::AlterCollation(_)
41 | ast::Stmt::AlterConversion(_)
42 | ast::Stmt::AlterDatabase(_)
43 | ast::Stmt::AlterDefaultPrivileges(_)
44 | ast::Stmt::AlterDomain(_)
45 | ast::Stmt::AlterEventTrigger(_)
46 | ast::Stmt::AlterExtension(_)
47 | ast::Stmt::AlterForeignDataWrapper(_)
48 | ast::Stmt::AlterForeignTable(_)
49 | ast::Stmt::AlterFunction(_)
50 | ast::Stmt::AlterGroup(_)
51 | ast::Stmt::AlterIndex(_)
52 | ast::Stmt::AlterLanguage(_)
53 | ast::Stmt::AlterLargeObject(_)
54 | ast::Stmt::AlterMaterializedView(_)
55 | ast::Stmt::AlterOperator(_)
56 | ast::Stmt::AlterOperatorClass(_)
57 | ast::Stmt::AlterOperatorFamily(_)
58 | ast::Stmt::AlterPolicy(_)
59 | ast::Stmt::AlterPropertyGraph(_)
60 | ast::Stmt::AlterProcedure(_)
61 | ast::Stmt::AlterPublication(_)
62 | ast::Stmt::AlterRole(_)
63 | ast::Stmt::AlterRoutine(_)
64 | ast::Stmt::AlterRule(_)
65 | ast::Stmt::AlterSchema(_)
66 | ast::Stmt::AlterSequence(_)
67 | ast::Stmt::AlterServer(_)
68 | ast::Stmt::AlterStatistics(_)
69 | ast::Stmt::AlterSubscription(_)
70 | ast::Stmt::AlterSystem(_)
71 | ast::Stmt::AlterTable(_)
72 | ast::Stmt::AlterTablespace(_)
73 | ast::Stmt::AlterTextSearchConfiguration(_)
74 | ast::Stmt::AlterTextSearchDictionary(_)
75 | ast::Stmt::AlterTextSearchParser(_)
76 | ast::Stmt::AlterTextSearchTemplate(_)
77 | ast::Stmt::AlterTrigger(_)
78 | ast::Stmt::AlterType(_)
79 | ast::Stmt::AlterUser(_)
80 | ast::Stmt::AlterUserMapping(_)
81 | ast::Stmt::AlterView(_)
82 | ast::Stmt::CreateAccessMethod(_)
83 | ast::Stmt::CreateAggregate(_)
84 | ast::Stmt::CreateCast(_)
85 | ast::Stmt::CreateCollation(_)
86 | ast::Stmt::CreateConversion(_)
87 | ast::Stmt::CreateDatabase(_)
88 | ast::Stmt::CreateDomain(_)
89 | ast::Stmt::CreateEventTrigger(_)
90 | ast::Stmt::CreateExtension(_)
91 | ast::Stmt::CreateForeignDataWrapper(_)
92 | ast::Stmt::CreateForeignTable(_)
93 | ast::Stmt::CreateFunction(_)
94 | ast::Stmt::CreateGroup(_)
95 | ast::Stmt::CreateIndex(_)
96 | ast::Stmt::CreateLanguage(_)
97 | ast::Stmt::CreateMaterializedView(_)
98 | ast::Stmt::CreateOperator(_)
99 | ast::Stmt::CreateOperatorClass(_)
100 | ast::Stmt::CreateOperatorFamily(_)
101 | ast::Stmt::CreatePolicy(_)
102 | ast::Stmt::CreatePropertyGraph(_)
103 | ast::Stmt::CreateProcedure(_)
104 | ast::Stmt::CreatePublication(_)
105 | ast::Stmt::CreateRole(_)
106 | ast::Stmt::CreateRule(_)
107 | ast::Stmt::CreateSchema(_)
108 | ast::Stmt::CreateSequence(_)
109 | ast::Stmt::CreateServer(_)
110 | ast::Stmt::CreateStatistics(_)
111 | ast::Stmt::CreateSubscription(_)
112 | ast::Stmt::CreateTableAs(_)
113 | ast::Stmt::SelectInto(_)
114 | ast::Stmt::CreateTablespace(_)
115 | ast::Stmt::CreateTextSearchConfiguration(_)
116 | ast::Stmt::CreateTextSearchDictionary(_)
117 | ast::Stmt::CreateTextSearchParser(_)
118 | ast::Stmt::CreateTextSearchTemplate(_)
119 | ast::Stmt::CreateTransform(_)
120 | ast::Stmt::CreateTrigger(_)
121 | ast::Stmt::CreateType(_)
122 | ast::Stmt::CreateUser(_)
123 | ast::Stmt::CreateUserMapping(_)
124 | ast::Stmt::CreateView(_)
125 | ast::Stmt::DropAccessMethod(_)
126 | ast::Stmt::DropAggregate(_)
127 | ast::Stmt::DropCast(_)
128 | ast::Stmt::DropCollation(_)
129 | ast::Stmt::DropConversion(_)
130 | ast::Stmt::DropDatabase(_)
131 | ast::Stmt::DropDomain(_)
132 | ast::Stmt::DropEventTrigger(_)
133 | ast::Stmt::DropExtension(_)
134 | ast::Stmt::DropForeignDataWrapper(_)
135 | ast::Stmt::DropForeignTable(_)
136 | ast::Stmt::DropFunction(_)
137 | ast::Stmt::DropGroup(_)
138 | ast::Stmt::DropIndex(_)
139 | ast::Stmt::DropLanguage(_)
140 | ast::Stmt::DropMaterializedView(_)
141 | ast::Stmt::DropOperator(_)
142 | ast::Stmt::DropOperatorClass(_)
143 | ast::Stmt::DropOperatorFamily(_)
144 | ast::Stmt::DropOwned(_)
145 | ast::Stmt::DropPolicy(_)
146 | ast::Stmt::DropPropertyGraph(_)
147 | ast::Stmt::DropProcedure(_)
148 | ast::Stmt::DropPublication(_)
149 | ast::Stmt::DropRole(_)
150 | ast::Stmt::DropRoutine(_)
151 | ast::Stmt::DropRule(_)
152 | ast::Stmt::DropSchema(_)
153 | ast::Stmt::DropSequence(_)
154 | ast::Stmt::DropServer(_)
155 | ast::Stmt::DropStatistics(_)
156 | ast::Stmt::DropSubscription(_)
157 | ast::Stmt::DropTable(_)
158 | ast::Stmt::DropTablespace(_)
159 | ast::Stmt::DropTextSearchConfig(_)
160 | ast::Stmt::DropTextSearchDict(_)
161 | ast::Stmt::DropTextSearchParser(_)
162 | ast::Stmt::DropTextSearchTemplate(_)
163 | ast::Stmt::DropTransform(_)
164 | ast::Stmt::DropTrigger(_)
165 | ast::Stmt::DropType(_)
166 | ast::Stmt::DropUser(_)
167 | ast::Stmt::DropUserMapping(_)
168 | ast::Stmt::DropView(_)
169 | ast::Stmt::Cluster(_)
171 | ast::Stmt::CommentOn(_)
172 | ast::Stmt::ImportForeignSchema(_)
173 | ast::Stmt::Load(_)
174 | ast::Stmt::Lock(_)
175 | ast::Stmt::Refresh(_)
176 | ast::Stmt::Reindex(_)
177 | ast::Stmt::Truncate(_)
178 | ast::Stmt::Vacuum(_)
179 => true,
180 ast::Stmt::Analyze(_)
181 | ast::Stmt::Begin(_)
182 | ast::Stmt::Call(_)
183 | ast::Stmt::Checkpoint(_)
184 | ast::Stmt::Close(_)
185 | ast::Stmt::Commit(_)
186 | ast::Stmt::CompoundSelect(_)
187 | ast::Stmt::Copy(_)
188 | ast::Stmt::Deallocate(_)
189 | ast::Stmt::Declare(_)
190 | ast::Stmt::Delete(_)
191 | ast::Stmt::Discard(_)
192 | ast::Stmt::Do(_)
193 | ast::Stmt::EmptyStmt(_)
194 | ast::Stmt::Execute(_)
195 | ast::Stmt::Explain(_)
196 | ast::Stmt::Fetch(_)
197 | ast::Stmt::Grant(_)
198 | ast::Stmt::Insert(_)
199 | ast::Stmt::Listen(_)
200 | ast::Stmt::Merge(_)
201 | ast::Stmt::Move(_)
202 | ast::Stmt::Notify(_)
203 | ast::Stmt::ParenSelect(_)
204 | ast::Stmt::Prepare(_)
205 | ast::Stmt::PrepareTransaction(_)
206 | ast::Stmt::Reassign(_)
207 | ast::Stmt::ReleaseSavepoint(_)
208 | ast::Stmt::Reset(_)
209 | ast::Stmt::Revoke(_)
210 | ast::Stmt::Repack(_)
211 | ast::Stmt::Rollback(_)
212 | ast::Stmt::SavepointCreate(_)
213 | ast::Stmt::SecurityLabel(_)
214 | ast::Stmt::Select(_)
215 | ast::Stmt::Set(_)
216 | ast::Stmt::SetConstraints(_)
217 | ast::Stmt::SetRole(_)
218 | ast::Stmt::SetSessionAuth(_)
219 | ast::Stmt::ResetRole(_)
220 | ast::Stmt::ResetSessionAuth(_)
221 | ast::Stmt::SetTransaction(_)
222 | ast::Stmt::Show(_)
223 | ast::Stmt::Table(_)
224 | ast::Stmt::Unlisten(_)
225 | ast::Stmt::Update(_)
226 | ast::Stmt::Values(_) => false,
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use squawk_syntax::SourceFile;
234
235 #[test]
236 fn alter_table() {
237 let sql = "ALTER TABLE users ADD COLUMN email TEXT;";
238 let file = SourceFile::parse(sql);
239 let stmts = file.tree().stmts().next().unwrap();
240 assert!(possibly_slow_stmt(&stmts));
241 }
242
243 #[test]
244 fn select_into() {
245 let sql = "select 1 a into t;";
246 let file = SourceFile::parse(sql);
247 let stmts = file.tree().stmts().next().unwrap();
248 assert!(possibly_slow_stmt(&stmts));
249 }
250
251 #[test]
252 fn select() {
253 let sql = "select 1;";
254 let file = SourceFile::parse(sql);
255 let stmts = file.tree().stmts().next().unwrap();
256 assert!(!possibly_slow_stmt(&stmts));
257 }
258
259 #[test]
260 fn create_table_without_foreign_key() {
261 let sql = "create table foo (id integer generated by default as identity primary key);";
262 let file = SourceFile::parse(sql);
263 let stmts = file.tree().stmts().next().unwrap();
264 assert!(!possibly_slow_stmt(&stmts));
265 }
266
267 #[test]
268 fn create_table_with_foreign_key() {
269 let sql = "create table foo (id integer, user_id integer references users(id));";
270 let file = SourceFile::parse(sql);
271 let stmts = file.tree().stmts().next().unwrap();
272 assert!(possibly_slow_stmt(&stmts));
273 }
274
275 #[test]
276 fn create_table_with_table_level_foreign_key() {
277 let sql = "create table foo (id integer, user_id integer, foreign key (user_id) references users(id));";
278 let file = SourceFile::parse(sql);
279 let stmts = file.tree().stmts().next().unwrap();
280 assert!(possibly_slow_stmt(&stmts));
281 }
282}