Skip to main content

pgrx_sql_entity_graph/
pgrx_sql.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10/*!
11
12Rust to SQL mapping support.
13
14> Like all of the [`sql_entity_graph`][crate] APIs, this is considered **internal**
15> to the `pgrx` framework and very subject to change between versions. While you may use this, please do it with caution.
16
17*/
18
19use eyre::eyre;
20use petgraph::dot::Dot;
21use petgraph::graph::NodeIndex;
22use petgraph::stable_graph::StableGraph;
23use std::any::TypeId;
24use std::collections::HashMap;
25use std::fmt::Debug;
26use std::path::Path;
27
28use crate::aggregate::entity::PgAggregateEntity;
29use crate::control_file::ControlFile;
30use crate::extension_sql::SqlDeclared;
31use crate::extension_sql::entity::{ExtensionSqlEntity, SqlDeclaredEntity};
32use crate::pg_extern::entity::PgExternEntity;
33use crate::pg_trigger::entity::PgTriggerEntity;
34use crate::positioning_ref::PositioningRef;
35use crate::postgres_enum::entity::PostgresEnumEntity;
36use crate::postgres_hash::entity::PostgresHashEntity;
37use crate::postgres_ord::entity::PostgresOrdEntity;
38use crate::postgres_type::entity::PostgresTypeEntity;
39use crate::schema::entity::SchemaEntity;
40use crate::to_sql::ToSql;
41use crate::type_keyed;
42use crate::{SqlGraphEntity, SqlGraphIdentifier, TypeMatch};
43
44use super::{PgExternReturnEntity, PgExternReturnEntityIteratedItem};
45
46#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
47pub enum SqlGraphRequires {
48    By,
49    ByArg,
50    ByReturn,
51}
52
53/// A generator for SQL.
54///
55/// Consumes a base mapping of types (typically `pgrx::DEFAULT_TYPEID_SQL_MAPPING`), a
56/// [`ControlFile`], and collections of each SQL entity.
57///
58/// During construction, a Directed Acyclic Graph is formed out the dependencies. For example,
59/// an item `detect_dog(x: &[u8]) -> animals::Dog` would have have a relationship with
60/// `animals::Dog`.
61///
62/// Typically, [`PgrxSql`] types are constructed in a `pgrx::pg_binary_magic!()` call in a binary
63/// out of entities collected during a `pgrx::pg_module_magic!()` call in a library.
64#[derive(Debug, Clone)]
65pub struct PgrxSql {
66    pub control: ControlFile,
67    pub graph: StableGraph<SqlGraphEntity, SqlGraphRequires>,
68    pub graph_root: NodeIndex,
69    pub graph_bootstrap: Option<NodeIndex>,
70    pub graph_finalize: Option<NodeIndex>,
71    pub schemas: HashMap<SchemaEntity, NodeIndex>,
72    pub extension_sqls: HashMap<ExtensionSqlEntity, NodeIndex>,
73    pub externs: HashMap<PgExternEntity, NodeIndex>,
74    pub types: HashMap<PostgresTypeEntity, NodeIndex>,
75    pub builtin_types: HashMap<String, NodeIndex>,
76    pub enums: HashMap<PostgresEnumEntity, NodeIndex>,
77    pub ords: HashMap<PostgresOrdEntity, NodeIndex>,
78    pub hashes: HashMap<PostgresHashEntity, NodeIndex>,
79    pub aggregates: HashMap<PgAggregateEntity, NodeIndex>,
80    pub triggers: HashMap<PgTriggerEntity, NodeIndex>,
81    pub extension_name: String,
82    pub versioned_so: bool,
83}
84
85impl PgrxSql {
86    pub fn build(
87        entities: impl Iterator<Item = SqlGraphEntity>,
88        extension_name: String,
89        versioned_so: bool,
90    ) -> eyre::Result<Self> {
91        let mut graph = StableGraph::new();
92
93        let mut entities = entities.collect::<Vec<_>>();
94        entities.sort();
95        // Split up things into their specific types:
96        let mut control: Option<ControlFile> = None;
97        let mut schemas: Vec<SchemaEntity> = Vec::default();
98        let mut extension_sqls: Vec<ExtensionSqlEntity> = Vec::default();
99        let mut externs: Vec<PgExternEntity> = Vec::default();
100        let mut types: Vec<PostgresTypeEntity> = Vec::default();
101        let mut enums: Vec<PostgresEnumEntity> = Vec::default();
102        let mut ords: Vec<PostgresOrdEntity> = Vec::default();
103        let mut hashes: Vec<PostgresHashEntity> = Vec::default();
104        let mut aggregates: Vec<PgAggregateEntity> = Vec::default();
105        let mut triggers: Vec<PgTriggerEntity> = Vec::default();
106        for entity in entities {
107            match entity {
108                SqlGraphEntity::ExtensionRoot(input_control) => {
109                    control = Some(input_control);
110                }
111                SqlGraphEntity::Schema(input_schema) => {
112                    schemas.push(input_schema);
113                }
114                SqlGraphEntity::CustomSql(input_sql) => {
115                    extension_sqls.push(input_sql);
116                }
117                SqlGraphEntity::Function(input_function) => {
118                    externs.push(input_function);
119                }
120                SqlGraphEntity::Type(input_type) => {
121                    types.push(input_type);
122                }
123                SqlGraphEntity::BuiltinType(_) => (),
124                SqlGraphEntity::Enum(input_enum) => {
125                    enums.push(input_enum);
126                }
127                SqlGraphEntity::Ord(input_ord) => {
128                    ords.push(input_ord);
129                }
130                SqlGraphEntity::Hash(input_hash) => {
131                    hashes.push(input_hash);
132                }
133                SqlGraphEntity::Aggregate(input_aggregate) => {
134                    aggregates.push(input_aggregate);
135                }
136                SqlGraphEntity::Trigger(input_trigger) => {
137                    triggers.push(input_trigger);
138                }
139            }
140        }
141
142        let control: ControlFile = control.expect("No control file found");
143        let root = graph.add_node(SqlGraphEntity::ExtensionRoot(control.clone()));
144
145        // The initial build phase.
146        //
147        // Notably, we do not set non-root edges here. We do that in a second step. This is
148        // primarily because externs, types, operators, and the like tend to intertwine. If we tried
149        // to do it here, we'd find ourselves trying to create edges to non-existing entities.
150
151        // Both of these must be unique, so we can only hold one.
152        // Populate nodes, but don't build edges until we know if there is a bootstrap/finalize.
153        let (mapped_extension_sqls, bootstrap, finalize) =
154            initialize_extension_sqls(&mut graph, root, extension_sqls)?;
155        let mapped_schemas = initialize_schemas(&mut graph, bootstrap, finalize, schemas)?;
156        let mapped_enums = initialize_enums(&mut graph, root, bootstrap, finalize, enums)?;
157        let mapped_types = initialize_types(&mut graph, root, bootstrap, finalize, types)?;
158        let (mapped_externs, mut mapped_builtin_types) = initialize_externs(
159            &mut graph,
160            root,
161            bootstrap,
162            finalize,
163            externs,
164            &mapped_types,
165            &mapped_enums,
166        )?;
167        let mapped_ords = initialize_ords(&mut graph, root, bootstrap, finalize, ords)?;
168        let mapped_hashes = initialize_hashes(&mut graph, root, bootstrap, finalize, hashes)?;
169        let mapped_aggregates = initialize_aggregates(
170            &mut graph,
171            root,
172            bootstrap,
173            finalize,
174            aggregates,
175            &mut mapped_builtin_types,
176            &mapped_enums,
177            &mapped_types,
178        )?;
179        let mapped_triggers = initialize_triggers(&mut graph, root, bootstrap, finalize, triggers)?;
180
181        // Now we can circle back and build up the edge sets.
182        connect_schemas(&mut graph, &mapped_schemas, root);
183        connect_extension_sqls(
184            &mut graph,
185            &mapped_extension_sqls,
186            &mapped_schemas,
187            &mapped_types,
188            &mapped_enums,
189            &mapped_externs,
190            &mapped_triggers,
191        )?;
192        connect_enums(&mut graph, &mapped_enums, &mapped_schemas);
193        connect_types(&mut graph, &mapped_types, &mapped_schemas);
194        connect_externs(
195            &mut graph,
196            &mapped_externs,
197            &mapped_hashes,
198            &mapped_schemas,
199            &mapped_types,
200            &mapped_enums,
201            &mapped_builtin_types,
202            &mapped_extension_sqls,
203            &mapped_triggers,
204        )?;
205        connect_ords(
206            &mut graph,
207            &mapped_ords,
208            &mapped_schemas,
209            &mapped_types,
210            &mapped_enums,
211            &mapped_externs,
212        );
213        connect_hashes(
214            &mut graph,
215            &mapped_hashes,
216            &mapped_schemas,
217            &mapped_types,
218            &mapped_enums,
219            &mapped_externs,
220        );
221        connect_aggregates(
222            &mut graph,
223            &mapped_aggregates,
224            &mapped_schemas,
225            &mapped_types,
226            &mapped_enums,
227            &mapped_builtin_types,
228            &mapped_externs,
229        )?;
230        connect_triggers(&mut graph, &mapped_triggers, &mapped_schemas);
231
232        let this = Self {
233            control,
234            schemas: mapped_schemas,
235            extension_sqls: mapped_extension_sqls,
236            externs: mapped_externs,
237            types: mapped_types,
238            builtin_types: mapped_builtin_types,
239            enums: mapped_enums,
240            ords: mapped_ords,
241            hashes: mapped_hashes,
242            aggregates: mapped_aggregates,
243            triggers: mapped_triggers,
244            graph,
245            graph_root: root,
246            graph_bootstrap: bootstrap,
247            graph_finalize: finalize,
248            extension_name,
249            versioned_so,
250        };
251        Ok(this)
252    }
253
254    // NOTE: this signature is demanded by the codegen we embed via cargo-pgrx
255    pub fn to_file(&self, file: impl AsRef<Path> + Debug) -> eyre::Result<()> {
256        use std::fs::{File, create_dir_all};
257        use std::io::Write;
258        let generated = self.to_sql()?;
259        let path = Path::new(file.as_ref());
260
261        let parent = path.parent();
262        if let Some(parent) = parent {
263            create_dir_all(parent)?;
264        }
265        let mut out = File::create(path)?;
266        write!(out, "{generated}")?;
267        Ok(())
268    }
269
270    pub fn write(&self, out: &mut impl std::io::Write) -> eyre::Result<()> {
271        let generated = self.to_sql()?;
272
273        #[cfg(feature = "syntax-highlighting")]
274        {
275            use std::io::{IsTerminal, stdout};
276            if stdout().is_terminal() {
277                self.write_highlighted(out, &generated)?;
278            } else {
279                write!(*out, "{}", generated)?;
280            }
281        }
282
283        #[cfg(not(feature = "syntax-highlighting"))]
284        {
285            write!(*out, "{generated}")?;
286        }
287
288        Ok(())
289    }
290
291    #[cfg(feature = "syntax-highlighting")]
292    fn write_highlighted(&self, out: &mut dyn std::io::Write, generated: &str) -> eyre::Result<()> {
293        use eyre::WrapErr as _;
294        use owo_colors::{OwoColorize, XtermColors};
295        use syntect::easy::HighlightLines;
296        use syntect::highlighting::{Style, ThemeSet};
297        use syntect::parsing::SyntaxSet;
298        use syntect::util::LinesWithEndings;
299        let ps = SyntaxSet::load_defaults_newlines();
300        let theme_bytes = include_str!("../assets/ansi.tmTheme").as_bytes();
301        let mut theme_reader = std::io::Cursor::new(theme_bytes);
302        let theme = ThemeSet::load_from_reader(&mut theme_reader)
303            .wrap_err("Couldn't parse theme for SQL highlighting, try piping to a file")?;
304
305        if let Some(syntax) = ps.find_syntax_by_extension("sql") {
306            let mut h = HighlightLines::new(syntax, &theme);
307            for line in LinesWithEndings::from(&generated) {
308                let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ps)?;
309                // Concept from https://github.com/sharkdp/bat/blob/1b030dc03b906aa345f44b8266bffeea77d763fe/src/terminal.rs#L6
310                for (style, content) in ranges {
311                    if style.foreground.a == 0x01 {
312                        write!(*out, "{}", content)?;
313                    } else {
314                        write!(*out, "{}", content.color(XtermColors::from(style.foreground.r)))?;
315                    }
316                }
317                write!(*out, "\x1b[0m")?;
318            }
319        } else {
320            write!(*out, "{}", generated)?;
321        }
322        Ok(())
323    }
324
325    // NOTE: this signature is demanded by the codegen we embed via cargo-pgrx
326    pub fn to_dot(&self, file: impl AsRef<Path> + Debug) -> eyre::Result<()> {
327        use std::fs::{File, create_dir_all};
328        use std::io::Write;
329        let generated = Dot::with_attr_getters(
330            &self.graph,
331            &[petgraph::dot::Config::EdgeNoLabel, petgraph::dot::Config::NodeNoLabel],
332            &|_graph, edge| {
333                match edge.weight() {
334                    SqlGraphRequires::By => r#"color = "gray""#,
335                    SqlGraphRequires::ByArg => r#"color = "black""#,
336                    SqlGraphRequires::ByReturn => r#"dir = "back", color = "black""#,
337                }
338                .to_owned()
339            },
340            &|_graph, (_index, node)| {
341                let dot_id = node.dot_identifier();
342                match node {
343                    // Colors derived from https://www.schemecolor.com/touch-of-creativity.php
344                    SqlGraphEntity::Schema(_item) => {
345                        format!("label = \"{dot_id}\", weight = 6, shape = \"tab\"")
346                    }
347                    SqlGraphEntity::Function(_item) => format!(
348                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#ADC7C6\", weight = 4, shape = \"box\"",
349                    ),
350                    SqlGraphEntity::Type(_item) => format!(
351                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#AE9BBD\", weight = 5, shape = \"oval\"",
352                    ),
353                    SqlGraphEntity::BuiltinType(_item) => {
354                        format!("label = \"{dot_id}\", shape = \"plain\"")
355                    }
356                    SqlGraphEntity::Enum(_item) => format!(
357                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#C9A7C8\", weight = 5, shape = \"oval\""
358                    ),
359                    SqlGraphEntity::Ord(_item) => format!(
360                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#FFCFD3\", weight = 5, shape = \"diamond\""
361                    ),
362                    SqlGraphEntity::Hash(_item) => format!(
363                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#FFE4E0\", weight = 5, shape = \"diamond\""
364                    ),
365                    SqlGraphEntity::Aggregate(_item) => format!(
366                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#FFE4E0\", weight = 5, shape = \"diamond\""
367                    ),
368                    SqlGraphEntity::Trigger(_item) => format!(
369                        "label = \"{dot_id}\", penwidth = 0, style = \"filled\", fillcolor = \"#FFE4E0\", weight = 5, shape = \"diamond\""
370                    ),
371                    SqlGraphEntity::CustomSql(_item) => {
372                        format!("label = \"{dot_id}\", weight = 3, shape = \"signature\"")
373                    }
374                    SqlGraphEntity::ExtensionRoot(_item) => {
375                        format!("label = \"{dot_id}\", shape = \"cylinder\"")
376                    }
377                }
378            },
379        );
380        let path = Path::new(file.as_ref());
381
382        let parent = path.parent();
383        if let Some(parent) = parent {
384            create_dir_all(parent)?;
385        }
386        let mut out = File::create(path)?;
387        write!(out, "{generated:?}")?;
388        Ok(())
389    }
390
391    pub fn schema_alias_of(&self, item_index: &NodeIndex) -> Option<String> {
392        self.graph
393            .neighbors_undirected(*item_index)
394            .flat_map(|neighbor_index| match &self.graph[neighbor_index] {
395                SqlGraphEntity::Schema(s) => Some(String::from(s.name)),
396                SqlGraphEntity::ExtensionRoot(_control) => None,
397                _ => None,
398            })
399            .next()
400    }
401
402    pub fn schema_prefix_for(&self, target: &NodeIndex) -> String {
403        self.schema_alias_of(target).map(|v| (v + ".").to_string()).unwrap_or_default()
404    }
405
406    pub fn to_sql(&self) -> eyre::Result<String> {
407        let mut full_sql = String::new();
408
409        // NB:  A properly we'd *like* to maintain is that the schema generator outputs
410        // consistent results from run-to-run when there are no changes to the schema.
411        // This is to improve change detection using simple tools like `diff`.
412        //
413        // Historically, we used [`petgraph::algo:toposort`] but its ordering is not at all
414        // consistent.
415        //
416        // [`petgraph::algo::tarjan_scc`] appears to be consistent, although it's not exactly
417        // clear if this is due to an implementation detail or specifics of the algorithm itself.
418        // (I, eeeebbbbrrrr, am not a graph theory expert)
419        //
420        // In any event, if in the future schema generation stops being consistent, this is the
421        // place to look.
422        //
423        // We have no tests around this as it's really just a property we'd like to have, and
424        // it does seem ensuring it is a bit of black magic.
425        for nodes in petgraph::algo::tarjan_scc(&self.graph).iter().rev() {
426            let mut inner_sql = Vec::with_capacity(nodes.len());
427
428            for node in nodes {
429                let step = &self.graph[*node];
430                let sql = step.to_sql(self)?;
431
432                let trimmed = sql.trim();
433                if !trimmed.is_empty() {
434                    inner_sql.push(format!("{trimmed}\n"))
435                }
436            }
437
438            if !inner_sql.is_empty() {
439                full_sql.push_str("/* <begin connected objects> */\n");
440                full_sql.push_str(&inner_sql.join("\n\n"));
441                full_sql.push_str("/* </end connected objects> */\n\n");
442            }
443        }
444
445        Ok(full_sql)
446    }
447
448    pub fn has_sql_declared_entity(&self, identifier: &SqlDeclared) -> Option<&SqlDeclaredEntity> {
449        self.extension_sqls.iter().find_map(|(item, _index)| {
450            item.creates
451                .iter()
452                .find(|create_entity| create_entity.has_sql_declared_entity(identifier))
453        })
454    }
455
456    pub fn get_module_pathname(&self) -> String {
457        if self.versioned_so {
458            let extname = &self.extension_name;
459            let extver = &self.control.default_version;
460            // Note: versioned so-name format must agree with cargo pgrx
461            format!("{extname}-{extver}")
462        } else {
463            String::from("MODULE_PATHNAME")
464        }
465    }
466
467    pub fn find_matching_fn(&self, name: &str) -> Option<&PgExternEntity> {
468        self.externs.keys().find(|key| key.full_path.ends_with(name))
469    }
470}
471
472fn build_base_edges(
473    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
474    index: NodeIndex,
475    root: NodeIndex,
476    bootstrap: Option<NodeIndex>,
477    finalize: Option<NodeIndex>,
478) {
479    graph.add_edge(root, index, SqlGraphRequires::By);
480    if let Some(bootstrap) = bootstrap {
481        graph.add_edge(bootstrap, index, SqlGraphRequires::By);
482    }
483    if let Some(finalize) = finalize {
484        graph.add_edge(index, finalize, SqlGraphRequires::By);
485    }
486}
487
488#[allow(clippy::type_complexity)]
489fn initialize_extension_sqls(
490    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
491    root: NodeIndex,
492    extension_sqls: Vec<ExtensionSqlEntity>,
493) -> eyre::Result<(HashMap<ExtensionSqlEntity, NodeIndex>, Option<NodeIndex>, Option<NodeIndex>)> {
494    let mut bootstrap = None;
495    let mut finalize = None;
496    let mut mapped_extension_sqls = HashMap::default();
497    for item in extension_sqls {
498        let entity: SqlGraphEntity = item.clone().into();
499        let index = graph.add_node(entity);
500        mapped_extension_sqls.insert(item.clone(), index);
501
502        if item.bootstrap {
503            if let Some(existing_index) = bootstrap {
504                let existing: &SqlGraphEntity = &graph[existing_index];
505                return Err(eyre!(
506                    "Cannot have multiple `extension_sql!()` with `bootstrap` positioning, found `{}`, other was `{}`",
507                    item.rust_identifier(),
508                    existing.rust_identifier(),
509                ));
510            }
511            bootstrap = Some(index)
512        }
513        if item.finalize {
514            if let Some(existing_index) = finalize {
515                let existing: &SqlGraphEntity = &graph[existing_index];
516                return Err(eyre!(
517                    "Cannot have multiple `extension_sql!()` with `finalize` positioning, found `{}`, other was `{}`",
518                    item.rust_identifier(),
519                    existing.rust_identifier(),
520                ));
521            }
522            finalize = Some(index)
523        }
524    }
525    for (item, index) in &mapped_extension_sqls {
526        graph.add_edge(root, *index, SqlGraphRequires::By);
527        if !item.bootstrap
528            && let Some(bootstrap) = bootstrap
529        {
530            graph.add_edge(bootstrap, *index, SqlGraphRequires::By);
531        }
532        if !item.finalize
533            && let Some(finalize) = finalize
534        {
535            graph.add_edge(*index, finalize, SqlGraphRequires::By);
536        }
537    }
538    Ok((mapped_extension_sqls, bootstrap, finalize))
539}
540
541/// A best effort attempt to find the related [`NodeIndex`] for some [`PositioningRef`].
542pub fn find_positioning_ref_target<'a>(
543    positioning_ref: &'a PositioningRef,
544    types: &'a HashMap<PostgresTypeEntity, NodeIndex>,
545    enums: &'a HashMap<PostgresEnumEntity, NodeIndex>,
546    externs: &'a HashMap<PgExternEntity, NodeIndex>,
547    schemas: &'a HashMap<SchemaEntity, NodeIndex>,
548    extension_sqls: &'a HashMap<ExtensionSqlEntity, NodeIndex>,
549    triggers: &'a HashMap<PgTriggerEntity, NodeIndex>,
550) -> Option<&'a NodeIndex> {
551    match positioning_ref {
552        PositioningRef::FullPath(path) => {
553            // The best we can do here is a fuzzy search.
554            let segments = path.split("::").collect::<Vec<_>>();
555            let last_segment = segments.last().expect("Expected at least one segment.");
556            let rest = &segments[..segments.len() - 1];
557            let module_path = rest.join("::");
558
559            for (other, other_index) in types {
560                if *last_segment == other.name && other.module_path.ends_with(&module_path) {
561                    return Some(other_index);
562                }
563            }
564            for (other, other_index) in enums {
565                if last_segment == &other.name && other.module_path.ends_with(&module_path) {
566                    return Some(other_index);
567                }
568            }
569            for (other, other_index) in externs {
570                if *last_segment == other.unaliased_name
571                    && other.module_path.ends_with(&module_path)
572                {
573                    return Some(other_index);
574                }
575            }
576            for (other, other_index) in schemas {
577                if other.module_path.ends_with(path) {
578                    return Some(other_index);
579                }
580            }
581
582            for (other, other_index) in triggers {
583                if last_segment == &other.function_name && other.module_path.ends_with(&module_path)
584                {
585                    return Some(other_index);
586                }
587            }
588        }
589        PositioningRef::Name(name) => {
590            for (other, other_index) in extension_sqls {
591                if other.name == name {
592                    return Some(other_index);
593                }
594            }
595        }
596    };
597    None
598}
599
600fn connect_extension_sqls(
601    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
602    extension_sqls: &HashMap<ExtensionSqlEntity, NodeIndex>,
603    schemas: &HashMap<SchemaEntity, NodeIndex>,
604    types: &HashMap<PostgresTypeEntity, NodeIndex>,
605    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
606    externs: &HashMap<PgExternEntity, NodeIndex>,
607    triggers: &HashMap<PgTriggerEntity, NodeIndex>,
608) -> eyre::Result<()> {
609    for (item, &index) in extension_sqls {
610        make_schema_connection(
611            graph,
612            "Extension SQL",
613            index,
614            &item.rust_identifier(),
615            item.module_path,
616            schemas,
617        );
618
619        for requires in &item.requires {
620            if let Some(target) = find_positioning_ref_target(
621                requires,
622                types,
623                enums,
624                externs,
625                schemas,
626                extension_sqls,
627                triggers,
628            ) {
629                graph.add_edge(*target, index, SqlGraphRequires::By);
630            } else {
631                return Err(eyre!(
632                    "Could not find `requires` target of `{}`{}: {}",
633                    item.rust_identifier(),
634                    match (item.file(), item.line()) {
635                        (Some(file), Some(line)) => format!(" ({file}:{line})"),
636                        _ => "".to_string(),
637                    },
638                    match requires {
639                        PositioningRef::FullPath(path) => path.to_string(),
640                        PositioningRef::Name(name) => format!(r#""{name}""#),
641                    },
642                ));
643            }
644        }
645    }
646    Ok(())
647}
648
649fn initialize_schemas(
650    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
651    bootstrap: Option<NodeIndex>,
652    finalize: Option<NodeIndex>,
653    schemas: Vec<SchemaEntity>,
654) -> eyre::Result<HashMap<SchemaEntity, NodeIndex>> {
655    let mut mapped_schemas = HashMap::default();
656    for item in schemas {
657        let entity = item.clone().into();
658        let index = graph.add_node(entity);
659        mapped_schemas.insert(item, index);
660        if let Some(bootstrap) = bootstrap {
661            graph.add_edge(bootstrap, index, SqlGraphRequires::By);
662        }
663        if let Some(finalize) = finalize {
664            graph.add_edge(index, finalize, SqlGraphRequires::By);
665        }
666    }
667    Ok(mapped_schemas)
668}
669
670fn connect_schemas(
671    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
672    schemas: &HashMap<SchemaEntity, NodeIndex>,
673    root: NodeIndex,
674) {
675    for index in schemas.values().copied() {
676        graph.add_edge(root, index, SqlGraphRequires::By);
677    }
678}
679
680fn initialize_enums(
681    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
682    root: NodeIndex,
683    bootstrap: Option<NodeIndex>,
684    finalize: Option<NodeIndex>,
685    enums: Vec<PostgresEnumEntity>,
686) -> eyre::Result<HashMap<PostgresEnumEntity, NodeIndex>> {
687    let mut mapped_enums = HashMap::default();
688    for item in enums {
689        let entity: SqlGraphEntity = item.clone().into();
690        let index = graph.add_node(entity);
691        mapped_enums.insert(item, index);
692        build_base_edges(graph, index, root, bootstrap, finalize);
693    }
694    Ok(mapped_enums)
695}
696
697fn connect_enums(
698    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
699    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
700    schemas: &HashMap<SchemaEntity, NodeIndex>,
701) {
702    for (item, &index) in enums {
703        make_schema_connection(
704            graph,
705            "Enum",
706            index,
707            &item.rust_identifier(),
708            item.module_path,
709            schemas,
710        );
711    }
712}
713
714fn initialize_types(
715    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
716    root: NodeIndex,
717    bootstrap: Option<NodeIndex>,
718    finalize: Option<NodeIndex>,
719    types: Vec<PostgresTypeEntity>,
720) -> eyre::Result<HashMap<PostgresTypeEntity, NodeIndex>> {
721    let mut mapped_types = HashMap::default();
722    for item in types {
723        let entity = item.clone().into();
724        let index = graph.add_node(entity);
725        mapped_types.insert(item, index);
726        build_base_edges(graph, index, root, bootstrap, finalize);
727    }
728    Ok(mapped_types)
729}
730
731fn connect_types(
732    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
733    types: &HashMap<PostgresTypeEntity, NodeIndex>,
734    schemas: &HashMap<SchemaEntity, NodeIndex>,
735) {
736    for (item, &index) in types {
737        make_schema_connection(
738            graph,
739            "Type",
740            index,
741            &item.rust_identifier(),
742            item.module_path,
743            schemas,
744        );
745    }
746}
747
748fn initialize_externs(
749    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
750    root: NodeIndex,
751    bootstrap: Option<NodeIndex>,
752    finalize: Option<NodeIndex>,
753    externs: Vec<PgExternEntity>,
754    mapped_types: &HashMap<PostgresTypeEntity, NodeIndex>,
755    mapped_enums: &HashMap<PostgresEnumEntity, NodeIndex>,
756) -> eyre::Result<(HashMap<PgExternEntity, NodeIndex>, HashMap<String, NodeIndex>)> {
757    let mut mapped_externs = HashMap::default();
758    let mut mapped_builtin_types = HashMap::default();
759    for item in externs {
760        let entity: SqlGraphEntity = item.clone().into();
761        let index = graph.add_node(entity.clone());
762        mapped_externs.insert(item.clone(), index);
763        build_base_edges(graph, index, root, bootstrap, finalize);
764
765        for arg in &item.fn_args {
766            let found = mapped_types.keys().any(|ty_item| ty_item.id_matches(&arg.used_ty.ty_id))
767                || mapped_enums.keys().any(|ty_item| ty_item.id_matches(&arg.used_ty.ty_id));
768
769            if !found {
770                mapped_builtin_types.entry(arg.used_ty.full_path.to_string()).or_insert_with(
771                    || {
772                        graph.add_node(SqlGraphEntity::BuiltinType(
773                            arg.used_ty.full_path.to_string(),
774                        ))
775                    },
776                );
777            }
778        }
779
780        match &item.fn_return {
781            PgExternReturnEntity::None | PgExternReturnEntity::Trigger => (),
782            PgExternReturnEntity::Type { ty, .. } | PgExternReturnEntity::SetOf { ty, .. } => {
783                let found = mapped_types.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id))
784                    || mapped_enums.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id));
785
786                if !found {
787                    mapped_builtin_types.entry(ty.full_path.to_string()).or_insert_with(|| {
788                        graph.add_node(SqlGraphEntity::BuiltinType(ty.full_path.to_string()))
789                    });
790                }
791            }
792            PgExternReturnEntity::Iterated { tys: iterated_returns, .. } => {
793                for PgExternReturnEntityIteratedItem { ty, .. } in iterated_returns {
794                    let found = mapped_types.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id))
795                        || mapped_enums.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id));
796
797                    if !found {
798                        mapped_builtin_types.entry(ty.ty_source.to_string()).or_insert_with(|| {
799                            graph.add_node(SqlGraphEntity::BuiltinType(ty.ty_source.to_string()))
800                        });
801                    }
802                }
803            }
804        }
805    }
806    Ok((mapped_externs, mapped_builtin_types))
807}
808
809fn connect_externs(
810    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
811    externs: &HashMap<PgExternEntity, NodeIndex>,
812    hashes: &HashMap<PostgresHashEntity, NodeIndex>,
813    schemas: &HashMap<SchemaEntity, NodeIndex>,
814    types: &HashMap<PostgresTypeEntity, NodeIndex>,
815    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
816    builtin_types: &HashMap<String, NodeIndex>,
817    extension_sqls: &HashMap<ExtensionSqlEntity, NodeIndex>,
818    triggers: &HashMap<PgTriggerEntity, NodeIndex>,
819) -> eyre::Result<()> {
820    for (item, &index) in externs {
821        let mut found_schema_declaration = false;
822        let mut has_explicit_requires = false;
823        for extern_attr in &item.extern_attrs {
824            match extern_attr {
825                crate::ExternArgs::Requires(requirements) => {
826                    for requires in requirements {
827                        if let Some(target) = find_positioning_ref_target(
828                            requires,
829                            types,
830                            enums,
831                            externs,
832                            schemas,
833                            extension_sqls,
834                            triggers,
835                        ) {
836                            graph.add_edge(*target, index, SqlGraphRequires::By);
837                            has_explicit_requires = true;
838                        } else {
839                            return Err(eyre!("Could not find `requires` target: {:?}", requires));
840                        }
841                    }
842                }
843                crate::ExternArgs::Support(support_fn) => {
844                    if let Some(target) = find_positioning_ref_target(
845                        support_fn,
846                        types,
847                        enums,
848                        externs,
849                        schemas,
850                        extension_sqls,
851                        triggers,
852                    ) {
853                        graph.add_edge(*target, index, SqlGraphRequires::By);
854                        has_explicit_requires = true
855                    }
856                }
857                crate::ExternArgs::Schema(declared_schema_name) => {
858                    for (schema, schema_index) in schemas {
859                        if schema.name == declared_schema_name {
860                            graph.add_edge(*schema_index, index, SqlGraphRequires::By);
861                            found_schema_declaration = true;
862                        }
863                    }
864                    if !found_schema_declaration {
865                        return Err(eyre!(
866                            "Got manual `schema = \"{declared_schema_name}\"` setting, but that schema did not exist."
867                        ));
868                    }
869                }
870                _ => (),
871            }
872        }
873
874        if !found_schema_declaration {
875            make_schema_connection(
876                graph,
877                "Extern",
878                index,
879                &item.rust_identifier(),
880                item.module_path,
881                schemas,
882            );
883        }
884
885        // The hash function must be defined after the {typename}_eq function.
886        for (hash_item, &hash_index) in hashes {
887            if item.module_path == hash_item.module_path
888                && item.name == hash_item.name.to_lowercase() + "_eq"
889            {
890                graph.add_edge(index, hash_index, SqlGraphRequires::By);
891            }
892        }
893
894        for arg in &item.fn_args {
895            let found = types
896                .iter()
897                .map(type_keyed)
898                .chain(enums.iter().map(type_keyed))
899                .find(|(item, _)| item.id_matches(&arg.used_ty.ty_id));
900            if let Some((_, ty_index)) = found {
901                graph.add_edge(*ty_index, index, SqlGraphRequires::ByArg);
902            } else {
903                let builtin_index = builtin_types.get(arg.used_ty.full_path).unwrap_or_else(|| {
904                    panic!("Could not fetch Builtin Type {}.", arg.used_ty.full_path)
905                });
906                graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
907            }
908            for (ext_item, ext_index) in extension_sqls {
909                if ext_item
910                    .has_sql_declared_entity(&SqlDeclared::Type(arg.used_ty.full_path.to_string()))
911                    .is_some()
912                {
913                    if !has_explicit_requires {
914                        graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
915                    }
916                } else if ext_item
917                    .has_sql_declared_entity(&SqlDeclared::Enum(arg.used_ty.full_path.to_string()))
918                    .is_some()
919                {
920                    graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
921                }
922            }
923        }
924
925        match &item.fn_return {
926            PgExternReturnEntity::None | PgExternReturnEntity::Trigger => (),
927            PgExternReturnEntity::Type { ty, .. } | PgExternReturnEntity::SetOf { ty, .. } => {
928                let found_index =
929                    types.iter().map(type_keyed).chain(enums.iter().map(type_keyed)).find_map(
930                        |(ty_item, index)| ty_item.id_matches(&ty.ty_id).then_some(index),
931                    );
932                if let Some(ty_index) = found_index {
933                    graph.add_edge(*ty_index, index, SqlGraphRequires::ByReturn);
934                } else {
935                    let builtin_index =
936                        builtin_types.get(&ty.full_path.to_string()).unwrap_or_else(|| {
937                            panic!("Could not fetch Builtin Type {}.", ty.full_path)
938                        });
939                    graph.add_edge(*builtin_index, index, SqlGraphRequires::ByReturn);
940                    for (ext_item, ext_index) in extension_sqls {
941                        if ext_item
942                            .has_sql_declared_entity(&SqlDeclared::Type(ty.full_path.into()))
943                            .is_some()
944                            || ext_item
945                                .has_sql_declared_entity(&SqlDeclared::Enum(ty.full_path.into()))
946                                .is_some()
947                        {
948                            graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
949                        }
950                    }
951                }
952            }
953            PgExternReturnEntity::Iterated { tys: iterated_returns, .. } => {
954                for PgExternReturnEntityIteratedItem { ty, .. } in iterated_returns {
955                    let found_index =
956                        types.iter().map(type_keyed).chain(enums.iter().map(type_keyed)).find_map(
957                            |(ty_item, index)| ty_item.id_matches(&ty.ty_id).then_some(index),
958                        );
959                    if let Some(ty_index) = found_index {
960                        graph.add_edge(*ty_index, index, SqlGraphRequires::ByReturn);
961                    } else {
962                        let builtin_index = builtin_types.get(ty.ty_source).unwrap_or_else(|| {
963                            panic!("Could not fetch Builtin Type {}.", ty.ty_source)
964                        });
965                        graph.add_edge(*builtin_index, index, SqlGraphRequires::ByReturn);
966                        for (ext_item, ext_index) in extension_sqls {
967                            if ext_item
968                                .has_sql_declared_entity(&SqlDeclared::Type(
969                                    ty.ty_source.to_string(),
970                                ))
971                                .is_some()
972                                || ext_item
973                                    .has_sql_declared_entity(&SqlDeclared::Enum(
974                                        ty.ty_source.to_string(),
975                                    ))
976                                    .is_some()
977                            {
978                                graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
979                            }
980                        }
981                    }
982                }
983            }
984        }
985    }
986    Ok(())
987}
988
989fn initialize_ords(
990    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
991    root: NodeIndex,
992    bootstrap: Option<NodeIndex>,
993    finalize: Option<NodeIndex>,
994    ords: Vec<PostgresOrdEntity>,
995) -> eyre::Result<HashMap<PostgresOrdEntity, NodeIndex>> {
996    let mut mapped_ords = HashMap::default();
997    for item in ords {
998        let entity = item.clone().into();
999        let index = graph.add_node(entity);
1000        mapped_ords.insert(item.clone(), index);
1001        build_base_edges(graph, index, root, bootstrap, finalize);
1002    }
1003    Ok(mapped_ords)
1004}
1005
1006fn connect_ords(
1007    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1008    ords: &HashMap<PostgresOrdEntity, NodeIndex>,
1009    schemas: &HashMap<SchemaEntity, NodeIndex>,
1010    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1011    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1012    externs: &HashMap<PgExternEntity, NodeIndex>,
1013) {
1014    for (item, &index) in ords {
1015        make_schema_connection(
1016            graph,
1017            "Ord",
1018            index,
1019            &item.rust_identifier(),
1020            item.module_path,
1021            schemas,
1022        );
1023
1024        make_type_or_enum_connection(
1025            graph,
1026            "Ord",
1027            index,
1028            &item.rust_identifier(),
1029            &item.id,
1030            types,
1031            enums,
1032        );
1033
1034        // Make PostgresOrdEntities (which will be translated into `CREATE OPERATOR CLASS` statements) depend
1035        // on the operators which they will reference. For example, a pgrx-defined Postgres type `parakeet`
1036        // which has `#[derive(PostgresOrd)]` will emit a `parakeet_btree_ops` operator class, which references
1037        // a definition of a < operator (among others) on the `parakeet` type. This code should ensure that the
1038        // < operator (along with all the others) is emitted before the `OPERATOR CLASS` itself.
1039
1040        for (extern_item, &extern_index) in externs {
1041            let fn_matches = |fn_name| {
1042                item.module_path == extern_item.module_path && extern_item.name == fn_name
1043            };
1044            let cmp_fn_matches = fn_matches(item.cmp_fn_name());
1045            let lt_fn_matches = fn_matches(item.lt_fn_name());
1046            let lte_fn_matches = fn_matches(item.le_fn_name());
1047            let eq_fn_matches = fn_matches(item.eq_fn_name());
1048            let gt_fn_matches = fn_matches(item.gt_fn_name());
1049            let gte_fn_matches = fn_matches(item.ge_fn_name());
1050            if cmp_fn_matches
1051                || lt_fn_matches
1052                || lte_fn_matches
1053                || eq_fn_matches
1054                || gt_fn_matches
1055                || gte_fn_matches
1056            {
1057                graph.add_edge(extern_index, index, SqlGraphRequires::By);
1058            }
1059        }
1060    }
1061}
1062
1063fn initialize_hashes(
1064    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1065    root: NodeIndex,
1066    bootstrap: Option<NodeIndex>,
1067    finalize: Option<NodeIndex>,
1068    hashes: Vec<PostgresHashEntity>,
1069) -> eyre::Result<HashMap<PostgresHashEntity, NodeIndex>> {
1070    let mut mapped_hashes = HashMap::default();
1071    for item in hashes {
1072        let entity: SqlGraphEntity = item.clone().into();
1073        let index = graph.add_node(entity);
1074        mapped_hashes.insert(item, index);
1075        build_base_edges(graph, index, root, bootstrap, finalize);
1076    }
1077    Ok(mapped_hashes)
1078}
1079
1080fn connect_hashes(
1081    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1082    hashes: &HashMap<PostgresHashEntity, NodeIndex>,
1083    schemas: &HashMap<SchemaEntity, NodeIndex>,
1084    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1085    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1086    externs: &HashMap<PgExternEntity, NodeIndex>,
1087) {
1088    for (item, &index) in hashes {
1089        make_schema_connection(
1090            graph,
1091            "Hash",
1092            index,
1093            &item.rust_identifier(),
1094            item.module_path,
1095            schemas,
1096        );
1097
1098        make_type_or_enum_connection(
1099            graph,
1100            "Hash",
1101            index,
1102            &item.rust_identifier(),
1103            &item.id,
1104            types,
1105            enums,
1106        );
1107
1108        if let Some((_, extern_index)) = externs.iter().find(|(extern_item, _)| {
1109            item.module_path == extern_item.module_path && extern_item.name == item.fn_name()
1110        }) {
1111            graph.add_edge(*extern_index, index, SqlGraphRequires::By);
1112        }
1113    }
1114}
1115
1116fn initialize_aggregates(
1117    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1118    root: NodeIndex,
1119    bootstrap: Option<NodeIndex>,
1120    finalize: Option<NodeIndex>,
1121    aggregates: Vec<PgAggregateEntity>,
1122    mapped_builtin_types: &mut HashMap<String, NodeIndex>,
1123    mapped_enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1124    mapped_types: &HashMap<PostgresTypeEntity, NodeIndex>,
1125) -> eyre::Result<HashMap<PgAggregateEntity, NodeIndex>> {
1126    let mut mapped_aggregates = HashMap::default();
1127    for item in aggregates {
1128        let entity: SqlGraphEntity = item.clone().into();
1129        let index = graph.add_node(entity);
1130
1131        for arg in &item.args {
1132            let found = mapped_types
1133                .iter()
1134                .map(type_keyed)
1135                .chain(mapped_enums.iter().map(type_keyed))
1136                .find(|(item, _)| item.id_matches(&arg.used_ty.ty_id));
1137
1138            if found.is_none() {
1139                mapped_builtin_types.entry(arg.used_ty.full_path.to_string()).or_insert_with(
1140                    || {
1141                        graph.add_node(SqlGraphEntity::BuiltinType(
1142                            arg.used_ty.full_path.to_string(),
1143                        ))
1144                    },
1145                );
1146            }
1147        }
1148
1149        mapped_aggregates.insert(item, index);
1150        build_base_edges(graph, index, root, bootstrap, finalize);
1151    }
1152    Ok(mapped_aggregates)
1153}
1154
1155fn connect_aggregate(
1156    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1157    item: &PgAggregateEntity,
1158    index: NodeIndex,
1159    schemas: &HashMap<SchemaEntity, NodeIndex>,
1160    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1161    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1162    builtin_types: &HashMap<String, NodeIndex>,
1163    externs: &HashMap<PgExternEntity, NodeIndex>,
1164) -> eyre::Result<()> {
1165    make_schema_connection(
1166        graph,
1167        "Aggregate",
1168        index,
1169        &item.rust_identifier(),
1170        item.module_path,
1171        schemas,
1172    );
1173
1174    make_type_or_enum_connection(
1175        graph,
1176        "Aggregate",
1177        index,
1178        &item.rust_identifier(),
1179        &item.ty_id,
1180        types,
1181        enums,
1182    );
1183
1184    for arg in &item.args {
1185        let found = make_type_or_enum_connection(
1186            graph,
1187            "Aggregate",
1188            index,
1189            &item.rust_identifier(),
1190            &arg.used_ty.ty_id,
1191            types,
1192            enums,
1193        );
1194        if !found {
1195            let builtin_index = builtin_types.get(arg.used_ty.full_path).unwrap_or_else(|| {
1196                panic!("Could not fetch Builtin Type {}.", arg.used_ty.full_path)
1197            });
1198            graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
1199        }
1200    }
1201
1202    for arg in item.direct_args.as_ref().unwrap_or(&vec![]) {
1203        let found = make_type_or_enum_connection(
1204            graph,
1205            "Aggregate",
1206            index,
1207            &item.rust_identifier(),
1208            &arg.used_ty.ty_id,
1209            types,
1210            enums,
1211        );
1212        if !found {
1213            let builtin_index = builtin_types.get(arg.used_ty.full_path).unwrap_or_else(|| {
1214                panic!("Could not fetch Builtin Type {}.", arg.used_ty.full_path)
1215            });
1216            graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
1217        }
1218    }
1219
1220    if let Some(arg) = &item.mstype {
1221        let found = make_type_or_enum_connection(
1222            graph,
1223            "Aggregate",
1224            index,
1225            &item.rust_identifier(),
1226            &arg.ty_id,
1227            types,
1228            enums,
1229        );
1230        if !found {
1231            let builtin_index = builtin_types
1232                .get(arg.full_path)
1233                .unwrap_or_else(|| panic!("Could not fetch Builtin Type {}.", arg.full_path));
1234            graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
1235        }
1236    }
1237
1238    make_extern_connection(
1239        graph,
1240        "Aggregate",
1241        index,
1242        &item.rust_identifier(),
1243        &(item.module_path.to_string() + "::" + item.sfunc),
1244        externs,
1245    )?;
1246
1247    if let Some(value) = item.finalfunc {
1248        make_extern_connection(
1249            graph,
1250            "Aggregate",
1251            index,
1252            &item.rust_identifier(),
1253            &(item.module_path.to_string() + "::" + value),
1254            externs,
1255        )?;
1256    }
1257    if let Some(value) = item.combinefunc {
1258        make_extern_connection(
1259            graph,
1260            "Aggregate",
1261            index,
1262            &item.rust_identifier(),
1263            &(item.module_path.to_string() + "::" + value),
1264            externs,
1265        )?;
1266    }
1267    if let Some(value) = item.serialfunc {
1268        make_extern_connection(
1269            graph,
1270            "Aggregate",
1271            index,
1272            &item.rust_identifier(),
1273            &(item.module_path.to_string() + "::" + value),
1274            externs,
1275        )?;
1276    }
1277    if let Some(value) = item.deserialfunc {
1278        make_extern_connection(
1279            graph,
1280            "Aggregate",
1281            index,
1282            &item.rust_identifier(),
1283            &(item.module_path.to_string() + "::" + value),
1284            externs,
1285        )?;
1286    }
1287    if let Some(value) = item.msfunc {
1288        make_extern_connection(
1289            graph,
1290            "Aggregate",
1291            index,
1292            &item.rust_identifier(),
1293            &(item.module_path.to_string() + "::" + value),
1294            externs,
1295        )?;
1296    }
1297    if let Some(value) = item.minvfunc {
1298        make_extern_connection(
1299            graph,
1300            "Aggregate",
1301            index,
1302            &item.rust_identifier(),
1303            &(item.module_path.to_string() + "::" + value),
1304            externs,
1305        )?;
1306    }
1307    if let Some(value) = item.mfinalfunc {
1308        make_extern_connection(
1309            graph,
1310            "Aggregate",
1311            index,
1312            &item.rust_identifier(),
1313            &(item.module_path.to_string() + "::" + value),
1314            externs,
1315        )?;
1316    }
1317    if let Some(value) = item.sortop {
1318        make_extern_connection(
1319            graph,
1320            "Aggregate",
1321            index,
1322            &item.rust_identifier(),
1323            &(item.module_path.to_string() + "::" + value),
1324            externs,
1325        )?;
1326    }
1327    Ok(())
1328}
1329
1330fn connect_aggregates(
1331    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1332    aggregates: &HashMap<PgAggregateEntity, NodeIndex>,
1333    schemas: &HashMap<SchemaEntity, NodeIndex>,
1334    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1335    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1336    builtin_types: &HashMap<String, NodeIndex>,
1337    externs: &HashMap<PgExternEntity, NodeIndex>,
1338) -> eyre::Result<()> {
1339    for (item, &index) in aggregates {
1340        connect_aggregate(graph, item, index, schemas, types, enums, builtin_types, externs)?
1341    }
1342    Ok(())
1343}
1344
1345fn initialize_triggers(
1346    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1347    root: NodeIndex,
1348    bootstrap: Option<NodeIndex>,
1349    finalize: Option<NodeIndex>,
1350    triggers: Vec<PgTriggerEntity>,
1351) -> eyre::Result<HashMap<PgTriggerEntity, NodeIndex>> {
1352    let mut mapped_triggers = HashMap::default();
1353    for item in triggers {
1354        let entity: SqlGraphEntity = item.clone().into();
1355        let index = graph.add_node(entity);
1356
1357        mapped_triggers.insert(item, index);
1358        build_base_edges(graph, index, root, bootstrap, finalize);
1359    }
1360    Ok(mapped_triggers)
1361}
1362
1363fn connect_triggers(
1364    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1365    triggers: &HashMap<PgTriggerEntity, NodeIndex>,
1366    schemas: &HashMap<SchemaEntity, NodeIndex>,
1367) {
1368    for (item, &index) in triggers {
1369        make_schema_connection(
1370            graph,
1371            "Trigger",
1372            index,
1373            &item.rust_identifier(),
1374            item.module_path,
1375            schemas,
1376        );
1377    }
1378}
1379
1380fn make_schema_connection(
1381    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1382    _kind: &str,
1383    index: NodeIndex,
1384    _rust_identifier: &str,
1385    module_path: &str,
1386    schemas: &HashMap<SchemaEntity, NodeIndex>,
1387) -> bool {
1388    let mut found = false;
1389    for (schema_item, &schema_index) in schemas {
1390        if module_path == schema_item.module_path {
1391            graph.add_edge(schema_index, index, SqlGraphRequires::By);
1392            found = true;
1393            break;
1394        }
1395    }
1396    found
1397}
1398
1399fn make_extern_connection(
1400    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1401    _kind: &str,
1402    index: NodeIndex,
1403    _rust_identifier: &str,
1404    full_path: &str,
1405    externs: &HashMap<PgExternEntity, NodeIndex>,
1406) -> eyre::Result<()> {
1407    match externs.iter().find(|(extern_item, _)| full_path == extern_item.full_path) {
1408        Some((_, extern_index)) => {
1409            graph.add_edge(*extern_index, index, SqlGraphRequires::By);
1410            Ok(())
1411        }
1412        None => Err(eyre!("Did not find connection `{full_path}` in {:#?}", {
1413            let mut paths = externs.keys().map(|v| v.full_path).collect::<Vec<_>>();
1414            paths.sort();
1415            paths
1416        })),
1417    }
1418}
1419
1420fn make_type_or_enum_connection(
1421    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1422    _kind: &str,
1423    index: NodeIndex,
1424    _rust_identifier: &str,
1425    ty_id: &TypeId,
1426    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1427    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1428) -> bool {
1429    types
1430        .iter()
1431        .map(type_keyed)
1432        .chain(enums.iter().map(type_keyed))
1433        .find(|(ty, _)| ty.id_matches(ty_id))
1434        .map(|(_, ty_index)| graph.add_edge(*ty_index, index, SqlGraphRequires::By))
1435        .is_some()
1436}