1pub mod ast_canon;
8mod ast_resolution;
9pub mod builder;
10pub mod cluster;
11pub mod directives;
12pub mod error;
13pub mod normalize_body;
14pub mod normalize_expr;
15pub mod statement;
16
17use std::collections::{BTreeMap, HashMap};
18use std::path::{Path, PathBuf};
19
20pub use directives::{FileDirectives, extract_file_directives};
21pub use error::{ParseError, SourceLocation};
22pub use statement::Statement;
23
24use crate::identifier::{Identifier, QualifiedName};
25use crate::ir::IrError;
26use crate::ir::aggregate::Aggregate;
27use crate::ir::cast::Cast;
28use crate::ir::catalog::Catalog;
29use crate::ir::event_trigger::EventTrigger;
30use crate::ir::publication::Publication;
31use crate::ir::statistic::Statistic;
32use crate::ir::subscription::Subscription;
33use crate::ir::text_search::{TsConfiguration, TsDictionary};
34
35pub fn parse_directory(root: &Path, ignores: &[glob::Pattern]) -> Result<Catalog, ParseError> {
45 parse_directory_with_locations(root, ignores).map(|(c, _)| c)
46}
47
48pub fn parse_directory_with_locations(
55 root: &Path,
56 ignores: &[glob::Pattern],
57) -> Result<(Catalog, HashMap<String, SourceLocation>), ParseError> {
58 let mut files: Vec<PathBuf> = Vec::new();
59 for entry in walkdir::WalkDir::new(root).sort_by_file_name() {
60 let entry = entry.map_err(|e| ParseError::Io {
61 path: e.path().map(Path::to_path_buf).unwrap_or_default(),
62 source: e
63 .into_io_error()
64 .unwrap_or_else(|| std::io::Error::other("walkdir error")),
65 })?;
66 if !entry.file_type().is_file() {
67 continue;
68 }
69 let path = entry.into_path();
70 if path.extension().and_then(|e| e.to_str()) != Some("sql") {
71 continue;
72 }
73 if ignores.iter().any(|pat| pat.matches_path(&path)) {
74 continue;
75 }
76 files.push(path);
77 }
78
79 let mut catalog = Catalog::default();
80 let mut locations: HashMap<String, SourceLocation> = HashMap::new();
81 let mut pending_fks: Vec<builder::alter_table_stmt::PendingFk> = Vec::new();
82 let mut pending_column_attrs: Vec<builder::alter_table_stmt::PendingColumnAttr> = Vec::new();
83 let mut pending_owners: Vec<builder::alter_table_stmt::PendingOwner> = Vec::new();
84 let mut pending_rls_toggles: Vec<builder::alter_table_stmt::PendingRlsToggle> = Vec::new();
85 let mut pending_rel_options: Vec<builder::alter_table_stmt::PendingRelOptions> = Vec::new();
86 let mut pending_tablespaces: Vec<builder::alter_table_stmt::PendingTablespace> = Vec::new();
87 let mut deferred_comments: Vec<(
88 pg_query::protobuf::CommentStmt,
89 SourceLocation,
90 Option<crate::identifier::Identifier>,
91 )> = Vec::new();
92 let mut publications: BTreeMap<Identifier, Publication> = BTreeMap::new();
96 let mut subscriptions: BTreeMap<Identifier, Subscription> = BTreeMap::new();
98 let mut statistics: BTreeMap<QualifiedName, Statistic> = BTreeMap::new();
100 let mut event_triggers: BTreeMap<Identifier, EventTrigger> = BTreeMap::new();
103 let mut aggregates: Vec<Aggregate> = Vec::new();
107 let mut casts: Vec<Cast> = Vec::new();
110 let mut ts_dictionaries: Vec<TsDictionary> = Vec::new();
114 let mut ts_configurations: Vec<TsConfiguration> = Vec::new();
117
118 for path in files {
119 let contents = std::fs::read_to_string(&path).map_err(|e| ParseError::Io {
120 path: path.clone(),
121 source: e,
122 })?;
123 process_file(
124 &path,
125 &contents,
126 &mut catalog,
127 &mut locations,
128 &mut pending_fks,
129 &mut pending_column_attrs,
130 &mut pending_owners,
131 &mut pending_rls_toggles,
132 &mut pending_rel_options,
133 &mut pending_tablespaces,
134 &mut deferred_comments,
135 &mut publications,
136 &mut subscriptions,
137 &mut statistics,
138 &mut event_triggers,
139 &mut aggregates,
140 &mut casts,
141 &mut ts_dictionaries,
142 &mut ts_configurations,
143 )?;
144 }
145
146 for (stmt, location, default_schema) in deferred_comments {
149 builder::comment_stmt::apply_comment(
150 &stmt,
151 &mut catalog,
152 default_schema.as_ref(),
153 &location,
154 )?;
155 }
156
157 apply_pending_fks(&mut catalog, pending_fks)?;
159 apply_pending_column_attrs(&mut catalog, pending_column_attrs)?;
160 apply_pending_owners(&mut catalog, pending_owners)?;
161 apply_pending_rls_toggles(&mut catalog, pending_rls_toggles)?;
162 apply_pending_rel_options(&mut catalog, pending_rel_options)?;
163 apply_pending_tablespaces(&mut catalog, pending_tablespaces)?;
164
165 catalog.publications = publications.into_values().collect();
167
168 catalog.subscriptions = subscriptions.into_values().collect();
170
171 catalog.statistics = statistics.into_values().collect();
173
174 catalog.event_triggers = event_triggers.into_values().collect();
176
177 catalog.aggregates = aggregates;
179
180 catalog.casts = casts;
182
183 catalog.ts_dictionaries = ts_dictionaries;
185 catalog.ts_configurations = ts_configurations;
186
187 ast_resolution::resolve(&catalog, &locations).map_err(ParseError::AstResolution)?;
190
191 if !catalog.views.is_empty() || !catalog.materialized_views.is_empty() {
195 ast_canon::canonicalize_view_bodies(&mut catalog).map_err(ParseError::AstCanon)?;
196 }
197
198 ast_canon::promote_mv_index_parents(&mut catalog);
204
205 let canonical = catalog
206 .canonicalize()
207 .map_err(|e: IrError| translate_canonicalize_error(e, &locations))?;
208 Ok((canonical, locations))
209}
210
211fn apply_statistics_comment(
217 stmt: &pg_query::protobuf::CommentStmt,
218 statistics: &mut BTreeMap<QualifiedName, Statistic>,
219 location: &SourceLocation,
220) -> Result<(), ParseError> {
221 use pg_query::NodeEnum;
222
223 let obj = stmt
225 .object
226 .as_ref()
227 .and_then(|o| o.node.as_ref())
228 .ok_or_else(|| ParseError::Structural {
229 location: location.clone(),
230 message: "COMMENT ON STATISTICS missing object reference".into(),
231 })?;
232 let NodeEnum::List(list) = obj else {
233 return Err(ParseError::Structural {
234 location: location.clone(),
235 message: format!(
236 "COMMENT ON STATISTICS expected a List node, got {:?}",
237 std::mem::discriminant(obj)
238 ),
239 });
240 };
241 let parts: Vec<String> = list
242 .items
243 .iter()
244 .filter_map(|n| {
245 if let Some(NodeEnum::String(s)) = n.node.as_ref() {
246 Some(s.sval.clone())
247 } else {
248 None
249 }
250 })
251 .collect();
252 let qname = match parts.as_slice() {
253 [schema, name] => QualifiedName::new(
254 builder::shared::ident(schema, location)?,
255 builder::shared::ident(name, location)?,
256 ),
257 [_name] => {
258 return Err(ParseError::UnqualifiedName {
259 location: location.clone(),
260 });
261 }
262 _ => {
263 return Err(ParseError::Structural {
264 location: location.clone(),
265 message: format!(
266 "COMMENT ON STATISTICS expected 1-2 qualified components, got {parts:?}"
267 ),
268 });
269 }
270 };
271
272 let comment = if stmt.comment.is_empty() {
273 None
274 } else {
275 Some(stmt.comment.clone())
276 };
277
278 let statistic = statistics.get_mut(&qname).ok_or_else(|| {
279 ParseError::CommentOnStatisticBeforeCreate(qname.clone(), location.clone())
280 })?;
281 statistic.comment = comment;
282 Ok(())
283}
284
285fn apply_pending_fks(
287 catalog: &mut Catalog,
288 pending_fks: Vec<builder::alter_table_stmt::PendingFk>,
289) -> Result<(), ParseError> {
290 for pending in pending_fks {
291 let table = catalog
292 .tables
293 .iter_mut()
294 .find(|t| t.qname == pending.target)
295 .ok_or_else(|| ParseError::Structural {
296 location: SourceLocation::new(PathBuf::new(), 0, 0),
297 message: format!("ALTER TABLE referenced unknown table {}", pending.target),
298 })?;
299 table.constraints.push(pending.constraint);
300 }
301 Ok(())
302}
303
304fn apply_pending_column_attrs(
306 catalog: &mut Catalog,
307 pending: Vec<builder::alter_table_stmt::PendingColumnAttr>,
308) -> Result<(), ParseError> {
309 use builder::alter_table_stmt::PendingColumnAttrKind;
310 for attr in pending {
311 let table = catalog
312 .tables
313 .iter_mut()
314 .find(|t| t.qname == attr.target)
315 .ok_or_else(|| ParseError::Structural {
316 location: SourceLocation::new(PathBuf::new(), 0, 0),
317 message: format!(
318 "ALTER TABLE ALTER COLUMN referenced unknown table {}",
319 attr.target
320 ),
321 })?;
322 let col = table
323 .columns
324 .iter_mut()
325 .find(|c| c.name == attr.column)
326 .ok_or_else(|| ParseError::Structural {
327 location: SourceLocation::new(PathBuf::new(), 0, 0),
328 message: format!(
329 "ALTER TABLE ALTER COLUMN referenced unknown column {}.{}",
330 attr.target, attr.column
331 ),
332 })?;
333 match attr.kind {
334 PendingColumnAttrKind::Storage(s) => {
335 col.storage = Some(s);
336 }
337 PendingColumnAttrKind::Compression(c) => {
338 col.compression = c;
339 }
340 }
341 }
342 Ok(())
343}
344
345fn apply_pending_rel_options(
350 catalog: &mut Catalog,
351 pending: Vec<builder::alter_table_stmt::PendingRelOptions>,
352) -> Result<(), ParseError> {
353 let loc = SourceLocation::new(PathBuf::new(), 0, 0);
354 builder::alter_table_stmt::apply_pending_rel_options(catalog, pending, &loc)
355}
356
357fn apply_pending_tablespaces(
361 catalog: &mut Catalog,
362 pending: Vec<builder::alter_table_stmt::PendingTablespace>,
363) -> Result<(), ParseError> {
364 let loc = SourceLocation::new(PathBuf::new(), 0, 0);
365 builder::alter_table_stmt::apply_pending_tablespaces(catalog, pending, &loc)
366}
367
368fn apply_pending_rls_toggles(
372 catalog: &mut Catalog,
373 pending: Vec<builder::alter_table_stmt::PendingRlsToggle>,
374) -> Result<(), ParseError> {
375 let loc = SourceLocation::new(PathBuf::new(), 0, 0);
376 builder::alter_table_stmt::apply_pending_rls_toggles(catalog, pending, &loc)
377}
378
379fn apply_pending_owners(
383 catalog: &mut Catalog,
384 pending: Vec<builder::alter_table_stmt::PendingOwner>,
385) -> Result<(), ParseError> {
386 let loc = SourceLocation::new(PathBuf::new(), 0, 0);
387 for po in pending {
388 builder::alter_table_stmt::apply_pending_owners(catalog, vec![po], &loc)?;
389 }
390 Ok(())
391}
392
393#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
394fn process_file(
395 path: &Path,
396 contents: &str,
397 catalog: &mut Catalog,
398 locations: &mut HashMap<String, SourceLocation>,
399 pending_fks: &mut Vec<builder::alter_table_stmt::PendingFk>,
400 pending_column_attrs: &mut Vec<builder::alter_table_stmt::PendingColumnAttr>,
401 pending_owners: &mut Vec<builder::alter_table_stmt::PendingOwner>,
402 pending_rls_toggles: &mut Vec<builder::alter_table_stmt::PendingRlsToggle>,
403 pending_rel_options: &mut Vec<builder::alter_table_stmt::PendingRelOptions>,
404 pending_tablespaces: &mut Vec<builder::alter_table_stmt::PendingTablespace>,
405 deferred_comments: &mut Vec<(
406 pg_query::protobuf::CommentStmt,
407 SourceLocation,
408 Option<crate::identifier::Identifier>,
409 )>,
410 publications: &mut BTreeMap<Identifier, Publication>,
411 subscriptions: &mut BTreeMap<Identifier, Subscription>,
412 statistics: &mut BTreeMap<QualifiedName, Statistic>,
413 event_triggers: &mut BTreeMap<Identifier, EventTrigger>,
414 aggregates: &mut Vec<Aggregate>,
415 casts: &mut Vec<Cast>,
416 ts_dictionaries: &mut Vec<TsDictionary>,
417 ts_configurations: &mut Vec<TsConfiguration>,
418) -> Result<(), ParseError> {
419 let directives = directives::extract_file_directives(contents, path)?;
420 let parsed = pg_query::parse(contents).map_err(|e| ParseError::PgQuery {
421 location: SourceLocation::new(path.to_path_buf(), 1, 1),
422 message: e.to_string(),
423 })?;
424
425 for raw in parsed.protobuf.stmts {
426 let location = stmt_location(path, contents, raw.stmt_location);
427 let Some(node) = raw.stmt.and_then(|n| n.node) else {
428 continue;
429 };
430 let stmt = Statement::classify(node, location.clone())?;
431 match stmt {
432 Statement::CreateSchema(s) => {
433 let schema = builder::create_schema_stmt::build_schema(&s, &location)?;
434 let schema_qname = QualifiedName::new(schema.name.clone(), schema.name.clone()); if let Some(prior) = locations.get(&schema.name.to_string()) {
436 return Err(ParseError::DuplicateObject {
437 qname: schema.name.to_string(),
438 first: prior.clone(),
439 second: location,
440 });
441 }
442 locations.insert(schema.name.to_string(), location.clone());
443 catalog.schemas.push(schema);
444 let _ = schema_qname;
445 }
446 Statement::CreateTable(s) => {
447 let mut table =
448 builder::create_stmt::build_table(&s, directives.schema.as_ref(), &location)?;
449 let serial_seqs =
450 builder::desugar_serial::desugar_serials_in_table(&mut table, &location)?;
451 if let Some(prior) = locations.get(&table.qname.to_string()) {
452 return Err(ParseError::DuplicateObject {
453 qname: table.qname.to_string(),
454 first: prior.clone(),
455 second: location,
456 });
457 }
458 locations.insert(table.qname.to_string(), location.clone());
459 catalog.tables.push(table);
460 for seq in serial_seqs {
461 if let Some(prior) = locations.get(&seq.qname.to_string()) {
462 return Err(ParseError::DuplicateObject {
463 qname: seq.qname.to_string(),
464 first: prior.clone(),
465 second: location.clone(),
466 });
467 }
468 locations.insert(seq.qname.to_string(), location.clone());
469 catalog.sequences.push(seq);
470 }
471 }
472 Statement::CreateSequence(s) => {
473 let seq = builder::create_seq_stmt::build_sequence(
474 &s,
475 directives.schema.as_ref(),
476 &location,
477 )?;
478 if let Some(prior) = locations.get(&seq.qname.to_string()) {
479 return Err(ParseError::DuplicateObject {
480 qname: seq.qname.to_string(),
481 first: prior.clone(),
482 second: location,
483 });
484 }
485 locations.insert(seq.qname.to_string(), location.clone());
486 catalog.sequences.push(seq);
487 }
488 Statement::CreateIndex(s) => {
489 let idx =
490 builder::index_stmt::build_index(&s, directives.schema.as_ref(), &location)?;
491 if let Some(prior) = locations.get(&idx.qname.to_string()) {
492 return Err(ParseError::DuplicateObject {
493 qname: idx.qname.to_string(),
494 first: prior.clone(),
495 second: location,
496 });
497 }
498 locations.insert(idx.qname.to_string(), location.clone());
499 catalog.indexes.push(idx);
500 }
501 Statement::AlterTable(s) => {
502 let alter_out = builder::alter_table_stmt::build_alter_table(
503 &s,
504 directives.schema.as_ref(),
505 &location,
506 )?;
507 pending_fks.extend(alter_out.pending_fks);
508 pending_column_attrs.extend(alter_out.pending_column_attrs);
509 pending_owners.extend(alter_out.pending_owners);
510 pending_rls_toggles.extend(alter_out.pending_rls_toggles);
511 pending_rel_options.extend(alter_out.pending_rel_options);
512 pending_tablespaces.extend(alter_out.pending_tablespaces);
513 }
514 Statement::Comment(s) => {
515 use pg_query::protobuf::ObjectType;
516 let kind = ObjectType::try_from(s.objtype).unwrap_or(ObjectType::Undefined);
517 if matches!(kind, ObjectType::ObjectStatisticExt) {
518 apply_statistics_comment(&s, statistics, &location)?;
522 } else if matches!(kind, ObjectType::ObjectEventTrigger) {
523 builder::event_trigger_stmt::apply_event_trigger_comment(
526 &s,
527 &location,
528 event_triggers,
529 )?;
530 } else if matches!(kind, ObjectType::ObjectAggregate) {
531 builder::aggregate_stmt::apply_comment(
534 &s,
535 directives.schema.as_ref(),
536 &location,
537 aggregates,
538 )?;
539 } else if matches!(kind, ObjectType::ObjectCast) {
540 builder::cast_stmt::apply_comment(
543 &s,
544 directives.schema.as_ref(),
545 &location,
546 casts,
547 )?;
548 } else if matches!(kind, ObjectType::ObjectTsdictionary) {
549 builder::text_search_stmt::apply_dictionary_comment(
552 &s,
553 directives.schema.as_ref(),
554 &location,
555 ts_dictionaries,
556 )?;
557 } else if matches!(kind, ObjectType::ObjectTsconfiguration) {
558 builder::text_search_stmt::apply_configuration_comment(
560 &s,
561 directives.schema.as_ref(),
562 &location,
563 ts_configurations,
564 )?;
565 } else {
566 deferred_comments.push((s, location, directives.schema.clone()));
567 }
568 }
569 Statement::CreateView(s) => {
570 let view = builder::create_view_stmt::build_view(
571 &s,
572 directives.schema.as_ref(),
573 &location,
574 )?;
575 if let Some(prior) = locations.get(&view.qname.to_string()) {
576 return Err(ParseError::DuplicateObject {
577 qname: view.qname.to_string(),
578 first: prior.clone(),
579 second: location,
580 });
581 }
582 locations.insert(view.qname.to_string(), location.clone());
583 catalog.views.push(view);
584 }
585 Statement::CreateMaterializedView(s) => {
586 let mv = builder::create_materialized_view_stmt::build_materialized_view(
587 &s,
588 directives.schema.as_ref(),
589 &location,
590 )?;
591 if let Some(prior) = locations.get(&mv.qname.to_string()) {
592 return Err(ParseError::DuplicateObject {
593 qname: mv.qname.to_string(),
594 first: prior.clone(),
595 second: location,
596 });
597 }
598 locations.insert(mv.qname.to_string(), location.clone());
599 catalog.materialized_views.push(mv);
600 }
601 Statement::CreateEnum(s) => {
602 let ut = builder::create_enum_stmt::build_enum(
603 &s,
604 directives.schema.as_ref(),
605 &location,
606 )?;
607 if let Some(prior) = locations.get(&ut.qname.to_string()) {
608 return Err(ParseError::DuplicateObject {
609 qname: ut.qname.to_string(),
610 first: prior.clone(),
611 second: location,
612 });
613 }
614 locations.insert(ut.qname.to_string(), location.clone());
615 catalog.types.push(ut);
616 }
617 Statement::CreateDomain(s) => {
618 let ut = builder::create_domain_stmt::build_domain(
619 &s,
620 directives.schema.as_ref(),
621 &location,
622 )?;
623 if let Some(prior) = locations.get(&ut.qname.to_string()) {
624 return Err(ParseError::DuplicateObject {
625 qname: ut.qname.to_string(),
626 first: prior.clone(),
627 second: location,
628 });
629 }
630 locations.insert(ut.qname.to_string(), location.clone());
631 catalog.types.push(ut);
632 }
633 Statement::CreateCompositeType(s) => {
634 let ut = builder::create_composite_type_stmt::build_composite(
635 &s,
636 directives.schema.as_ref(),
637 &location,
638 )?;
639 if let Some(prior) = locations.get(&ut.qname.to_string()) {
640 return Err(ParseError::DuplicateObject {
641 qname: ut.qname.to_string(),
642 first: prior.clone(),
643 second: location,
644 });
645 }
646 locations.insert(ut.qname.to_string(), location.clone());
647 catalog.types.push(ut);
648 }
649 Statement::CreateRange(s) => {
650 let ut = builder::create_range_stmt::build_range(
651 &s,
652 directives.schema.as_ref(),
653 &location,
654 )?;
655 if let Some(prior) = locations.get(&ut.qname.to_string()) {
656 return Err(ParseError::DuplicateObject {
657 qname: ut.qname.to_string(),
658 first: prior.clone(),
659 second: location,
660 });
661 }
662 locations.insert(ut.qname.to_string(), location.clone());
663 catalog.types.push(ut);
664 }
665 Statement::CreateFunction(s) => {
666 let routine = builder::create_function_stmt::build_function_or_procedure(
667 &s,
668 directives.schema.as_ref(),
669 &location,
670 )?;
671 let builder::create_function_stmt::Routine::Function(f) = routine else {
672 return Err(ParseError::Structural {
673 location,
674 message: "internal error: expected Function from non-procedure stmt".into(),
675 });
676 };
677 let arg_sig = f
678 .args
679 .iter()
680 .filter(|a| {
681 matches!(
682 a.mode,
683 crate::ir::function::ArgMode::In
684 | crate::ir::function::ArgMode::InOut
685 | crate::ir::function::ArgMode::Variadic
686 )
687 })
688 .map(|a| a.ty.render_sql())
689 .collect::<Vec<_>>()
690 .join(",");
691 let key = format!("functions.{}({arg_sig})", f.qname);
692 if let Some(prior) = locations.get(&key) {
693 return Err(ParseError::DuplicateObject {
694 qname: key,
695 first: prior.clone(),
696 second: location,
697 });
698 }
699 locations.insert(key, location.clone());
700 catalog.functions.push(f);
701 }
702 Statement::CreateProcedure(s) => {
703 let routine = builder::create_function_stmt::build_function_or_procedure(
704 &s,
705 directives.schema.as_ref(),
706 &location,
707 )?;
708 let builder::create_function_stmt::Routine::Procedure(p) = routine else {
709 return Err(ParseError::Structural {
710 location,
711 message: "internal error: expected Procedure from procedure stmt".into(),
712 });
713 };
714 let key = format!("procedures.{}", p.qname);
720 if let Some(prior) = locations.get(&key) {
721 return Err(ParseError::DuplicateObject {
722 qname: key,
723 first: prior.clone(),
724 second: location,
725 });
726 }
727 locations.insert(key, location.clone());
728 catalog.procedures.push(p);
729 }
730 Statement::CreateExtension(s) => {
731 let ext = builder::create_extension_stmt::build_extension(&s, &location)?;
732 let key = format!("extensions.{}", ext.name);
733 if let Some(prior) = locations.get(&key) {
734 return Err(ParseError::DuplicateObject {
735 qname: key,
736 first: prior.clone(),
737 second: location,
738 });
739 }
740 locations.insert(key, location.clone());
741 catalog.extensions.push(ext);
742 }
743 Statement::CreateTrigger(s) => {
744 let trigger = builder::create_trigger_stmt::build_trigger(&s, &location)?;
745 let key = format!("triggers.{}", trigger.qname);
746 if let Some(prior) = locations.get(&key) {
747 return Err(ParseError::DuplicateObject {
748 qname: key,
749 first: prior.clone(),
750 second: location,
751 });
752 }
753 locations.insert(key, location.clone());
754 catalog.triggers.push(trigger);
755 }
756 Statement::AlterTableAttachPartition(s) => {
757 let attach = builder::alter_table_attach_partition::build_attach_partition(
758 &s,
759 directives.schema.as_ref(),
760 &location,
761 )?;
762 let child_table = catalog
763 .tables
764 .iter_mut()
765 .find(|t| t.qname == attach.child)
766 .ok_or_else(|| ParseError::Structural {
767 location: location.clone(),
768 message: format!(
769 "ATTACH PARTITION {} must follow its CREATE TABLE statement",
770 attach.child
771 ),
772 })?;
773 if child_table.partition_of.is_some() {
774 return Err(ParseError::Structural {
775 location,
776 message: format!("table {} already declared as a partition", attach.child),
777 });
778 }
779 child_table.partition_of = Some(attach.partition_of);
780 }
781 Statement::Grant(s) => {
782 builder::grants::apply(&s, catalog, &location)?;
783 }
784 Statement::AlterOwner(s) => {
785 use pg_query::protobuf::ObjectType;
786 let objtype = ObjectType::try_from(s.object_type).unwrap_or(ObjectType::Undefined);
787 if matches!(objtype, ObjectType::ObjectEventTrigger) {
788 builder::event_trigger_stmt::apply_event_trigger_owner(
792 &s,
793 &location,
794 event_triggers,
795 )?;
796 } else if matches!(objtype, ObjectType::ObjectAggregate) {
797 builder::aggregate_stmt::apply_owner(
800 &s,
801 directives.schema.as_ref(),
802 &location,
803 aggregates,
804 )?;
805 } else if matches!(objtype, ObjectType::ObjectTsdictionary) {
806 builder::text_search_stmt::apply_dictionary_owner(
808 &s,
809 directives.schema.as_ref(),
810 &location,
811 ts_dictionaries,
812 )?;
813 } else if matches!(objtype, ObjectType::ObjectTsconfiguration) {
814 builder::text_search_stmt::apply_configuration_owner(
816 &s,
817 directives.schema.as_ref(),
818 &location,
819 ts_configurations,
820 )?;
821 } else {
822 builder::owner_stmt::apply(&s, catalog, &location)?;
823 }
824 }
825 Statement::AlterDefaultPrivileges(s) => {
826 builder::default_privileges::apply(&s, catalog, &location)?;
827 }
828 Statement::CreatePolicy(s) => {
829 builder::policy_stmt::apply(&s, catalog, &location)?;
830 }
831 Statement::CreatePublication(s) => {
832 builder::publication_stmt::parse_create_publication(&s, location, publications)?;
833 }
834 Statement::AlterPublication(s) => {
835 builder::publication_stmt::parse_alter_publication(&s, location, publications)?;
836 }
837 Statement::CreateSubscription(s) => {
838 builder::subscription_stmt::parse_create_subscription(&s, location, subscriptions)?;
839 }
840 Statement::AlterSubscription(s) => {
841 builder::subscription_stmt::parse_alter_subscription(&s, location, subscriptions)?;
842 }
843 Statement::CreateStatistics(s) => {
844 builder::statistic_stmt::parse_create_statistics(&s, location, statistics)?;
845 }
846 Statement::AlterStatistics(s) => {
847 builder::statistic_stmt::parse_alter_statistics(&s, &location, statistics)?;
848 }
849 Statement::CreateCollation(s) => {
850 let coll = builder::create_collation_stmt::build_collation(
851 &s,
852 directives.schema.as_ref(),
853 &location,
854 )?;
855 if let Some(prior) = locations.get(&coll.qname.to_string()) {
856 return Err(ParseError::DuplicateObject {
857 qname: coll.qname.to_string(),
858 first: prior.clone(),
859 second: location,
860 });
861 }
862 locations.insert(coll.qname.to_string(), location.clone());
863 catalog.collations.push(coll);
864 }
865 Statement::CreateAggregate(s) => {
866 builder::aggregate_stmt::parse_create(
867 &s,
868 directives.schema.as_ref(),
869 &location,
870 aggregates,
871 )?;
872 }
873 Statement::CreateEventTrigger(s) => {
874 builder::event_trigger_stmt::parse_create_event_trigger(
875 &s,
876 directives.schema.as_ref(),
877 location,
878 event_triggers,
879 )?;
880 }
881 Statement::AlterEventTrigger(s) => {
882 builder::event_trigger_stmt::parse_alter_event_trigger(
883 &s,
884 location,
885 event_triggers,
886 )?;
887 }
888 Statement::CreateCast(s) => {
889 builder::cast_stmt::parse_create(&s, directives.schema.as_ref(), &location, casts)?;
890 }
891 Statement::CreateTsDictionary(s) => {
892 builder::text_search_stmt::parse_create_dictionary(
893 &s,
894 directives.schema.as_ref(),
895 &location,
896 ts_dictionaries,
897 )?;
898 }
899 Statement::CreateTsConfiguration(s) => {
900 builder::text_search_stmt::parse_create_configuration(
901 &s,
902 directives.schema.as_ref(),
903 &location,
904 ts_configurations,
905 )?;
906 }
907 Statement::AlterTsDictionary(s) => {
908 builder::text_search_stmt::apply_alter_dictionary(
909 &s,
910 directives.schema.as_ref(),
911 &location,
912 ts_dictionaries,
913 )?;
914 }
915 Statement::AlterTsConfiguration(s) => {
916 builder::text_search_stmt::apply_alter_configuration(
917 &s,
918 directives.schema.as_ref(),
919 &location,
920 ts_configurations,
921 )?;
922 }
923 }
924 }
925 Ok(())
926}
927
928fn stmt_location(path: &Path, contents: &str, byte_offset: i32) -> SourceLocation {
930 let offset = usize::try_from(byte_offset).unwrap_or(0);
931 let mut line = 1usize;
932 let mut col = 1usize;
933 for (i, c) in contents.char_indices() {
934 if i >= offset {
935 break;
936 }
937 if c == '\n' {
938 line += 1;
939 col = 1;
940 } else {
941 col += 1;
942 }
943 }
944 SourceLocation::new(path.to_path_buf(), line, col)
945}
946
947fn translate_canonicalize_error(
948 e: IrError,
949 locations: &HashMap<String, SourceLocation>,
950) -> ParseError {
951 if let IrError::InvalidIdentifier(msg) = &e {
952 if let Some(rest) = msg.strip_prefix("duplicate ")
954 && let Some((_, qname)) = rest.split_once(": ")
955 && let Some(loc) = locations.get(qname)
956 {
957 return ParseError::DuplicateObject {
958 qname: qname.to_string(),
959 first: loc.clone(),
960 second: loc.clone(),
961 };
962 }
963 }
964 let placeholder = SourceLocation::new(PathBuf::new(), 0, 0);
965 ParseError::Ir {
966 location: placeholder,
967 source: e,
968 }
969}
970
971#[cfg(test)]
973pub(crate) fn smoke_parse(sql: &str) -> Result<pg_query::ParseResult, pg_query::Error> {
974 pg_query::parse(sql)
975}
976
977#[cfg(test)]
978mod tests {
979 use super::*;
980
981 #[test]
982 fn pg_query_round_trips_a_create_table() {
983 let sql = "CREATE TABLE app.users (id integer);";
984 let result = smoke_parse(sql).expect("pg_query parses");
985 assert!(!result.protobuf.stmts.is_empty());
987 }
988
989 #[test]
990 fn pg_query_reports_syntax_errors() {
991 let sql = "CREATE TABLE !bad!;";
992 assert!(smoke_parse(sql).is_err());
993 }
994}