1use crate::analysis::expr_ir::ExprIr;
3use crate::analysis::facts::{
4 AlterIndexActionFact, AlterTableActionFact, AlterTypeActionFact, AlterTypeFact, ColumnFact,
5 CreateTypeFact, FkFact, PersistenceFact, SearchPathTarget, StatementFact, TableConstraintFact,
6 TypeCreationKind,
7};
8use crate::ast::identifiers::{Ident, QualifiedName};
9use squawk_syntax::ast::{
10 AlterColumnOption, AlterConstraint, AlterDomain, AlterIndex, AlterSequence, AlterTable,
11 AlterTableAction, AlterType, AstNode, AttachPartition, Column, ColumnConstraint, Constraint,
12 CreateDatabase, CreateDomain, CreateIndex, CreateMaterializedView, CreatePolicy,
13 CreateSequence, CreateTable, CreateTableAs, CreateTrigger, CreateType, CreateView,
14 DetachPartition, DropDomain, DropIndex, DropMaterializedView, DropPolicy, DropSequence,
15 DropTable, DropTrigger, DropType, DropView, FieldExpr, Grant, Name, NameRef, Path, PathSegment,
16 ReleaseSavepoint, RenameTo, Revoke, RevokeCommand, Rollback, Savepoint, Set, Stmt, TableArg,
17 TableConstraint,
18};
19use squawk_syntax::{SyntaxKind, ast};
20
21pub struct AstVisitor;
22
23impl AstVisitor {
24 fn resolve_name(n: Name) -> String {
25 Ident::new(n.text().to_string(), n.is_quoted()).resolve()
26 }
27
28 fn resolve_name_ref(nr: &NameRef) -> String {
29 Ident::new(nr.text().to_string(), nr.is_quoted()).resolve()
30 }
31
32 pub fn extract(stmt: &Stmt) -> Option<StatementFact> {
33 let syntax = stmt.syntax();
34 match stmt {
35 Stmt::CreateTable(node) => return Self::extract_create_table(node),
36 Stmt::CreateTableAs(node) => return Self::extract_create_table_as(node),
37 Stmt::CreateView(node) => return Self::extract_create_view(node),
38 Stmt::CreateMaterializedView(node) => {
39 return Self::extract_create_materialized_view(node);
40 }
41 Stmt::CreateIndex(node) => return Self::extract_create_index(node),
42 Stmt::AlterTable(node) => return Self::extract_alter_table(node),
43 Stmt::AlterIndex(node) => return Self::extract_alter_index(node),
44 Stmt::DropTable(node) => return Self::extract_drop_table(node),
45 Stmt::DropView(node) => return Self::extract_drop_view(node),
46 Stmt::DropMaterializedView(node) => return Self::extract_drop_materialized_view(node),
47 Stmt::DropIndex(node) => return Self::extract_drop_index(node),
48 Stmt::Set(node) => return Self::extract_set(node),
49 Stmt::Begin(_) => return Some(StatementFact::BeginTransaction),
50 Stmt::Commit(_) => return Some(StatementFact::CommitTransaction),
51 Stmt::Rollback(node) => return Self::extract_rollback(node),
52 Stmt::Savepoint(node) => return Some(Self::extract_savepoint(node)),
53 Stmt::ReleaseSavepoint(node) => return Some(Self::extract_release_savepoint(node)),
54 Stmt::Do(_) => return Some(StatementFact::OpaqueBlock),
55 Stmt::Execute(_) => return Some(StatementFact::Execute),
56 Stmt::Vacuum(node) => {
57 let relation = if let Some(list) = node.table_and_columns_list() {
58 list.table_and_columnss()
59 .next()
60 .and_then(|tc| tc.relation_name())
61 .and_then(|rn| rn.path())
62 .and_then(|path| Self::path_to_qualified_name(&path))
63 } else {
64 None
65 };
66 return Some(StatementFact::Vacuum {
67 relation,
68 is_full: node.is_full(),
69 });
70 }
71 _ => {}
72 }
73
74 if let Some(node) = ast::CreateSchema::cast(syntax.clone()) {
75 return Self::extract_create_schema(&node);
76 }
77 if let Some(node) = ast::AlterSchema::cast(syntax.clone()) {
78 return Self::extract_alter_schema(&node);
79 }
80 if let Some(node) = ast::DropSchema::cast(syntax.clone()) {
81 return Self::extract_drop_schema(&node);
82 }
83
84 if let Some(node) = ast::AlterView::cast(syntax.clone()) {
85 return Self::extract_alter_view(&node);
86 }
87 if let Some(node) = ast::AlterMaterializedView::cast(syntax.clone()) {
88 return Self::extract_alter_materialized_view(&node);
89 }
90 if let Some(node) = ast::Refresh::cast(syntax.clone()) {
91 return Self::extract_refresh(&node);
92 }
93
94 if let Some(node) = CreateSequence::cast(syntax.clone()) {
95 return Self::extract_create_sequence(&node);
96 }
97 if let Some(node) = AlterSequence::cast(syntax.clone()) {
98 return Self::extract_alter_sequence(&node);
99 }
100 if let Some(node) = DropSequence::cast(syntax.clone()) {
101 return Self::extract_drop_sequence(&node);
102 }
103 if let Some(node) = CreateType::cast(syntax.clone()) {
104 return Self::extract_create_type(&node);
105 }
106 if let Some(node) = AlterType::cast(syntax.clone()) {
107 return Self::extract_alter_type(&node);
108 }
109 if let Some(node) = CreateDomain::cast(syntax.clone()) {
110 return Self::extract_create_domain(&node);
111 }
112 if let Some(node) = AlterDomain::cast(syntax.clone()) {
113 return Self::extract_alter_domain(&node);
114 }
115 if let Some(node) = DropType::cast(syntax.clone()) {
116 return Self::extract_drop_type(&node);
117 }
118 if let Some(node) = DropDomain::cast(syntax.clone()) {
119 return Self::extract_drop_domain(&node);
120 }
121 if let Some(node) = CreatePolicy::cast(syntax.clone()) {
122 return Self::extract_create_policy(&node);
123 }
124 if let Some(node) = DropPolicy::cast(syntax.clone()) {
125 return Self::extract_drop_policy(&node);
126 }
127 if let Some(node) = CreateTrigger::cast(syntax.clone()) {
128 return Self::extract_create_trigger(&node);
129 }
130 if let Some(node) = DropTrigger::cast(syntax.clone()) {
131 return Self::extract_drop_trigger(&node);
132 }
133
134 if ast::PrepareTransaction::cast(syntax.clone()).is_some() {
135 let name = syntax
136 .descendants()
137 .find_map(ast::Literal::cast)
138 .map(|l| l.syntax().text().to_string().trim_matches('\'').to_string())
139 .or_else(|| {
140 syntax
141 .descendants()
142 .find_map(Name::cast)
143 .map(Self::resolve_name)
144 })
145 .unwrap_or_default();
146 return Some(StatementFact::PrepareTransaction { name });
147 }
148 if ast::SetTransaction::cast(syntax.clone()).is_some() {
149 return Some(StatementFact::SetTransaction);
150 }
151 if ast::SetConstraints::cast(syntax.clone()).is_some() {
152 return Some(StatementFact::SetConstraints);
153 }
154
155 if let Some(node) = ast::CreateFunction::cast(syntax.clone()) {
156 return Self::extract_create_function(&node);
157 }
158 if let Some(node) = ast::AlterFunction::cast(syntax.clone()) {
159 return Self::extract_alter_function(&node);
160 }
161 if let Some(node) = ast::DropFunction::cast(syntax.clone()) {
162 return Self::extract_drop_function(&node);
163 }
164 if let Some(node) = ast::CreateProcedure::cast(syntax.clone()) {
165 return Self::extract_create_procedure(&node);
166 }
167 if let Some(node) = ast::AlterProcedure::cast(syntax.clone()) {
168 return Self::extract_alter_procedure(&node);
169 }
170 if let Some(node) = ast::DropProcedure::cast(syntax.clone()) {
171 return Self::extract_drop_procedure(&node);
172 }
173 if let Some(node) = ast::CreatePublication::cast(syntax.clone()) {
174 return Self::extract_create_publication(&node);
175 }
176 if let Some(node) = ast::AlterPublication::cast(syntax.clone()) {
177 return Self::extract_alter_publication(&node);
178 }
179 if let Some(node) = ast::DropPublication::cast(syntax.clone()) {
180 return Self::extract_drop_publication(&node);
181 }
182 if let Some(node) = ast::CreateSubscription::cast(syntax.clone()) {
183 return Self::extract_create_subscription(&node);
184 }
185 if let Some(node) = ast::AlterSubscription::cast(syntax.clone()) {
186 return Self::extract_alter_subscription(&node);
187 }
188 if let Some(node) = ast::DropSubscription::cast(syntax.clone()) {
189 return Self::extract_drop_subscription(&node);
190 }
191 if let Some(node) = ast::CreateRole::cast(syntax.clone()) {
192 return Self::extract_create_role(&node);
193 }
194 if let Some(node) = ast::AlterRole::cast(syntax.clone()) {
195 return Self::extract_alter_role(&node);
196 }
197 if let Some(node) = ast::DropRole::cast(syntax.clone()) {
198 return Self::extract_drop_role(&node);
199 }
200 if let Some(node) = Grant::cast(syntax.clone()) {
201 return Self::extract_grant(&node);
202 }
203 if let Some(node) = Revoke::cast(syntax.clone()) {
204 return Self::extract_revoke(&node);
205 }
206 if let Some(node) = CreateDatabase::cast(syntax.clone()) {
207 return Self::extract_create_database(&node);
208 }
209 if let Some(node) = ast::AlterDatabase::cast(syntax.clone()) {
210 return Self::extract_alter_database(&node);
211 }
212 if let Some(node) = ast::DropDatabase::cast(syntax.clone()) {
213 return Self::extract_drop_database(&node);
214 }
215
216 None
217 }
218
219 fn extract_create_schema(node: &ast::CreateSchema) -> Option<StatementFact> {
220 let name = node.name().map(|n| {
221 Ident::new(
222 n.text().to_string().trim_matches('"').to_string(),
223 n.is_quoted(),
224 )
225 })?;
226
227 Some(StatementFact::CreateSchema {
228 name: QualifiedName::new(None, name),
229 if_not_exists: node.if_not_exists().is_some(),
230 })
231 }
232
233 fn extract_alter_schema(node: &ast::AlterSchema) -> Option<StatementFact> {
234 let nr = node.name_ref()?;
235 let name = QualifiedName::new(
236 None,
237 Ident::new(
238 nr.text().to_string().trim_matches('"').to_string(),
239 nr.is_quoted(),
240 ),
241 );
242 let new_name = node.rename_to().and_then(|rt| {
243 rt.name().map(|n| {
244 Ident::new(
245 n.text().to_string().trim_matches('"').to_string(),
246 n.is_quoted(),
247 )
248 })
249 });
250 Some(StatementFact::AlterSchema { name, new_name })
251 }
252
253 fn extract_drop_schema(node: &ast::DropSchema) -> Option<StatementFact> {
254 let names: Vec<QualifiedName> = node
257 .name_refs()
258 .map(|nr| {
259 let ident = Ident::new(
260 nr.text().to_string().trim_matches('"').to_string(),
261 nr.is_quoted(),
262 );
263 QualifiedName::new(None, ident)
264 })
265 .collect();
266
267 if names.is_empty() {
268 return None;
269 }
270
271 Some(StatementFact::DropSchema {
272 names,
273 if_exists: node.if_exists().is_some(),
274 cascade: node.cascade_token().is_some(),
275 })
276 }
277
278 fn extract_create_table(node: &CreateTable) -> Option<StatementFact> {
279 let path = node.syntax().descendants().find_map(Path::cast)?;
280 let name = Self::path_to_qualified_name(&path)?;
281
282 let persistence = match node
283 .persistence()
284 .map(|p| p.syntax().text().to_string().to_lowercase())
285 .as_deref()
286 {
287 Some("temporary") | Some("temp") => PersistenceFact::Temporary,
288 Some("unlogged") => PersistenceFact::Unlogged,
289 _ => PersistenceFact::Permanent,
290 };
291
292 let partition_by = node.partition_by().map(|p| p.syntax().text().to_string());
293 let partition_of = node
294 .partition_of()
295 .and_then(|p| p.path())
296 .and_then(|p| Self::path_to_qualified_name(&p));
297 let partition_type = node
298 .partition_type()
299 .map(|pt| pt.syntax().text().to_string());
300
301 let (columns, foreign_keys, table_constraints) = node
302 .table_arg_list()
303 .map(|tal| Self::extract_table_body(tal.args()))
304 .unwrap_or_else(|| (Vec::new(), Vec::new(), Vec::new()));
305
306 Some(StatementFact::CreateTable {
307 name,
308 if_not_exists: node.if_not_exists().is_some(),
309 as_select: false,
310 persistence,
311 columns,
312 foreign_keys,
313 table_constraints,
314 partition_by,
315 partition_of,
316 partition_type,
317 })
318 }
319
320 fn extract_create_table_as(node: &CreateTableAs) -> Option<StatementFact> {
321 let path = node.path()?;
322 let persistence = match node
323 .persistence()
324 .map(|p| p.syntax().text().to_string().to_lowercase())
325 .as_deref()
326 {
327 Some("temporary") | Some("temp") => PersistenceFact::Temporary,
328 Some("unlogged") => PersistenceFact::Unlogged,
329 _ => PersistenceFact::Permanent,
330 };
331 Some(StatementFact::CreateTable {
332 name: Self::path_to_qualified_name(&path)?,
333 if_not_exists: node.if_not_exists().is_some(),
334 as_select: true,
335 persistence,
336 columns: Vec::new(),
337 foreign_keys: Vec::new(),
338 table_constraints: Vec::new(),
339 partition_by: None,
340 partition_of: None,
341 partition_type: None,
342 })
343 }
344
345 fn extract_drop_table(node: &DropTable) -> Option<StatementFact> {
346 let path = node.paths().next()?;
347 Some(StatementFact::DropTable {
348 name: Self::path_to_qualified_name(&path)?,
349 if_exists: node.if_exists().is_some(),
350 cascade: node.cascade_token().is_some(),
351 })
352 }
353
354 fn extract_alter_table(node: &AlterTable) -> Option<StatementFact> {
355 let path = node.syntax().descendants().find_map(Path::cast)?;
356 let table_name = Self::path_to_qualified_name(&path)?;
357 let mut actions = Vec::new();
358
359 for action in node.actions() {
360 if let Some(ap) = AttachPartition::cast(action.syntax().clone()) {
361 if let Some(child_path) = ap.syntax().descendants().find_map(Path::cast)
362 && let Some(child) = Self::path_to_qualified_name(&child_path)
363 {
364 actions.push(AlterTableActionFact::AttachPartition { child });
365 }
366 continue;
367 }
368 if let Some(dp) = DetachPartition::cast(action.syntax().clone()) {
369 if let Some(child_path) = dp.syntax().descendants().find_map(Path::cast)
370 && let Some(child) = Self::path_to_qualified_name(&child_path)
371 {
372 actions.push(AlterTableActionFact::DetachPartition { child });
373 }
374 continue;
375 }
376 if let Some(ac) = AlterConstraint::cast(action.syntax().clone()) {
377 let deferrable = ac.deferrable_constraint_option().is_some();
378 actions.push(AlterTableActionFact::AlterConstraint {
379 name: None,
380 deferrable,
381 });
382 continue;
383 }
384 if let Some(rc) = ast::RenameConstraint::cast(action.syntax().clone()) {
385 let old_name = rc
386 .syntax()
387 .descendants()
388 .find_map(NameRef::cast)
389 .map(|nr| Self::resolve_name_ref(&nr));
390 let new_name = rc
391 .syntax()
392 .descendants()
393 .find_map(Name::cast)
394 .map(Self::resolve_name);
395 if let (Some(old_name), Some(new_name)) = (old_name, new_name) {
396 actions.push(AlterTableActionFact::RenameConstraint { old_name, new_name });
397 }
398 continue;
399 }
400
401 match action {
402 AlterTableAction::AddColumn(add) => {
403 if let Some(name) = add.name().map(Self::resolve_name) {
404 let mut not_null = false;
405 let mut default = None;
406 for c in add.constraints() {
407 match c {
408 Constraint::NotNullConstraint(_) => not_null = true,
409 Constraint::PrimaryKeyConstraint(_) => not_null = true,
410 Constraint::DefaultConstraint(dc) => {
411 default = dc
412 .expr()
413 .map(crate::analysis::expr_visitor::ExprVisitor::convert)
414 }
415 _ => {}
416 }
417 }
418 actions.push(AlterTableActionFact::AddColumn {
419 name,
420 ty: add.ty().map(|t| t.syntax().text().to_string()),
421 if_not_exists: add.if_not_exists().is_some(),
422 not_null,
423 default,
424 });
425 }
426 }
427 AlterTableAction::DropColumn(drop) => {
428 if let Some(name) = drop.name_ref().map(|nr| Self::resolve_name_ref(&nr)) {
429 actions.push(AlterTableActionFact::DropColumn {
430 name,
431 if_exists: drop.if_exists().is_some(),
432 });
433 }
434 }
435 AlterTableAction::RenameColumn(rc) => {
436 let from_ident = rc
437 .from()
438 .map(|nr| {
439 Ident::new(
440 nr.text().to_string().trim_matches('"').to_string(),
441 nr.is_quoted(),
442 )
443 })
444 .or_else(|| {
445 rc.syntax().descendants().find_map(NameRef::cast).map(|nr| {
446 Ident::new(
447 nr.text().to_string().trim_matches('"').to_string(),
448 nr.is_quoted(),
449 )
450 })
451 });
452
453 let to_ident = rc
454 .to()
455 .map(|nr| {
456 Ident::new(
457 nr.text().to_string().trim_matches('"').to_string(),
458 nr.is_quoted(),
459 )
460 })
461 .or_else(|| {
462 rc.syntax().descendants().find_map(Name::cast).map(|n| {
463 Ident::new(
464 n.text().to_string().trim_matches('"').to_string(),
465 n.is_quoted(),
466 )
467 })
468 });
469
470 if let (Some(from), Some(to)) = (from_ident, to_ident) {
471 actions.push(AlterTableActionFact::RenameColumn { from, to });
472 }
473 }
474 AlterTableAction::RenameTo(rt) => {
475 if let Some(new_name) = rt.name() {
476 actions.push(AlterTableActionFact::RenameTo {
477 new_name: Ident::new(
478 new_name.text().to_string().trim_matches('"').to_string(),
479 new_name.is_quoted(),
480 ),
481 });
482 }
483 }
484 AlterTableAction::AddConstraint(ac) => {
485 if let Some(fact) = Self::extract_add_constraint_fact(&ac) {
486 actions.push(fact);
487 }
488 }
489 AlterTableAction::DropConstraint(dc) => {
490 if let Some(name) = dc.name_ref().map(|nr| Self::resolve_name_ref(&nr)) {
491 actions.push(AlterTableActionFact::DropConstraint { name });
492 }
493 }
494 AlterTableAction::AlterColumn(alter_col) => {
495 let col_ident = alter_col
496 .syntax()
497 .descendants()
498 .find_map(NameRef::cast)
499 .map(|nr| Self::resolve_name_ref(&nr))
500 .or_else(|| {
501 alter_col
502 .syntax()
503 .descendants()
504 .find_map(Name::cast)
505 .map(Self::resolve_name)
506 });
507
508 if let Some(col_name) = col_ident
509 && let Some(opt) = alter_col.option()
510 && let Some(fact) = Self::extract_alter_column_option(col_name, opt)
511 {
512 actions.push(fact);
513 }
514 }
515 AlterTableAction::ValidateConstraint(vc) => {
516 if let Some(constraint_name) = vc
517 .syntax()
518 .descendants()
519 .find_map(NameRef::cast)
520 .map(|nr| Self::resolve_name_ref(&nr))
521 {
522 actions.push(AlterTableActionFact::ValidateConstraint { constraint_name });
523 }
524 }
525 AlterTableAction::SetAccessMethod(sam) => {
526 if sam.name_ref().is_some() {
527 actions.push(AlterTableActionFact::SetAccessMethod);
528 }
529 }
530 AlterTableAction::DisableTrigger(dt) => {
531 let trigger_name = dt
532 .name_ref()
533 .map(|nr| Self::resolve_name_ref(&nr))
534 .or_else(|| {
535 if dt.all_token().is_some() {
536 Some("ALL".to_string())
537 } else {
538 None
539 }
540 })
541 .or_else(|| {
542 dt.syntax()
543 .descendants()
544 .find_map(NameRef::cast)
545 .map(|nr| Self::resolve_name_ref(&nr))
546 });
547 actions.push(AlterTableActionFact::DisableTrigger { trigger_name });
548 }
549 AlterTableAction::EnableTrigger(et) => {
550 let trigger_name = et
551 .name_ref()
552 .map(|nr| Self::resolve_name_ref(&nr))
553 .or_else(|| {
554 if et.all_token().is_some() {
555 Some("ALL".to_string())
556 } else {
557 None
558 }
559 })
560 .or_else(|| {
561 et.syntax()
562 .descendants()
563 .find_map(NameRef::cast)
564 .map(|nr| Self::resolve_name_ref(&nr))
565 });
566 actions.push(AlterTableActionFact::EnableTrigger { trigger_name });
567 }
568 AlterTableAction::SetSchema(ss) => {
569 if let Some(nr) = ss.name_ref() {
570 actions.push(AlterTableActionFact::SetSchema {
571 new_schema: Self::resolve_name_ref(&nr),
572 });
573 }
574 }
575 AlterTableAction::SetTablespace(st) => {
576 if let Some(path) = st.path()
577 && let Some(qname) = Self::path_to_qualified_name(&path)
578 {
579 actions.push(AlterTableActionFact::SetTablespace {
580 tablespace: qname.name.resolve(),
581 });
582 }
583 }
584 AlterTableAction::OwnerTo(ot) => {
585 if let Some(role_ref) = ot.role_ref()
586 && let Some(nr) = role_ref.name_ref()
587 {
588 actions.push(AlterTableActionFact::OwnerTo {
589 new_owner: Self::resolve_name_ref(&nr),
590 });
591 } else {
592 let nr = ot.syntax().descendants().find_map(NameRef::cast);
593 if let Some(nr) = nr {
594 actions.push(AlterTableActionFact::OwnerTo {
595 new_owner: Self::resolve_name_ref(&nr),
596 });
597 }
598 }
599 }
600 AlterTableAction::SetLogged(_) => {
601 actions.push(AlterTableActionFact::SetLogged);
602 }
603 AlterTableAction::SetUnlogged(_) => {
604 actions.push(AlterTableActionFact::SetUnlogged);
605 }
606 AlterTableAction::ReplicaIdentity(ri) => {
607 let option = ri
608 .name_ref()
609 .map(|nr| Self::resolve_name_ref(&nr))
610 .or_else(|| {
611 if ri.default_token().is_some() {
612 Some("DEFAULT".to_string())
613 } else if ri.full_token().is_some() {
614 Some("FULL".to_string())
615 } else if ri.syntax().descendants().find_map(NameRef::cast).is_some() {
616 ri.syntax()
617 .descendants()
618 .find_map(NameRef::cast)
619 .map(|nr| Self::resolve_name_ref(&nr))
620 } else {
621 Some("NOTHING".to_string())
622 }
623 })
624 .unwrap_or_default();
625 actions.push(AlterTableActionFact::ReplicaIdentity { option });
626 }
627 AlterTableAction::ClusterOn(co) => {
628 let index = co
629 .name_ref()
630 .map(|nr| Self::resolve_name_ref(&nr))
631 .or_else(|| {
632 co.syntax()
633 .descendants()
634 .find_map(NameRef::cast)
635 .map(|nr| Self::resolve_name_ref(&nr))
636 })
637 .unwrap_or_default();
638 actions.push(AlterTableActionFact::ClusterOn { index });
639 }
640 AlterTableAction::InheritTable(it) => {
641 if let Some(path) = it.path()
642 && let Some(parent) = Self::path_to_qualified_name(&path)
643 {
644 actions.push(AlterTableActionFact::InheritTable { parent });
645 }
646 }
647 AlterTableAction::NoInheritTable(nit) => {
648 if let Some(path) = nit.path()
649 && let Some(parent) = Self::path_to_qualified_name(&path)
650 {
651 actions.push(AlterTableActionFact::NoInheritTable { parent });
652 }
653 }
654 AlterTableAction::MergePartitions(mp) => {
655 if let Some(path) = mp.path()
656 && let Some(parent) = Self::path_to_qualified_name(&path)
657 {
658 actions.push(AlterTableActionFact::MergePartitions { parent });
659 }
660 }
661 AlterTableAction::SplitPartition(_sp) => {
662 actions.push(AlterTableActionFact::SplitPartition);
663 }
664 AlterTableAction::ForceRls(_) => {
665 actions.push(AlterTableActionFact::ForceRls);
666 }
667 AlterTableAction::EnableRls(_) => {
668 actions.push(AlterTableActionFact::EnableRls);
669 }
670 AlterTableAction::DisableRls(_) => {
671 actions.push(AlterTableActionFact::DisableRls);
672 }
673 AlterTableAction::EnableAlwaysTrigger(eat) => {
674 let trigger_name = eat
675 .name_ref()
676 .map(|nr| Self::resolve_name_ref(&nr))
677 .or_else(|| {
678 eat.syntax()
679 .descendants()
680 .find_map(NameRef::cast)
681 .map(|nr| Self::resolve_name_ref(&nr))
682 });
683 actions.push(AlterTableActionFact::EnableAlwaysTrigger { trigger_name });
684 }
685 AlterTableAction::EnableReplicaTrigger(ert) => {
686 let trigger_name = ert
687 .name_ref()
688 .map(|nr| Self::resolve_name_ref(&nr))
689 .or_else(|| {
690 ert.syntax()
691 .descendants()
692 .find_map(NameRef::cast)
693 .map(|nr| Self::resolve_name_ref(&nr))
694 });
695 actions.push(AlterTableActionFact::EnableReplicaTrigger { trigger_name });
696 }
697 _ => {
698 let txt = action.syntax().text().to_string().to_lowercase();
699
700 if txt.contains("set storage") {
701 let parts: Vec<&str> = txt.split_whitespace().collect();
702 if let Some(idx) = parts.iter().position(|&p| p == "column")
703 && idx + 1 < parts.len()
704 {
705 let c_name = parts[idx + 1].trim_matches('"').to_string();
706 actions.push(AlterTableActionFact::SetStorage { column: c_name });
707 }
708 }
709 }
710 }
711 }
712
713 Some(StatementFact::AlterTable {
714 name: table_name,
715 actions,
716 })
717 }
718
719 fn extract_table_body(
720 args: impl Iterator<Item = TableArg>,
721 ) -> (Vec<ColumnFact>, Vec<FkFact>, Vec<TableConstraintFact>) {
722 let mut columns = Vec::new();
723 let mut foreign_keys = Vec::new();
724 let mut table_constraints = Vec::new();
725
726 for arg in args {
727 match arg {
728 TableArg::Column(col) => {
729 for fk in Self::extract_column_fk_facts(&col) {
730 foreign_keys.push(fk);
731 }
732 if let Some(fact) = Self::extract_column_fact(&col) {
733 columns.push(fact);
734 }
735 }
736 TableArg::LikeClause(like) => {
737 if let Some(path) = like.syntax().descendants().find_map(Path::cast)
738 && let Some(_parent) = Self::path_to_qualified_name(&path)
739 {
740 }
745 }
746 TableArg::TableConstraint(tc) => {
747 if let Some(fk) = Self::extract_table_fk_fact(&tc) {
748 foreign_keys.push(fk);
749 }
750 if let Some(tc_fact) = Self::extract_table_constraint_fact(&tc) {
751 table_constraints.push(tc_fact);
752 }
753 }
754 }
755 }
756 (columns, foreign_keys, table_constraints)
757 }
758
759 fn extract_column_fact(col: &Column) -> Option<ColumnFact> {
760 let name = Self::resolve_name(col.name()?);
761 let ty = col.ty().map(|t| t.syntax().text().to_string());
762 let not_null = col
763 .constraints()
764 .any(|c| matches!(c, ColumnConstraint::NotNullConstraint(_)));
765 let is_primary_key = col
766 .constraints()
767 .any(|c| matches!(c, ColumnConstraint::PrimaryKeyConstraint(_)));
768 let default = col.constraints().find_map(|c| {
769 if let ColumnConstraint::DefaultConstraint(dc) = c {
770 Some(crate::analysis::expr_visitor::ExprVisitor::convert(
771 dc.expr()?,
772 ))
773 } else {
774 None
775 }
776 });
777 Some(ColumnFact {
778 name,
779 ty,
780 not_null,
781 is_primary_key,
782 default,
783 })
784 }
785
786 fn extract_alter_column_option(
787 col_name: String,
788 opt: AlterColumnOption,
789 ) -> Option<AlterTableActionFact> {
790 match opt {
791 AlterColumnOption::SetStorage(_) => {
792 Some(AlterTableActionFact::SetStorage { column: col_name })
793 }
794 AlterColumnOption::SetNotNull(_) => {
795 Some(AlterTableActionFact::SetNotNull { column: col_name })
796 }
797 AlterColumnOption::DropNotNull(_) => {
798 Some(AlterTableActionFact::DropNotNull { column: col_name })
799 }
800 AlterColumnOption::SetType(st) => {
801 let has_using = st
802 .syntax()
803 .descendants()
804 .any(|t| t.kind() == SyntaxKind::USING_KW);
805 Some(AlterTableActionFact::SetType {
806 column: col_name,
807 ty: st.ty()?.syntax().text().to_string(),
808 has_using,
809 })
810 }
811 AlterColumnOption::SetDefault(sd) => Some(AlterTableActionFact::SetDefault {
812 column: col_name,
813 default: sd
814 .expr()
815 .map(crate::analysis::expr_visitor::ExprVisitor::convert),
816 }),
817 AlterColumnOption::SetExpression(se) => Some(AlterTableActionFact::SetExpression {
818 column: col_name,
819 expr: se
820 .expr()
821 .map(crate::analysis::expr_visitor::ExprVisitor::convert)
822 .unwrap_or(ExprIr::Omitted),
823 }),
824 AlterColumnOption::SetOptions(so) => Some(AlterTableActionFact::SetOptions {
825 column: col_name,
826 attributes: so
827 .attribute_list()
828 .map(|al| {
829 al.attribute_options()
830 .map(|ao| crate::analysis::facts::AttributeFact {
831 name: ao.name().map(|n| n.text().to_string()).unwrap_or_default(),
832 value: ao
833 .syntax()
834 .descendants()
835 .find_map(ast::Literal::cast)
836 .map(|l| l.syntax().text().to_string())
837 .unwrap_or_default(),
838 })
839 .collect()
840 })
841 .unwrap_or_default(),
842 }),
843 AlterColumnOption::Inherit(i) => Some(AlterTableActionFact::Inherit {
844 column: col_name,
845 parent: i
846 .syntax()
847 .descendants()
848 .find_map(Path::cast)
849 .and_then(|p| Self::path_to_qualified_name(&p))
850 .unwrap_or_else(|| {
851 QualifiedName::new(None, Ident::new("unknown".to_string(), false))
852 }),
853 }),
854 AlterColumnOption::NoInherit(ni) => Some(AlterTableActionFact::NoInherit {
855 column: col_name,
856 parent: ni
857 .syntax()
858 .descendants()
859 .find_map(Path::cast)
860 .and_then(|p| Self::path_to_qualified_name(&p))
861 .unwrap_or_else(|| {
862 QualifiedName::new(None, Ident::new("unknown".to_string(), false))
863 }),
864 }),
865 _ => None,
866 }
867 }
868
869 fn extract_add_constraint_fact(
870 ac: &squawk_syntax::ast::AddConstraint,
871 ) -> Option<AlterTableActionFact> {
872 let not_valid = ac.not_valid().is_some();
873
874 if let Some(fkc) = ac
875 .syntax()
876 .descendants()
877 .find_map(ast::ForeignKeyConstraint::cast)
878 {
879 let constraint_name = fkc
880 .constraint_name()
881 .and_then(|cn| cn.name())
882 .map(Self::resolve_name)
883 .or_else(|| {
884 ac.syntax()
885 .descendants()
886 .find_map(ast::ConstraintName::cast)
887 .and_then(|cn| cn.name())
888 .map(Self::resolve_name)
889 });
890 let path = fkc.syntax().descendants().find_map(Path::cast)?;
891 let references = Self::path_to_qualified_name(&path)?;
892 return Some(AlterTableActionFact::AddForeignKey {
893 constraint_name,
894 references,
895 from_columns: fkc
896 .from_columns()
897 .map(Self::extract_column_list_names)
898 .unwrap_or_default(),
899 to_columns: fkc
900 .to_columns()
901 .map(Self::extract_column_list_names)
902 .unwrap_or_default(),
903 not_valid,
904 });
905 }
906
907 if let Some(cc) = ac
908 .syntax()
909 .descendants()
910 .find_map(ast::CheckConstraint::cast)
911 {
912 let constraint_name = cc
913 .constraint_name()
914 .and_then(|cn| cn.name())
915 .map(Self::resolve_name)
916 .or_else(|| {
917 ac.syntax()
918 .descendants()
919 .find_map(ast::ConstraintName::cast)
920 .and_then(|cn| cn.name())
921 .map(Self::resolve_name)
922 });
923 return Some(AlterTableActionFact::AddCheckConstraint {
924 constraint_name,
925 not_valid,
926 });
927 }
928
929 if ac
930 .syntax()
931 .descendants()
932 .any(|n| ast::UniqueConstraint::can_cast(n.kind()))
933 {
934 return Some(AlterTableActionFact::AddUniqueConstraint);
935 }
936
937 if ac
938 .syntax()
939 .descendants()
940 .any(|n| ast::PrimaryKeyConstraint::can_cast(n.kind()))
941 {
942 return Some(AlterTableActionFact::AddPrimaryKeyConstraint);
943 }
944
945 None
946 }
947
948 fn extract_table_constraint_fact(tc: &TableConstraint) -> Option<TableConstraintFact> {
949 match tc {
950 TableConstraint::PrimaryKeyConstraint(pkc) => Some(TableConstraintFact::PrimaryKey {
951 columns: Self::extract_column_list_names(pkc.column_list()?),
952 }),
953 TableConstraint::UniqueConstraint(uc) => Some(TableConstraintFact::Unique {
954 columns: Self::extract_column_list_names(uc.column_list()?),
955 }),
956 TableConstraint::CheckConstraint(_) => Some(TableConstraintFact::Check),
957 _ => None,
958 }
959 }
960
961 fn extract_column_fk_facts(col: &Column) -> Vec<FkFact> {
962 let col_name = col.name().map(Self::resolve_name);
963 col.constraints()
964 .filter_map(|c| {
965 if let ColumnConstraint::ReferencesConstraint(rc) = c {
966 let ref_path = rc.syntax().descendants().find_map(Path::cast)?;
967 Some(FkFact {
968 constraint_name: None,
969 references: Self::path_to_qualified_name(&ref_path)?,
970 from_columns: col_name.iter().cloned().collect(),
971 to_columns: Vec::new(),
972 })
973 } else {
974 None
975 }
976 })
977 .collect()
978 }
979
980 fn extract_table_fk_fact(tc: &TableConstraint) -> Option<FkFact> {
981 if let TableConstraint::ForeignKeyConstraint(fkc) = tc {
982 let constraint_name = fkc
983 .constraint_name()
984 .and_then(|cn| cn.name())
985 .map(Self::resolve_name);
986 let path = fkc.path()?;
987 let references = Self::path_to_qualified_name(&path)?;
988 let from_columns = fkc
989 .from_columns()
990 .map(Self::extract_column_list_names)
991 .unwrap_or_default();
992 let to_columns = fkc
993 .to_columns()
994 .map(Self::extract_column_list_names)
995 .unwrap_or_default();
996 Some(FkFact {
997 constraint_name,
998 references,
999 from_columns,
1000 to_columns,
1001 })
1002 } else {
1003 None
1004 }
1005 }
1006
1007 fn extract_column_list_names(cl: ast::ColumnList) -> Vec<String> {
1008 cl.columns()
1009 .filter_map(|col| col.name_ref().map(|nr| Self::resolve_name_ref(&nr)))
1010 .collect()
1011 }
1012
1013 fn extract_create_index(node: &CreateIndex) -> Option<StatementFact> {
1014 let relation_path = node.syntax().descendants().find_map(Path::cast)?;
1015 let relation = Self::path_to_qualified_name(&relation_path)?;
1016
1017 let index_ident = if let Some(name) = node.name() {
1018 Ident::new(
1019 name.text().to_string().trim_matches('"').to_string(),
1020 name.is_quoted(),
1021 )
1022 } else {
1023 Ident::new(
1024 format!("<unnamed_idx_on_{}>", relation.name.resolve()),
1025 false,
1026 )
1027 };
1028
1029 let using_method = node.using_method().map(|um| {
1030 um.name_ref()
1031 .map(|nr| nr.text().to_string().to_uppercase())
1032 .unwrap_or_default()
1033 });
1034
1035 let has_predicate = node.where_clause().is_some();
1036 let unique = node.unique_token().is_some();
1037
1038 Some(StatementFact::CreateIndex {
1039 name: QualifiedName::new(None, index_ident),
1040 relation,
1041 if_not_exists: node.if_not_exists().is_some(),
1042 concurrently: node.concurrently_token().is_some(),
1043 using_method,
1044 has_predicate,
1045 unique,
1046 })
1047 }
1048
1049 fn extract_alter_index(node: &AlterIndex) -> Option<StatementFact> {
1050 let path = node.path()?;
1051 let name = Self::path_to_qualified_name(&path)?;
1052 let mut actions = Vec::new();
1053
1054 if let Some(rt) = node.syntax().descendants().find_map(RenameTo::cast)
1055 && let Some(new_name) = rt.name()
1056 {
1057 actions.push(AlterIndexActionFact::RenameTo {
1058 new_name: Ident::new(
1059 new_name.text().to_string().trim_matches('"').to_string(),
1060 new_name.is_quoted(),
1061 ),
1062 });
1063 }
1064
1065 if actions.is_empty() {
1066 return None;
1067 }
1068 Some(StatementFact::AlterIndex { name, actions })
1069 }
1070
1071 fn extract_drop_index(node: &DropIndex) -> Option<StatementFact> {
1072 let names: Vec<QualifiedName> = node
1073 .paths()
1074 .filter_map(|p| Self::path_to_qualified_name(&p))
1075 .collect();
1076 if names.is_empty() {
1077 return None;
1078 }
1079 Some(StatementFact::DropIndex {
1080 names,
1081 if_exists: node.if_exists().is_some(),
1082 concurrently: node.concurrently_token().is_some(),
1083 })
1084 }
1085
1086 fn extract_create_view(node: &CreateView) -> Option<StatementFact> {
1087 let path = node.path()?;
1088 Some(StatementFact::CreateView {
1089 name: Self::path_to_qualified_name(&path)?,
1090 or_replace: node.or_replace().is_some(),
1091 depends_on: Self::extract_view_dependencies(node.syntax()),
1092 })
1093 }
1094
1095 fn extract_alter_view(node: &ast::AlterView) -> Option<StatementFact> {
1096 let path = node.path()?;
1097 let name = Self::path_to_qualified_name(&path)?;
1098
1099 if let Some(rt) = node.syntax().descendants().find_map(RenameTo::cast)
1101 && let Some(new_name_node) = rt.name()
1102 {
1103 return Some(StatementFact::AlterView {
1104 name,
1105 action: crate::analysis::facts::AlterViewAction::RenameTo {
1106 new_name: Ident::new(
1107 new_name_node
1108 .text()
1109 .to_string()
1110 .trim_matches('"')
1111 .to_string(),
1112 new_name_node.is_quoted(),
1113 ),
1114 },
1115 });
1116 }
1117
1118 if let Some(ot) = node.owner_to()
1120 && let Some(role_ref) = ot.syntax().descendants().find_map(ast::RoleRef::cast)
1121 && let Some(nr) = role_ref.name_ref()
1122 {
1123 return Some(StatementFact::AlterView {
1124 name,
1125 action: crate::analysis::facts::AlterViewAction::OwnerTo {
1126 new_owner: Self::resolve_name_ref(&nr),
1127 },
1128 });
1129 }
1130
1131 if let Some(ss) = node.set_schema()
1133 && let Some(nr) = ss.name_ref()
1134 {
1135 return Some(StatementFact::AlterView {
1136 name,
1137 action: crate::analysis::facts::AlterViewAction::SetSchema {
1138 new_schema: Self::resolve_name_ref(&nr),
1139 },
1140 });
1141 }
1142
1143 if node.column_token().is_some()
1146 && node.default_token().is_some()
1147 && (node.set_token().is_some() || node.drop_token().is_some())
1148 {
1149 let col_name = node
1150 .name_ref()
1151 .map(|nr| Self::resolve_name_ref(&nr))
1152 .unwrap_or_default();
1153
1154 if node.drop_token().is_some() {
1155 return Some(StatementFact::AlterView {
1156 name,
1157 action: crate::analysis::facts::AlterViewAction::DropDefault {
1158 column: col_name,
1159 },
1160 });
1161 }
1162
1163 if let Some(expr) = node.expr() {
1165 return Some(StatementFact::AlterView {
1166 name,
1167 action: crate::analysis::facts::AlterViewAction::SetDefault {
1168 column: col_name,
1169 default: Some(crate::analysis::expr_visitor::ExprVisitor::convert(expr)),
1170 },
1171 });
1172 }
1173 }
1174
1175 if node.rename_token().is_some() && node.column_token().is_some() {
1177 let from = node
1178 .name_ref()
1179 .map(|nr| {
1180 Ident::new(
1181 nr.text().to_string().trim_matches('"').to_string(),
1182 nr.is_quoted(),
1183 )
1184 })
1185 .unwrap_or_else(|| Ident::new("unknown".to_string(), false));
1186 let to = node
1187 .name()
1188 .map(|n| {
1189 Ident::new(
1190 n.text().to_string().trim_matches('"').to_string(),
1191 n.is_quoted(),
1192 )
1193 })
1194 .unwrap_or_else(|| Ident::new("unknown".to_string(), false));
1195 return Some(StatementFact::AlterView {
1196 name,
1197 action: crate::analysis::facts::AlterViewAction::RenameColumn { from, to },
1198 });
1199 }
1200
1201 if let Some(so) = node.set_options() {
1203 let options: Vec<String> = so
1204 .syntax()
1205 .descendants()
1206 .filter_map(ast::NameRef::cast)
1207 .map(|nr| Self::resolve_name_ref(&nr))
1208 .collect();
1209 return Some(StatementFact::AlterView {
1210 name,
1211 action: crate::analysis::facts::AlterViewAction::SetOptions { options },
1212 });
1213 }
1214
1215 if let Some(ro) = node.reset_options() {
1217 let options: Vec<String> = ro
1218 .syntax()
1219 .descendants()
1220 .filter_map(ast::NameRef::cast)
1221 .map(|nr| Self::resolve_name_ref(&nr))
1222 .collect();
1223 return Some(StatementFact::AlterView {
1224 name,
1225 action: crate::analysis::facts::AlterViewAction::ResetOptions { options },
1226 });
1227 }
1228
1229 None
1231 }
1232
1233 fn extract_create_materialized_view(node: &CreateMaterializedView) -> Option<StatementFact> {
1234 let path = node.path()?;
1235 Some(StatementFact::CreateMaterializedView {
1236 name: Self::path_to_qualified_name(&path)?,
1237 depends_on: Self::extract_view_dependencies(node.syntax()),
1238 })
1239 }
1240
1241 fn extract_alter_materialized_view(node: &ast::AlterMaterializedView) -> Option<StatementFact> {
1242 let path = node.path()?;
1243 let new_name = node.action().find_map(|action| {
1244 if let squawk_syntax::ast::AlterMaterializedViewAction::RenameTo(rt) = action {
1245 rt.name().map(|n| {
1246 Ident::new(
1247 n.text().to_string().trim_matches('"').to_string(),
1248 n.is_quoted(),
1249 )
1250 })
1251 } else {
1252 None
1253 }
1254 });
1255 Some(StatementFact::AlterMaterializedView {
1256 name: Self::path_to_qualified_name(&path)?,
1257 new_name,
1258 })
1259 }
1260
1261 fn extract_refresh(node: &ast::Refresh) -> Option<StatementFact> {
1262 let path = node.path()?;
1263 Some(StatementFact::RefreshMaterializedView {
1264 name: Self::path_to_qualified_name(&path)?,
1265 concurrently: node.concurrently_token().is_some(),
1266 })
1267 }
1268
1269 fn extract_drop_view(node: &DropView) -> Option<StatementFact> {
1270 let path = node.paths().next()?;
1271 Some(StatementFact::DropView {
1272 name: Self::path_to_qualified_name(&path)?,
1273 if_exists: node.if_exists().is_some(),
1274 cascade: node.cascade_token().is_some(),
1275 })
1276 }
1277
1278 fn extract_drop_materialized_view(node: &DropMaterializedView) -> Option<StatementFact> {
1279 let names: Vec<QualifiedName> = node
1280 .paths()
1281 .filter_map(|p| Self::path_to_qualified_name(&p))
1282 .collect();
1283 if names.is_empty() {
1284 return None;
1285 }
1286 Some(StatementFact::DropMaterializedView {
1287 names,
1288 if_exists: node.if_exists().is_some(),
1289 cascade: node.cascade_token().is_some(),
1290 })
1291 }
1292
1293 fn extract_view_dependencies(syntax: &squawk_syntax::SyntaxNode) -> Vec<QualifiedName> {
1294 let mut depends_on = Vec::new();
1295 let keywords = [
1296 "SELECT",
1297 "FROM",
1298 "WHERE",
1299 "JOIN",
1300 "ON",
1301 "AND",
1302 "OR",
1303 "AS",
1304 "WITH",
1305 "GROUP",
1306 "BY",
1307 "HAVING",
1308 "LIMIT",
1309 "OFFSET",
1310 "ORDER",
1311 "ASC",
1312 "DESC",
1313 "IN",
1314 "NOT",
1315 "IS",
1316 "NULL",
1317 "UNION",
1318 "ALL",
1319 "EXCEPT",
1320 "INTERSECT",
1321 "TRUE",
1322 "FALSE",
1323 ];
1324
1325 let mut local_declarations = Vec::new();
1326 for name_node in syntax.descendants().filter_map(Name::cast) {
1327 local_declarations.push(name_node.text().to_string().trim_matches('"').to_string());
1328 }
1329
1330 for n in syntax.descendants().filter_map(NameRef::cast) {
1331 let text = n.text().to_string();
1332 let upper = text.to_uppercase();
1333 let is_quoted = n.is_quoted();
1334 let clean_text = text.trim_matches('"').to_string();
1335
1336 if !is_quoted && keywords.contains(&upper.as_str()) {
1337 continue;
1338 }
1339
1340 if local_declarations.contains(&clean_text) {
1341 continue;
1342 }
1343
1344 let field_expr = n.syntax().ancestors().skip(1).find_map(FieldExpr::cast);
1345
1346 let qname = if let Some(fe) = field_expr {
1347 let name_refs: Vec<NameRef> =
1348 fe.syntax().children().filter_map(NameRef::cast).collect();
1349
1350 if name_refs.len() >= 2 {
1351 let is_last = name_refs
1352 .last()
1353 .is_some_and(|last| last.syntax().text_range() == n.syntax().text_range());
1354
1355 if is_last {
1356 let schema_text = name_refs[0].text().to_string();
1357 QualifiedName::new(
1358 Some(Ident::new(
1359 schema_text.trim_matches('"').to_string(),
1360 name_refs[0].is_quoted(),
1361 )),
1362 Ident::new(clean_text.clone(), is_quoted),
1363 )
1364 } else {
1365 continue;
1366 }
1367 } else {
1368 QualifiedName::new(None, Ident::new(clean_text.clone(), is_quoted))
1369 }
1370 } else {
1371 QualifiedName::new(None, Ident::new(clean_text.clone(), is_quoted))
1372 };
1373
1374 if !depends_on.contains(&qname) {
1375 depends_on.push(qname);
1376 }
1377 }
1378 depends_on
1379 }
1380
1381 fn extract_create_sequence(node: &CreateSequence) -> Option<StatementFact> {
1382 let path = node.path()?;
1383 let name = Self::path_to_qualified_name(&path)?;
1384 Some(StatementFact::CreateSequence {
1385 name,
1386 if_not_exists: node.if_not_exists().is_some(),
1387 owned_by: Self::extract_owned_by(node.syntax()),
1388 })
1389 }
1390
1391 fn extract_alter_sequence(node: &AlterSequence) -> Option<StatementFact> {
1392 let path = node.path()?;
1393 let name = Self::path_to_qualified_name(&path)?;
1394 Some(StatementFact::AlterSequence {
1395 name,
1396 owned_by: Self::extract_owned_by(node.syntax()),
1397 })
1398 }
1399
1400 fn extract_drop_sequence(node: &DropSequence) -> Option<StatementFact> {
1401 let names: Vec<QualifiedName> = node
1402 .paths()
1403 .filter_map(|p| Self::path_to_qualified_name(&p))
1404 .collect();
1405
1406 Some(StatementFact::DropSequence {
1407 names,
1408 if_exists: node.if_exists().is_some(),
1409 cascade: node.cascade_token().is_some(),
1410 })
1411 }
1412
1413 fn extract_owned_by(node: &squawk_syntax::SyntaxNode) -> Option<(QualifiedName, String)> {
1414 for opt in node.descendants().filter_map(ast::SequenceOption::cast) {
1415 if opt.owned_token().is_some() {
1416 let path = opt.path()?;
1417
1418 let segments: Vec<PathSegment> = path
1419 .syntax()
1420 .descendants()
1421 .filter_map(PathSegment::cast)
1422 .collect();
1423 if segments.len() >= 2 {
1424 let col_ident = Self::segment_ident(segments.last().unwrap().clone())?;
1425 let col_name = col_ident.resolve();
1426
1427 let table_len = segments.len() - 1;
1428 let table_name = if table_len == 1 {
1429 QualifiedName::new(None, Self::segment_ident(segments[0].clone())?)
1430 } else {
1431 QualifiedName::new(
1432 Some(Self::segment_ident(segments[table_len - 2].clone())?),
1433 Self::segment_ident(segments[table_len - 1].clone())?,
1434 )
1435 };
1436 return Some((table_name, col_name));
1437 }
1438 }
1439 }
1440 None
1441 }
1442
1443 fn extract_create_domain(node: &CreateDomain) -> Option<StatementFact> {
1444 let path = node.path()?;
1445 let base_type = node
1446 .ty()
1447 .map(|t| t.syntax().text().to_string())
1448 .unwrap_or_else(|| "<domain>".to_string());
1449 Some(StatementFact::CreateDomain {
1450 name: Self::path_to_qualified_name(&path)?,
1451 base_type,
1452 })
1453 }
1454
1455 fn extract_alter_domain(node: &AlterDomain) -> Option<StatementFact> {
1456 let path = node.path()?;
1457 let action = node.action().map(|a| match a {
1458 squawk_syntax::ast::AlterDomainAction::AddConstraint(_) => {
1459 crate::analysis::facts::AlterDomainActionFact::AddConstraint
1460 }
1461 squawk_syntax::ast::AlterDomainAction::DropConstraint(_) => {
1462 crate::analysis::facts::AlterDomainActionFact::DropConstraint
1463 }
1464 squawk_syntax::ast::AlterDomainAction::DropDefault(_) => {
1465 crate::analysis::facts::AlterDomainActionFact::DropDefault
1466 }
1467 squawk_syntax::ast::AlterDomainAction::DropNotNull(_) => {
1468 crate::analysis::facts::AlterDomainActionFact::DropNotNull
1469 }
1470 squawk_syntax::ast::AlterDomainAction::OwnerTo(_) => {
1471 crate::analysis::facts::AlterDomainActionFact::OwnerChange
1472 }
1473 squawk_syntax::ast::AlterDomainAction::RenameConstraint(_) => {
1474 crate::analysis::facts::AlterDomainActionFact::RenameConstraint
1475 }
1476 squawk_syntax::ast::AlterDomainAction::RenameTo(_) => {
1477 crate::analysis::facts::AlterDomainActionFact::RenameTo
1478 }
1479 squawk_syntax::ast::AlterDomainAction::SetDefault(_) => {
1480 crate::analysis::facts::AlterDomainActionFact::SetDefault
1481 }
1482 squawk_syntax::ast::AlterDomainAction::SetNotNull(_) => {
1483 crate::analysis::facts::AlterDomainActionFact::SetNotNull
1484 }
1485 squawk_syntax::ast::AlterDomainAction::SetSchema(_) => {
1486 crate::analysis::facts::AlterDomainActionFact::SetSchema
1487 }
1488 squawk_syntax::ast::AlterDomainAction::ValidateConstraint(_) => {
1489 crate::analysis::facts::AlterDomainActionFact::ValidateConstraint
1490 }
1491 });
1492 Some(StatementFact::AlterDomain {
1493 name: Self::path_to_qualified_name(&path)?,
1494 action,
1495 })
1496 }
1497
1498 fn extract_drop_type(node: &DropType) -> Option<StatementFact> {
1499 println!("EXTRACT DROP TYPE!");
1500 let names: Vec<QualifiedName> = node
1501 .paths()
1502 .filter_map(|p| Self::path_to_qualified_name(&p))
1503 .collect();
1504
1505 Some(StatementFact::DropType {
1506 names,
1507 if_exists: node.if_exists().is_some(),
1508 cascade: node.cascade_token().is_some(),
1509 })
1510 }
1511 fn extract_drop_domain(node: &DropDomain) -> Option<StatementFact> {
1512 let names: Vec<QualifiedName> = node
1513 .paths()
1514 .filter_map(|p| Self::path_to_qualified_name(&p))
1515 .collect();
1516
1517 Some(StatementFact::DropDomain {
1518 names,
1519 if_exists: node.if_exists().is_some(),
1520 cascade: node.cascade_token().is_some(),
1521 })
1522 }
1523
1524 fn extract_create_type(node: &CreateType) -> Option<StatementFact> {
1525 let path = node.path()?;
1526 let name = Self::path_to_qualified_name(&path)?;
1527
1528 let kind = if node.enum_token().is_some() {
1529 TypeCreationKind::Enum
1530 } else if node.range_token().is_some() {
1531 TypeCreationKind::Range
1532 } else if node.attribute_list().is_some() {
1533 TypeCreationKind::Composite
1534 } else {
1535 TypeCreationKind::Base
1536 };
1537
1538 Some(StatementFact::CreateType(CreateTypeFact { name, kind }))
1539 }
1540
1541 fn extract_alter_type(node: &AlterType) -> Option<StatementFact> {
1542 let path = node.path()?;
1543 let name = Self::path_to_qualified_name(&path)?;
1544 let mut actions = Vec::new();
1545
1546 if let Some(av) = node.add_value()
1547 && let Some(lit) = av.literal()
1548 {
1549 actions.push(AlterTypeActionFact::AddValue {
1550 new_value: lit
1551 .syntax()
1552 .text()
1553 .to_string()
1554 .trim_matches('\'')
1555 .to_string(),
1556 });
1557 }
1558
1559 Some(StatementFact::AlterType(AlterTypeFact { name, actions }))
1560 }
1561
1562 fn extract_create_policy(node: &CreatePolicy) -> Option<StatementFact> {
1563 let name = node.name().map(Self::resolve_name)?;
1564 let path = node.syntax().descendants().find_map(Path::cast)?;
1565 let table = Self::path_to_qualified_name(&path)?;
1566
1567 let permissive = if let Some(as_type) = node.as_policy_type() {
1568 as_type
1569 .ident_token()
1570 .map(|t| t.text().to_lowercase())
1571 .map(|t| t == "permissive")
1572 .unwrap_or(true)
1573 } else {
1574 true
1575 };
1576
1577 let command = if node.all_token().is_some() {
1578 crate::analysis::facts::PolicyCommand::All
1579 } else if node.select_token().is_some() {
1580 crate::analysis::facts::PolicyCommand::Select
1581 } else if node.insert_token().is_some() {
1582 crate::analysis::facts::PolicyCommand::Insert
1583 } else if node.update_token().is_some() {
1584 crate::analysis::facts::PolicyCommand::Update
1585 } else if node.delete_token().is_some() {
1586 crate::analysis::facts::PolicyCommand::Delete
1587 } else {
1588 crate::analysis::facts::PolicyCommand::All
1589 };
1590
1591 Some(StatementFact::CreatePolicy {
1592 name,
1593 table,
1594 permissive,
1595 command,
1596 })
1597 }
1598
1599 fn extract_drop_policy(node: &DropPolicy) -> Option<StatementFact> {
1600 let path = node.syntax().descendants().find_map(Path::cast)?;
1601 let table = Self::path_to_qualified_name(&path)?;
1602 let name = Self::resolve_name_ref(&node.name_ref()?);
1603 Some(StatementFact::DropPolicy {
1604 name,
1605 table,
1606 if_exists: node.if_exists().is_some(),
1607 })
1608 }
1609
1610 fn extract_create_trigger(node: &CreateTrigger) -> Option<StatementFact> {
1611 let name = node.name().map(Self::resolve_name)?;
1612 let table = node
1614 .on_table()
1615 .and_then(|on| on.path())
1616 .and_then(|p| Self::path_to_qualified_name(&p))?;
1617 let function = node.call_expr().and_then(|call| {
1618 let node_ref = call.syntax();
1619 let fn_name = node_ref.descendants().find_map(Name::cast).map(|n| {
1620 let ident = Ident::new(
1621 n.text().to_string().trim_matches('"').to_string(),
1622 n.is_quoted(),
1623 );
1624 QualifiedName::new(None, ident)
1625 });
1626 if fn_name.is_some() {
1627 return fn_name;
1628 }
1629 node_ref.descendants().find_map(NameRef::cast).map(|n| {
1630 let ident = Ident::new(
1631 n.text().to_string().trim_matches('"').to_string(),
1632 n.is_quoted(),
1633 );
1634 QualifiedName::new(None, ident)
1635 })
1636 });
1637 Some(StatementFact::CreateTrigger {
1638 name,
1639 table,
1640 function,
1641 })
1642 }
1643
1644 fn extract_drop_trigger(node: &DropTrigger) -> Option<StatementFact> {
1645 let trigger_path = node.path()?;
1646 let trigger_name = Self::path_to_qualified_name(&trigger_path)?.name.resolve();
1647 let table_path = node.on_table()?.path()?;
1648 let table = Self::path_to_qualified_name(&table_path)?;
1649 Some(StatementFact::DropTrigger {
1650 name: trigger_name,
1651 table,
1652 if_exists: node.if_exists().is_some(),
1653 })
1654 }
1655
1656 fn extract_param(param: &squawk_syntax::ast::Param) -> crate::analysis::facts::ParamFact {
1657 crate::analysis::facts::ParamFact {
1658 mode: match param.mode() {
1659 Some(ast::ParamMode::ParamVariadic(_)) => {
1660 crate::analysis::facts::ParamModeFact::Variadic
1661 }
1662 Some(ast::ParamMode::ParamInOut(_)) => crate::analysis::facts::ParamModeFact::InOut,
1663 Some(ast::ParamMode::ParamOut(_)) => crate::analysis::facts::ParamModeFact::Out,
1664 _ => crate::analysis::facts::ParamModeFact::In,
1665 },
1666 name: param.name().map(Self::resolve_name),
1667 ty: param
1668 .ty()
1669 .map(|t| t.syntax().text().to_string())
1670 .unwrap_or_else(|| "unknown".into()),
1671 default: param.param_default().and_then(|pd| {
1672 pd.expr()
1673 .map(crate::analysis::expr_visitor::ExprVisitor::convert)
1674 }),
1675 }
1676 }
1677
1678 fn extract_ret_type(ret: &squawk_syntax::ast::RetType) -> crate::analysis::facts::RetTypeFact {
1679 if let Some(tal) = ret.table_arg_list() {
1680 let cols = tal
1681 .args()
1682 .filter_map(|arg| match arg {
1683 TableArg::Column(col) => Self::extract_column_fact(&col),
1684 _ => None,
1685 })
1686 .collect();
1687 crate::analysis::facts::RetTypeFact::Table(cols)
1688 } else {
1689 let ty = ret
1690 .ty()
1691 .map(|t| t.syntax().text().to_string())
1692 .unwrap_or_else(|| "unknown".into());
1693 crate::analysis::facts::RetTypeFact::Scalar(ty)
1694 }
1695 }
1696
1697 fn extract_func_option(
1698 opt: &squawk_syntax::ast::FuncOption,
1699 ) -> crate::analysis::facts::FuncOptionFact {
1700 match opt {
1701 ast::FuncOption::LanguageFuncOption(f) => {
1702 crate::analysis::facts::FuncOptionFact::Language(
1703 f.name_ref()
1704 .map(|n| n.text().to_string())
1705 .unwrap_or_default(),
1706 )
1707 }
1708 ast::FuncOption::VolatilityFuncOption(f) => {
1709 let vol = if f.immutable_token().is_some() {
1710 crate::analysis::facts::VolatilityKind::Immutable
1711 } else if f.stable_token().is_some() {
1712 crate::analysis::facts::VolatilityKind::Stable
1713 } else {
1714 crate::analysis::facts::VolatilityKind::Volatile
1715 };
1716 crate::analysis::facts::FuncOptionFact::Volatility(vol)
1717 }
1718 ast::FuncOption::SecurityFuncOption(f) => {
1719 let sec = if f.invoker_token().is_some() {
1720 crate::analysis::facts::SecurityKind::Invoker
1721 } else {
1722 crate::analysis::facts::SecurityKind::Definer
1723 };
1724 crate::analysis::facts::FuncOptionFact::Security(sec)
1725 }
1726 ast::FuncOption::StrictFuncOption(f) => {
1727 let strct = if f.called_token().is_some() {
1728 crate::analysis::facts::StrictKind::CalledOnNull
1729 } else if f.returns_token().is_some() {
1730 crate::analysis::facts::StrictKind::ReturnsNullOnNull
1731 } else {
1732 crate::analysis::facts::StrictKind::Strict
1733 };
1734 crate::analysis::facts::FuncOptionFact::Strict(strct)
1735 }
1736 ast::FuncOption::LeakproofFuncOption(f) => {
1737 let is_leakproof = f.leakproof_token().is_some();
1738 crate::analysis::facts::FuncOptionFact::Leakproof(is_leakproof)
1739 }
1740 ast::FuncOption::ParallelFuncOption(f) => {
1741 crate::analysis::facts::FuncOptionFact::Parallel(
1742 f.syntax()
1743 .descendants()
1744 .find_map(ast::NameRef::cast)
1745 .map(|n| n.text())
1746 .unwrap_or_default(),
1747 )
1748 }
1749 ast::FuncOption::CostFuncOption(_) => crate::analysis::facts::FuncOptionFact::Cost,
1750 ast::FuncOption::RowsFuncOption(_) => crate::analysis::facts::FuncOptionFact::Rows,
1751 ast::FuncOption::ResetFuncOption(f) => crate::analysis::facts::FuncOptionFact::Reset(
1752 f.name_ref()
1753 .map(|n| n.text().to_string())
1754 .unwrap_or_default(),
1755 ),
1756 ast::FuncOption::AsFuncOption(f) => {
1757 let lit = f
1758 .syntax()
1759 .descendants()
1760 .find_map(ast::Literal::cast)
1761 .map(|l| l.syntax().text().to_string().trim_matches('\'').to_string());
1762 crate::analysis::facts::FuncOptionFact::As {
1763 definition: lit,
1764 obj_file: None,
1765 link_symbol: None,
1766 }
1767 }
1768 ast::FuncOption::TransformFuncOption(_) => {
1769 crate::analysis::facts::FuncOptionFact::Transform
1770 }
1771 ast::FuncOption::WindowFuncOption(_) => crate::analysis::facts::FuncOptionFact::Window,
1772 ast::FuncOption::SupportFuncOption(_) => {
1773 crate::analysis::facts::FuncOptionFact::Support
1774 }
1775 _ => crate::analysis::facts::FuncOptionFact::Unknown,
1776 }
1777 }
1778
1779 fn extract_create_function(node: &squawk_syntax::ast::CreateFunction) -> Option<StatementFact> {
1780 let path = node.path()?;
1781 let name = Self::path_to_qualified_name(&path)?;
1782 let or_replace = node.or_replace().is_some();
1783 let params = node
1784 .param_list()
1785 .map(|pl| pl.params().map(|p| Self::extract_param(&p)).collect())
1786 .unwrap_or_default();
1787 let return_type = node.ret_type().map(|r| Self::extract_ret_type(&r));
1788 let options = node
1789 .option_list()
1790 .map(|ol| {
1791 ol.options()
1792 .map(|o| Self::extract_func_option(&o))
1793 .collect()
1794 })
1795 .unwrap_or_default();
1796
1797 Some(StatementFact::CreateFunction(
1798 crate::analysis::facts::CreateFunctionFact {
1799 name,
1800 or_replace,
1801 params,
1802 return_type,
1803 options,
1804 },
1805 ))
1806 }
1807
1808 fn extract_alter_function(node: &ast::AlterFunction) -> Option<StatementFact> {
1809 let path = node.function_sig().and_then(|sig| sig.path())?;
1810 let name = Self::path_to_qualified_name(&path)?;
1811 let params = node
1812 .function_sig()
1813 .and_then(|sig| sig.param_list())
1814 .map(|pl| pl.params().map(|p| p.syntax().text().to_string()).collect())
1815 .unwrap_or_default();
1816 let action = if let Some(rt) = node.rename_to() {
1817 crate::analysis::facts::AlterFunctionAction::Rename {
1818 from: name.name.resolve(),
1819 to: rt.name().map(|n| n.text()).unwrap_or_default(),
1820 }
1821 } else if let Some(ot) = node.owner_to() {
1822 crate::analysis::facts::AlterFunctionAction::OwnerChange(Self::extract_role(
1823 &ot.role_ref().unwrap(),
1824 ))
1825 } else if let Some(ss) = node.set_schema() {
1826 crate::analysis::facts::AlterFunctionAction::SchemaChange {
1827 new_schema: ss.name_ref().map(|n| n.text()).unwrap_or_default(),
1828 }
1829 } else if let Some(de) = node.depends_on_extension() {
1830 crate::analysis::facts::AlterFunctionAction::DependsOnExtension {
1831 extension: de
1832 .name_ref()
1833 .map(|n| n.text().to_string())
1834 .unwrap_or_default(),
1835 }
1836 } else if let Some(nde) = node.no_depends_on_extension() {
1837 crate::analysis::facts::AlterFunctionAction::NoDependsOnExtension {
1838 extension: nde
1839 .name_ref()
1840 .map(|n| n.text().to_string())
1841 .unwrap_or_default(),
1842 }
1843 } else {
1844 let ol = node.func_option_list()?;
1845 crate::analysis::facts::AlterFunctionAction::OptionsChange(
1846 ol.options()
1847 .map(|o| Self::extract_func_option(&o))
1848 .collect(),
1849 )
1850 };
1851
1852 Some(StatementFact::AlterFunction(
1853 crate::analysis::facts::AlterFunctionFact {
1854 name,
1855 params,
1856 action,
1857 },
1858 ))
1859 }
1860
1861 fn extract_drop_function(node: &squawk_syntax::ast::DropFunction) -> Option<StatementFact> {
1862 let sigs = node
1863 .function_sig_list()
1864 .map(|sl| {
1865 sl.function_sigs()
1866 .filter_map(|sig| {
1867 let path = sig.path()?;
1868 Some(crate::analysis::facts::FunctionSigFact {
1869 name: Self::path_to_qualified_name(&path).unwrap_or_else(|| {
1870 QualifiedName::new(None, Ident::new("unknown".to_string(), false))
1871 }),
1872 params: sig
1873 .param_list()
1874 .map(|pl| {
1875 pl.params()
1876 .map(|p| {
1877 p.ty()
1878 .map(|t| t.syntax().text().to_string())
1879 .unwrap_or_else(|| "unknown".into())
1880 })
1881 .collect()
1882 })
1883 .unwrap_or_default(),
1884 })
1885 })
1886 .collect::<Vec<_>>()
1887 })
1888 .unwrap_or_default();
1889
1890 Some(StatementFact::DropFunction(
1891 crate::analysis::facts::DropFunctionFact {
1892 signatures: sigs,
1893 if_exists: node.if_exists().is_some(),
1894 cascade: node.cascade_token().is_some(),
1895 },
1896 ))
1897 }
1898
1899 fn extract_create_procedure(
1900 node: &squawk_syntax::ast::CreateProcedure,
1901 ) -> Option<StatementFact> {
1902 let path = node.path()?;
1903 let name = Self::path_to_qualified_name(&path)?;
1904 let or_replace = node.or_replace().is_some();
1905 let params = node
1906 .param_list()
1907 .map(|pl| pl.params().map(|p| Self::extract_param(&p)).collect())
1908 .unwrap_or_default();
1909 let options = node
1910 .option_list()
1911 .map(|ol| {
1912 ol.options()
1913 .map(|o| Self::extract_func_option(&o))
1914 .collect()
1915 })
1916 .unwrap_or_default();
1917
1918 Some(StatementFact::CreateProcedure(
1919 crate::analysis::facts::CreateProcedureFact {
1920 name,
1921 or_replace,
1922 params,
1923 options,
1924 },
1925 ))
1926 }
1927
1928 fn extract_alter_procedure(node: &squawk_syntax::ast::AlterProcedure) -> Option<StatementFact> {
1929 let sig = node.function_sig()?;
1930 let path = sig.path()?;
1931 let name = Self::path_to_qualified_name(&path)?;
1932 let params = sig
1933 .param_list()
1934 .map(|pl| pl.params().map(|p| p.syntax().text().to_string()).collect())
1935 .unwrap_or_default();
1936
1937 let action = if let Some(rt) = node.rename_to() {
1938 crate::analysis::facts::AlterFunctionAction::Rename {
1939 from: name.name.resolve(),
1940 to: rt.name().map(|n| n.text()).unwrap_or_default(),
1941 }
1942 } else if let Some(ot) = node.owner_to() {
1943 crate::analysis::facts::AlterFunctionAction::OwnerChange(Self::extract_role(
1944 &ot.role_ref().unwrap(),
1945 ))
1946 } else if let Some(ss) = node.set_schema() {
1947 crate::analysis::facts::AlterFunctionAction::SchemaChange {
1948 new_schema: ss.name_ref().map(|n| n.text()).unwrap_or_default(),
1949 }
1950 } else if let Some(de) = node.depends_on_extension() {
1951 crate::analysis::facts::AlterFunctionAction::DependsOnExtension {
1952 extension: de
1953 .name_ref()
1954 .map(|n| n.text().to_string())
1955 .unwrap_or_default(),
1956 }
1957 } else if let Some(nde) = node.no_depends_on_extension() {
1958 crate::analysis::facts::AlterFunctionAction::NoDependsOnExtension {
1959 extension: nde
1960 .name_ref()
1961 .map(|n| n.text().to_string())
1962 .unwrap_or_default(),
1963 }
1964 } else {
1965 let ol = node.func_option_list()?;
1966 crate::analysis::facts::AlterFunctionAction::OptionsChange(
1967 ol.options()
1968 .map(|o| Self::extract_func_option(&o))
1969 .collect(),
1970 )
1971 };
1972
1973 Some(StatementFact::AlterProcedure(
1974 crate::analysis::facts::AlterProcedureFact {
1975 name,
1976 params,
1977 action,
1978 },
1979 ))
1980 }
1981
1982 fn extract_drop_procedure(node: &squawk_syntax::ast::DropProcedure) -> Option<StatementFact> {
1983 let sigs = node
1984 .function_sig_list()
1985 .map(|sl| {
1986 sl.function_sigs()
1987 .filter_map(|sig| {
1988 let path = sig.path()?;
1989 Some(crate::analysis::facts::FunctionSigFact {
1990 name: Self::path_to_qualified_name(&path).unwrap_or_else(|| {
1991 QualifiedName::new(None, Ident::new("unknown".to_string(), false))
1992 }),
1993 params: sig
1994 .param_list()
1995 .map(|pl| {
1996 pl.params()
1997 .map(|p| {
1998 p.ty()
1999 .map(|t| t.syntax().text().to_string())
2000 .unwrap_or_else(|| "unknown".into())
2001 })
2002 .collect()
2003 })
2004 .unwrap_or_default(),
2005 })
2006 })
2007 .collect::<Vec<_>>()
2008 })
2009 .unwrap_or_default();
2010
2011 Some(StatementFact::DropProcedure(
2012 crate::analysis::facts::DropProcedureFact {
2013 signatures: sigs,
2014 if_exists: node.if_exists().is_some(),
2015 cascade: node.cascade_token().is_some(),
2016 },
2017 ))
2018 }
2019
2020 fn extract_create_publication(
2021 node: &squawk_syntax::ast::CreatePublication,
2022 ) -> Option<StatementFact> {
2023 let name = node.name().map(Self::resolve_name).unwrap_or_default();
2024 let scope = if node.all_token().is_some() && node.tables_token().is_some() {
2025 crate::analysis::facts::PublicationScope::AllTables {
2026 except: node
2027 .except_table_clause()
2028 .map(|c| {
2029 c.syntax()
2030 .descendants()
2031 .filter_map(NameRef::cast)
2032 .map(|nr| Self::resolve_name_ref(&nr))
2033 .collect()
2034 })
2035 .unwrap_or_default(),
2036 }
2037 } else {
2038 let objects = node
2039 .publication_objects()
2040 .map(|obj| {
2041 if let Some(path) = obj.path() {
2042 crate::analysis::facts::PublicationObjectFact::Table {
2043 name: Self::path_to_qualified_name(&path).unwrap_or_else(|| {
2044 QualifiedName::new(None, Ident::new("unknown".to_string(), false))
2045 }),
2046 only: obj.only_token().is_some(),
2047 include_partitions: obj.star_token().is_some(),
2048 columns: obj.column_list().map(|cl| {
2049 cl.columns()
2050 .filter_map(|c| c.name().map(Self::resolve_name))
2051 .collect()
2052 }),
2053 row_filter: obj.where_condition_clause().and_then(|w| {
2054 w.expr()
2055 .map(crate::analysis::expr_visitor::ExprVisitor::convert)
2056 }),
2057 }
2058 } else if obj.in_token().is_some() {
2059 crate::analysis::facts::PublicationObjectFact::SchemaTables {
2060 schema: obj
2061 .name_ref()
2062 .map(|n| n.text().to_string())
2063 .or_else(|| {
2064 obj.current_schema_token()
2065 .map(|_| "CURRENT_SCHEMA".to_string())
2066 })
2067 .unwrap_or_default(),
2068 row_filter: obj.where_condition_clause().and_then(|w| {
2069 w.expr()
2070 .map(crate::analysis::expr_visitor::ExprVisitor::convert)
2071 }),
2072 }
2073 } else if obj.current_schema_token().is_some() {
2074 crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand
2075 } else {
2076 crate::analysis::facts::PublicationObjectFact::Unknown
2077 }
2078 })
2079 .collect();
2080 crate::analysis::facts::PublicationScope::Explicit(objects)
2081 };
2082 let params = node
2083 .with_params()
2084 .map(|wp| {
2085 wp.attribute_list()
2086 .map(|al| {
2087 al.attribute_options()
2088 .map(|p| crate::analysis::facts::AttributeFact {
2089 name: p.name().map(|n| n.text().to_string()).unwrap_or_default(),
2090 value: p
2091 .syntax()
2092 .descendants()
2093 .find_map(ast::Literal::cast)
2094 .map(|l| l.syntax().text().to_string())
2095 .unwrap_or_default(),
2096 })
2097 .collect()
2098 })
2099 .unwrap_or_default()
2100 })
2101 .unwrap_or_default();
2102
2103 Some(StatementFact::CreatePublication(
2104 crate::analysis::facts::CreatePublicationFact {
2105 name,
2106 scope,
2107 params,
2108 },
2109 ))
2110 }
2111
2112 fn extract_alter_publication(
2113 node: &squawk_syntax::ast::AlterPublication,
2114 ) -> Option<StatementFact> {
2115 let name = node
2116 .name_ref()
2117 .map(|nr| Self::resolve_name_ref(&nr))
2118 .unwrap_or_default();
2119 Some(StatementFact::AlterPublication(
2120 crate::analysis::facts::AlterPublicationFact { name },
2121 ))
2122 }
2123
2124 fn extract_drop_publication(
2125 node: &squawk_syntax::ast::DropPublication,
2126 ) -> Option<StatementFact> {
2127 let names = node
2128 .name_refs()
2129 .map(|nr| Self::resolve_name_ref(&nr))
2130 .collect();
2131 Some(StatementFact::DropPublication(
2132 crate::analysis::facts::DropPublicationFact {
2133 names,
2134 if_exists: node.if_exists().is_some(),
2135 cascade: node.cascade_token().is_some(),
2136 },
2137 ))
2138 }
2139
2140 fn extract_create_subscription(
2141 node: &squawk_syntax::ast::CreateSubscription,
2142 ) -> Option<StatementFact> {
2143 let name = node.name().map(Self::resolve_name);
2144 let connection = if node.server_token().is_some() {
2145 crate::analysis::facts::ConnectionTarget::Server(node.name_ref().map(|n| n.text()))
2146 } else {
2147 crate::analysis::facts::ConnectionTarget::Literal(
2148 node.literal()
2149 .map(|l| l.syntax().text().to_string().trim_matches('\'').to_string()),
2150 )
2151 };
2152 let publications = node
2153 .name_refs()
2154 .map(|nr| Self::resolve_name_ref(&nr))
2155 .collect();
2156 let params = node.with_params().map(|wp| {
2157 wp.attribute_list()
2158 .map(|al| {
2159 al.attribute_options()
2160 .map(|p| crate::analysis::facts::AttributeFact {
2161 name: p.name().map(|n| n.text().to_string()).unwrap_or_default(),
2162 value: p
2163 .syntax()
2164 .descendants()
2165 .find_map(ast::Literal::cast)
2166 .map(|l| l.syntax().text().to_string())
2167 .unwrap_or_default(),
2168 })
2169 .collect()
2170 })
2171 .unwrap_or_default()
2172 });
2173
2174 Some(StatementFact::CreateSubscription(
2175 crate::analysis::facts::CreateSubscriptionFact {
2176 name,
2177 connection,
2178 publications,
2179 params,
2180 },
2181 ))
2182 }
2183
2184 fn extract_alter_subscription(
2185 node: &squawk_syntax::ast::AlterSubscription,
2186 ) -> Option<StatementFact> {
2187 let name = node
2188 .name_ref()
2189 .map(|nr| Self::resolve_name_ref(&nr))
2190 .unwrap_or_default();
2191 Some(StatementFact::AlterSubscription(
2192 crate::analysis::facts::AlterSubscriptionFact { name },
2193 ))
2194 }
2195
2196 fn extract_drop_subscription(
2197 node: &squawk_syntax::ast::DropSubscription,
2198 ) -> Option<StatementFact> {
2199 let name = Self::resolve_name_ref(&node.name_ref()?);
2200 Some(StatementFact::DropSubscription(
2201 crate::analysis::facts::DropSubscriptionFact {
2202 name,
2203 if_exists: node.if_exists().is_some(),
2204 },
2205 ))
2206 }
2207
2208 fn extract_role(role_ref: &squawk_syntax::ast::RoleRef) -> crate::analysis::facts::RoleFact {
2209 if let Some(name) = role_ref.name_ref() {
2210 crate::analysis::facts::RoleFact::Named {
2211 name: Self::resolve_name_ref(&name),
2212 via_legacy_group_syntax: role_ref.group_token().is_some(),
2213 }
2214 } else if role_ref.current_role_token().is_some() {
2215 crate::analysis::facts::RoleFact::CurrentRole
2216 } else if role_ref.current_user_token().is_some() {
2217 crate::analysis::facts::RoleFact::CurrentUser
2218 } else if role_ref.session_user_token().is_some() {
2219 crate::analysis::facts::RoleFact::SessionUser
2220 } else {
2221 crate::analysis::facts::RoleFact::Unknown
2222 }
2223 }
2224
2225 fn extract_create_role(node: &squawk_syntax::ast::CreateRole) -> Option<StatementFact> {
2226 let name = Self::resolve_name(node.name()?);
2227 let inherits = node
2228 .role_option_list()
2229 .map(|ol| ol.role_options().any(|o| o.inherit_token().is_some()))
2230 .unwrap_or(false);
2231 Some(StatementFact::CreateRole(
2232 crate::analysis::facts::CreateRoleFact { name, inherits },
2233 ))
2234 }
2235
2236 fn extract_alter_role(node: &squawk_syntax::ast::AlterRole) -> Option<StatementFact> {
2237 let name = Self::extract_role(&node.role_ref()?);
2238 let inherits = node.role_option_list().and_then(|ol| {
2239 let mut found = None;
2240 for o in ol.role_options() {
2241 if o.inherit_token().is_some() {
2242 found = Some(true);
2243 }
2244 }
2245 found
2246 });
2247 Some(StatementFact::AlterRole(
2248 crate::analysis::facts::AlterRoleFact { name, inherits },
2249 ))
2250 }
2251
2252 fn extract_drop_role(node: &squawk_syntax::ast::DropRole) -> Option<StatementFact> {
2253 let names = node
2254 .name_refs()
2255 .map(|nr| Self::resolve_name_ref(&nr))
2256 .collect();
2257 Some(StatementFact::DropRole(
2258 crate::analysis::facts::DropRoleFact {
2259 names,
2260 if_exists: node.if_exists().is_some(),
2261 },
2262 ))
2263 }
2264
2265 fn extract_privilege_from_revoke_command(
2266 cmd: &RevokeCommand,
2267 ) -> crate::analysis::facts::PrivilegeFact {
2268 if let Some(role_ref) = cmd.role_ref() {
2269 crate::analysis::facts::PrivilegeFact::RoleMembership(Self::resolve_name_ref(
2270 &role_ref.name_ref().unwrap(),
2271 ))
2272 } else if cmd.select_token().is_some() {
2273 crate::analysis::facts::PrivilegeFact::Select
2274 } else if cmd.insert_token().is_some() {
2275 crate::analysis::facts::PrivilegeFact::Insert
2276 } else if cmd.update_token().is_some() {
2277 crate::analysis::facts::PrivilegeFact::Update
2278 } else if cmd.delete_token().is_some() {
2279 crate::analysis::facts::PrivilegeFact::Delete
2280 } else if cmd.truncate_token().is_some() {
2281 crate::analysis::facts::PrivilegeFact::Truncate
2282 } else if cmd.references_token().is_some() {
2283 crate::analysis::facts::PrivilegeFact::References
2284 } else if cmd.trigger_token().is_some() {
2285 crate::analysis::facts::PrivilegeFact::Trigger
2286 } else if cmd.execute_token().is_some() {
2287 crate::analysis::facts::PrivilegeFact::Execute
2288 } else if cmd.create_token().is_some() {
2289 crate::analysis::facts::PrivilegeFact::Create
2290 } else if cmd.temp_token().is_some() || cmd.temporary_token().is_some() {
2291 crate::analysis::facts::PrivilegeFact::Temporary
2292 } else if cmd.alter_token().is_some() && cmd.system_token().is_some() {
2293 crate::analysis::facts::PrivilegeFact::AlterSystem
2294 } else if cmd.all_token().is_some() {
2295 crate::analysis::facts::PrivilegeFact::All
2296 } else if let Some(ident) = cmd.syntax().descendants().find_map(ast::Name::cast) {
2297 crate::analysis::facts::PrivilegeFact::Named(ident.text().to_string())
2298 } else {
2299 crate::analysis::facts::PrivilegeFact::Unknown
2300 }
2301 }
2302
2303 fn extract_grant_target_from_privilege_objects(
2304 po: Option<squawk_syntax::ast::PrivilegeObjects>,
2305 syntax: &squawk_syntax::SyntaxNode,
2306 ) -> Option<crate::analysis::facts::GrantTarget> {
2307 if let Some(po) = po {
2308 if po.table_token().is_some() || po.paths().next().is_some() {
2309 let paths: Vec<_> = po
2310 .paths()
2311 .filter_map(|p| Self::path_to_qualified_name(&p))
2312 .collect();
2313 if paths.is_empty() {
2314 None
2315 } else {
2316 Some(crate::analysis::facts::GrantTarget::Tables(paths))
2317 }
2318 } else if po.tables_token().is_some()
2319 && po.in_token().is_some()
2320 && po.schema_token().is_some()
2321 {
2322 let schemas: Vec<_> = po
2323 .name_refs()
2324 .map(|nr| Self::resolve_name_ref(&nr))
2325 .collect();
2326 if schemas.is_empty() {
2327 None
2328 } else {
2329 Some(crate::analysis::facts::GrantTarget::AllTablesInSchema(
2330 schemas,
2331 ))
2332 }
2333 } else {
2334 None
2335 }
2336 } else {
2337 let paths: Vec<_> = syntax.descendants().filter_map(Path::cast).collect();
2338 let name_refs: Vec<_> = syntax.descendants().filter_map(NameRef::cast).collect();
2339 if !paths.is_empty() {
2340 Some(crate::analysis::facts::GrantTarget::Tables(
2341 paths
2342 .iter()
2343 .filter_map(Self::path_to_qualified_name)
2344 .collect(),
2345 ))
2346 } else if !name_refs.is_empty() {
2347 Some(crate::analysis::facts::GrantTarget::AllTablesInSchema(
2348 name_refs.iter().map(Self::resolve_name_ref).collect(),
2349 ))
2350 } else {
2351 None
2352 }
2353 }
2354 }
2355
2356 fn extract_grant(node: &Grant) -> Option<StatementFact> {
2357 let privileges = if node.all_token().is_some() {
2358 crate::analysis::facts::PrivilegeSpec::All
2359 } else {
2360 crate::analysis::facts::PrivilegeSpec::List(
2361 node.revoke_command_list()
2362 .map(|rcl| {
2363 rcl.revoke_commands()
2364 .map(|rc| Self::extract_privilege_from_revoke_command(&rc))
2365 .collect()
2366 })
2367 .unwrap_or_default(),
2368 )
2369 };
2370
2371 let target = Self::extract_grant_target_from_privilege_objects(
2372 node.privilege_objects(),
2373 node.syntax(),
2374 )?;
2375
2376 let grantees = node
2377 .role_ref_list()
2378 .map(|rrl| rrl.role_refs().map(|r| Self::extract_role(&r)).collect())
2379 .unwrap_or_default();
2380 let with_grant_option = node.grant_with_clause().is_some();
2381 let granted_by = node.role_ref().map(|r| Self::extract_role(&r));
2382
2383 Some(StatementFact::Grant(crate::analysis::facts::GrantFact {
2384 privileges,
2385 target,
2386 grantees,
2387 with_grant_option,
2388 granted_by,
2389 }))
2390 }
2391
2392 fn extract_revoke(node: &Revoke) -> Option<StatementFact> {
2393 let grant_option_only = node.for_token().is_some()
2394 && node.grant_token().is_some()
2395 && node.option_token().is_some();
2396
2397 let privileges = if let Some(p) = node.privileges() {
2398 if p.all_token().is_some() {
2399 crate::analysis::facts::PrivilegeSpec::All
2400 } else {
2401 crate::analysis::facts::PrivilegeSpec::List(
2402 p.revoke_command_list()
2403 .map(|rcl| {
2404 rcl.revoke_commands()
2405 .map(|rc| Self::extract_privilege_from_revoke_command(&rc))
2406 .collect()
2407 })
2408 .unwrap_or_default(),
2409 )
2410 }
2411 } else {
2412 crate::analysis::facts::PrivilegeSpec::List(vec![])
2413 };
2414
2415 let target = Self::extract_grant_target_from_privilege_objects(
2416 node.privilege_objects(),
2417 node.syntax(),
2418 )?;
2419
2420 let revokees = node
2421 .role_ref_list()
2422 .map(|rrl| rrl.role_refs().map(|r| Self::extract_role(&r)).collect())
2423 .unwrap_or_default();
2424 let granted_by = node.role_ref().map(|r| Self::extract_role(&r));
2425 let cascade = node.cascade_token().is_some();
2426
2427 Some(StatementFact::Revoke(crate::analysis::facts::RevokeFact {
2428 grant_option_only,
2429 privileges,
2430 target,
2431 revokees,
2432 granted_by,
2433 cascade,
2434 }))
2435 }
2436
2437 fn extract_db_option(
2438 opt: squawk_syntax::ast::DatabaseOption,
2439 ) -> crate::analysis::facts::DatabaseOptionFact {
2440 let value = if opt.default_token().is_some() {
2441 crate::analysis::facts::DatabaseOptionValue::Default
2442 } else {
2443 crate::analysis::facts::DatabaseOptionValue::Literal(
2444 opt.literal()
2445 .map(|l| l.syntax().text().to_string().trim_matches('\'').to_string()),
2446 )
2447 };
2448
2449 if opt.owner_token().is_some() {
2450 crate::analysis::facts::DatabaseOptionFact::Owner(value)
2451 } else if opt.template_token().is_some() {
2452 crate::analysis::facts::DatabaseOptionFact::Template(value)
2453 } else if opt.encoding_token().is_some() {
2454 crate::analysis::facts::DatabaseOptionFact::Encoding(value)
2455 } else if opt.tablespace_token().is_some() {
2456 crate::analysis::facts::DatabaseOptionFact::Tablespace(value)
2457 } else if opt.connection_token().is_some() && opt.limit_token().is_some() {
2458 crate::analysis::facts::DatabaseOptionFact::ConnectionLimit(value)
2459 } else if let Some(ident) = opt.ident_token() {
2460 crate::analysis::facts::DatabaseOptionFact::Named(ident.text().to_string(), value)
2461 } else {
2462 crate::analysis::facts::DatabaseOptionFact::Unknown(value)
2463 }
2464 }
2465
2466 fn extract_create_database(node: &CreateDatabase) -> Option<StatementFact> {
2467 let name = node.name().map(Self::resolve_name).unwrap_or_default();
2468 let options = node
2469 .database_option_list()
2470 .map(|ol| ol.database_options().map(Self::extract_db_option).collect())
2471 .unwrap_or_default();
2472 Some(StatementFact::CreateDatabase(
2473 crate::analysis::facts::CreateDatabaseFact { name, options },
2474 ))
2475 }
2476
2477 fn extract_alter_database(node: &ast::AlterDatabase) -> Option<StatementFact> {
2478 let name_ref = node.name_ref()?;
2479 let name = QualifiedName::new(
2480 None,
2481 Ident::new(name_ref.text().to_string(), name_ref.is_quoted()),
2482 );
2483
2484 let action = if let Some(rt) = node.rename_to() {
2485 crate::analysis::facts::AlterDatabaseAction::Rename {
2486 to: rt.name().map(|n| n.text()).unwrap_or_default(),
2487 }
2488 } else if let Some(ot) = node.owner_to() {
2489 crate::analysis::facts::AlterDatabaseAction::OwnerChange(Self::extract_role(
2490 &ot.role_ref().unwrap(),
2491 ))
2492 } else if let Some(st) = node.set_tablespace() {
2493 crate::analysis::facts::AlterDatabaseAction::TablespaceChange {
2494 new_tablespace: st
2495 .path()
2496 .map(|p| p.syntax().text().to_string())
2497 .unwrap_or_default(),
2498 }
2499 } else if let Some(scp) = node.set_config_param() {
2500 crate::analysis::facts::AlterDatabaseAction::SetConfigParam {
2501 param: scp
2502 .path()
2503 .map(|p| p.syntax().text().to_string())
2504 .unwrap_or_default(),
2505 }
2506 } else if let Some(rcp) = node.reset_config_param() {
2507 crate::analysis::facts::AlterDatabaseAction::ResetConfigParam {
2508 param: rcp.path().map(|p| p.syntax().text().to_string()),
2509 }
2510 } else if node.refresh_collation_version().is_some() {
2511 crate::analysis::facts::AlterDatabaseAction::RefreshCollationVersion
2512 } else {
2513 let ol = node.database_option_list()?;
2514 crate::analysis::facts::AlterDatabaseAction::OptionChanges(
2515 ol.database_options().map(Self::extract_db_option).collect(),
2516 )
2517 };
2518
2519 Some(StatementFact::AlterDatabase(
2520 crate::analysis::facts::AlterDatabaseFact { name, action },
2521 ))
2522 }
2523
2524 fn extract_drop_database(node: &ast::DropDatabase) -> Option<StatementFact> {
2525 let name_ref = node.name_ref()?;
2526 let name = QualifiedName::new(
2527 None,
2528 Ident::new(name_ref.text().to_string(), name_ref.is_quoted()),
2529 );
2530 Some(StatementFact::DropDatabase(
2531 crate::analysis::facts::DropDatabaseFact {
2532 name,
2533 if_exists: node.if_exists().is_some(),
2534 },
2535 ))
2536 }
2537
2538 fn extract_set(node: &Set) -> Option<StatementFact> {
2539 let setting_name = node.path()?.syntax().text().to_string().to_lowercase();
2540 if setting_name != "search_path" {
2541 return None;
2542 }
2543
2544 let schemas: Vec<String> = node
2545 .config_values()
2546 .filter_map(|cv| match cv {
2547 ast::ConfigValue::NameRef(nr) => Some(Self::resolve_name_ref(&nr)),
2548 ast::ConfigValue::Literal(_) => None,
2549 })
2550 .filter(|s| s.to_lowercase() != "default")
2551 .collect();
2552
2553 let is_default = node.config_value().is_some_and(|cv| {
2554 matches!(&cv, ast::ConfigValue::NameRef(nr) if nr.text().to_uppercase() == "DEFAULT")
2555 });
2556
2557 if is_default {
2558 return Some(StatementFact::SetSearchPath {
2559 target: SearchPathTarget::Default,
2560 });
2561 }
2562
2563 Some(StatementFact::SetSearchPath {
2564 target: SearchPathTarget::Schemas(schemas),
2565 })
2566 }
2567
2568 fn extract_rollback(node: &Rollback) -> Option<StatementFact> {
2569 if node.prepared_token().is_some() {
2570 return Some(StatementFact::OpaqueBlock);
2571 }
2572
2573 match node.name_ref().map(|nr| Self::resolve_name_ref(&nr)) {
2574 Some(name) => Some(StatementFact::RollbackToSavepoint { name }),
2575 None => Some(StatementFact::RollbackTransaction),
2576 }
2577 }
2578
2579 fn extract_savepoint(node: &Savepoint) -> StatementFact {
2580 StatementFact::Savepoint {
2581 name: node
2582 .name()
2583 .map(|n| n.text().to_string())
2584 .unwrap_or_default(),
2585 }
2586 }
2587
2588 fn extract_release_savepoint(node: &ReleaseSavepoint) -> StatementFact {
2589 StatementFact::ReleaseSavepoint {
2590 name: node
2591 .name_ref()
2592 .map(|n| n.text().to_string())
2593 .unwrap_or_default(),
2594 }
2595 }
2596
2597 fn segment_ident(segment: PathSegment) -> Option<Ident> {
2598 if let Some(nr) = segment.syntax().descendants().find_map(NameRef::cast) {
2599 Some(Ident::new(nr.text().to_string(), nr.is_quoted()))
2600 } else {
2601 segment
2602 .syntax()
2603 .descendants()
2604 .find_map(Name::cast)
2605 .map(|n| Ident::new(n.text().to_string(), n.is_quoted()))
2606 }
2607 }
2608
2609 fn path_to_qualified_name(path: &Path) -> Option<QualifiedName> {
2610 let segments: Vec<PathSegment> = path
2611 .syntax()
2612 .descendants()
2613 .filter_map(PathSegment::cast)
2614 .collect();
2615 if segments.is_empty() {
2616 return None;
2617 }
2618
2619 if segments.len() >= 2 {
2620 let schema = Self::segment_ident(segments[0].clone());
2621 let name = Self::segment_ident(segments[1].clone())?;
2622 Some(QualifiedName::new(schema, name))
2623 } else {
2624 let name = Self::segment_ident(segments[0].clone())?;
2625 Some(QualifiedName::new(None, name))
2626 }
2627 }
2628}