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