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!("$libdir/{extname}-{extver}")
460        } else {
461            String::from("MODULE_PATHNAME")
462        }
463    }
464}
465
466fn build_base_edges(
467    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
468    index: NodeIndex,
469    root: NodeIndex,
470    bootstrap: Option<NodeIndex>,
471    finalize: Option<NodeIndex>,
472) {
473    graph.add_edge(root, index, SqlGraphRequires::By);
474    if let Some(bootstrap) = bootstrap {
475        graph.add_edge(bootstrap, index, SqlGraphRequires::By);
476    }
477    if let Some(finalize) = finalize {
478        graph.add_edge(index, finalize, SqlGraphRequires::By);
479    }
480}
481
482#[allow(clippy::type_complexity)]
483fn initialize_extension_sqls(
484    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
485    root: NodeIndex,
486    extension_sqls: Vec<ExtensionSqlEntity>,
487) -> eyre::Result<(HashMap<ExtensionSqlEntity, NodeIndex>, Option<NodeIndex>, Option<NodeIndex>)> {
488    let mut bootstrap = None;
489    let mut finalize = None;
490    let mut mapped_extension_sqls = HashMap::default();
491    for item in extension_sqls {
492        let entity: SqlGraphEntity = item.clone().into();
493        let index = graph.add_node(entity);
494        mapped_extension_sqls.insert(item.clone(), index);
495
496        if item.bootstrap {
497            if let Some(existing_index) = bootstrap {
498                let existing: &SqlGraphEntity = &graph[existing_index];
499                return Err(eyre!(
500                    "Cannot have multiple `extension_sql!()` with `bootstrap` positioning, found `{}`, other was `{}`",
501                    item.rust_identifier(),
502                    existing.rust_identifier(),
503                ));
504            }
505            bootstrap = Some(index)
506        }
507        if item.finalize {
508            if let Some(existing_index) = finalize {
509                let existing: &SqlGraphEntity = &graph[existing_index];
510                return Err(eyre!(
511                    "Cannot have multiple `extension_sql!()` with `finalize` positioning, found `{}`, other was `{}`",
512                    item.rust_identifier(),
513                    existing.rust_identifier(),
514                ));
515            }
516            finalize = Some(index)
517        }
518    }
519    for (item, index) in &mapped_extension_sqls {
520        graph.add_edge(root, *index, SqlGraphRequires::By);
521        if !item.bootstrap {
522            if let Some(bootstrap) = bootstrap {
523                graph.add_edge(bootstrap, *index, SqlGraphRequires::By);
524            }
525        }
526        if !item.finalize {
527            if let Some(finalize) = finalize {
528                graph.add_edge(*index, finalize, SqlGraphRequires::By);
529            }
530        }
531    }
532    Ok((mapped_extension_sqls, bootstrap, finalize))
533}
534
535/// A best effort attempt to find the related [`NodeIndex`] for some [`PositioningRef`].
536pub fn find_positioning_ref_target<'a>(
537    positioning_ref: &'a PositioningRef,
538    types: &'a HashMap<PostgresTypeEntity, NodeIndex>,
539    enums: &'a HashMap<PostgresEnumEntity, NodeIndex>,
540    externs: &'a HashMap<PgExternEntity, NodeIndex>,
541    schemas: &'a HashMap<SchemaEntity, NodeIndex>,
542    extension_sqls: &'a HashMap<ExtensionSqlEntity, NodeIndex>,
543    triggers: &'a HashMap<PgTriggerEntity, NodeIndex>,
544) -> Option<&'a NodeIndex> {
545    match positioning_ref {
546        PositioningRef::FullPath(path) => {
547            // The best we can do here is a fuzzy search.
548            let segments = path.split("::").collect::<Vec<_>>();
549            let last_segment = segments.last().expect("Expected at least one segment.");
550            let rest = &segments[..segments.len() - 1];
551            let module_path = rest.join("::");
552
553            for (other, other_index) in types {
554                if *last_segment == other.name && other.module_path.ends_with(&module_path) {
555                    return Some(other_index);
556                }
557            }
558            for (other, other_index) in enums {
559                if last_segment == &other.name && other.module_path.ends_with(&module_path) {
560                    return Some(other_index);
561                }
562            }
563            for (other, other_index) in externs {
564                if *last_segment == other.unaliased_name
565                    && other.module_path.ends_with(&module_path)
566                {
567                    return Some(other_index);
568                }
569            }
570            for (other, other_index) in schemas {
571                if other.module_path.ends_with(path) {
572                    return Some(other_index);
573                }
574            }
575
576            for (other, other_index) in triggers {
577                if last_segment == &other.function_name && other.module_path.ends_with(&module_path)
578                {
579                    return Some(other_index);
580                }
581            }
582        }
583        PositioningRef::Name(name) => {
584            for (other, other_index) in extension_sqls {
585                if other.name == name {
586                    return Some(other_index);
587                }
588            }
589        }
590    };
591    None
592}
593
594fn connect_extension_sqls(
595    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
596    extension_sqls: &HashMap<ExtensionSqlEntity, NodeIndex>,
597    schemas: &HashMap<SchemaEntity, NodeIndex>,
598    types: &HashMap<PostgresTypeEntity, NodeIndex>,
599    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
600    externs: &HashMap<PgExternEntity, NodeIndex>,
601    triggers: &HashMap<PgTriggerEntity, NodeIndex>,
602) -> eyre::Result<()> {
603    for (item, &index) in extension_sqls {
604        make_schema_connection(
605            graph,
606            "Extension SQL",
607            index,
608            &item.rust_identifier(),
609            item.module_path,
610            schemas,
611        );
612
613        for requires in &item.requires {
614            if let Some(target) = find_positioning_ref_target(
615                requires,
616                types,
617                enums,
618                externs,
619                schemas,
620                extension_sqls,
621                triggers,
622            ) {
623                graph.add_edge(*target, index, SqlGraphRequires::By);
624            } else {
625                return Err(eyre!(
626                    "Could not find `requires` target of `{}`{}: {}",
627                    item.rust_identifier(),
628                    match (item.file(), item.line()) {
629                        (Some(file), Some(line)) => format!(" ({file}:{line})"),
630                        _ => "".to_string(),
631                    },
632                    match requires {
633                        PositioningRef::FullPath(path) => path.to_string(),
634                        PositioningRef::Name(name) => format!(r#""{name}""#),
635                    },
636                ));
637            }
638        }
639    }
640    Ok(())
641}
642
643fn initialize_schemas(
644    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
645    bootstrap: Option<NodeIndex>,
646    finalize: Option<NodeIndex>,
647    schemas: Vec<SchemaEntity>,
648) -> eyre::Result<HashMap<SchemaEntity, NodeIndex>> {
649    let mut mapped_schemas = HashMap::default();
650    for item in schemas {
651        let entity = item.clone().into();
652        let index = graph.add_node(entity);
653        mapped_schemas.insert(item, index);
654        if let Some(bootstrap) = bootstrap {
655            graph.add_edge(bootstrap, index, SqlGraphRequires::By);
656        }
657        if let Some(finalize) = finalize {
658            graph.add_edge(index, finalize, SqlGraphRequires::By);
659        }
660    }
661    Ok(mapped_schemas)
662}
663
664fn connect_schemas(
665    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
666    schemas: &HashMap<SchemaEntity, NodeIndex>,
667    root: NodeIndex,
668) {
669    for index in schemas.values().copied() {
670        graph.add_edge(root, index, SqlGraphRequires::By);
671    }
672}
673
674fn initialize_enums(
675    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
676    root: NodeIndex,
677    bootstrap: Option<NodeIndex>,
678    finalize: Option<NodeIndex>,
679    enums: Vec<PostgresEnumEntity>,
680) -> eyre::Result<HashMap<PostgresEnumEntity, NodeIndex>> {
681    let mut mapped_enums = HashMap::default();
682    for item in enums {
683        let entity: SqlGraphEntity = item.clone().into();
684        let index = graph.add_node(entity);
685        mapped_enums.insert(item, index);
686        build_base_edges(graph, index, root, bootstrap, finalize);
687    }
688    Ok(mapped_enums)
689}
690
691fn connect_enums(
692    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
693    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
694    schemas: &HashMap<SchemaEntity, NodeIndex>,
695) {
696    for (item, &index) in enums {
697        make_schema_connection(
698            graph,
699            "Enum",
700            index,
701            &item.rust_identifier(),
702            item.module_path,
703            schemas,
704        );
705    }
706}
707
708fn initialize_types(
709    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
710    root: NodeIndex,
711    bootstrap: Option<NodeIndex>,
712    finalize: Option<NodeIndex>,
713    types: Vec<PostgresTypeEntity>,
714) -> eyre::Result<HashMap<PostgresTypeEntity, NodeIndex>> {
715    let mut mapped_types = HashMap::default();
716    for item in types {
717        let entity = item.clone().into();
718        let index = graph.add_node(entity);
719        mapped_types.insert(item, index);
720        build_base_edges(graph, index, root, bootstrap, finalize);
721    }
722    Ok(mapped_types)
723}
724
725fn connect_types(
726    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
727    types: &HashMap<PostgresTypeEntity, NodeIndex>,
728    schemas: &HashMap<SchemaEntity, NodeIndex>,
729) {
730    for (item, &index) in types {
731        make_schema_connection(
732            graph,
733            "Type",
734            index,
735            &item.rust_identifier(),
736            item.module_path,
737            schemas,
738        );
739    }
740}
741
742fn initialize_externs(
743    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
744    root: NodeIndex,
745    bootstrap: Option<NodeIndex>,
746    finalize: Option<NodeIndex>,
747    externs: Vec<PgExternEntity>,
748    mapped_types: &HashMap<PostgresTypeEntity, NodeIndex>,
749    mapped_enums: &HashMap<PostgresEnumEntity, NodeIndex>,
750) -> eyre::Result<(HashMap<PgExternEntity, NodeIndex>, HashMap<String, NodeIndex>)> {
751    let mut mapped_externs = HashMap::default();
752    let mut mapped_builtin_types = HashMap::default();
753    for item in externs {
754        let entity: SqlGraphEntity = item.clone().into();
755        let index = graph.add_node(entity.clone());
756        mapped_externs.insert(item.clone(), index);
757        build_base_edges(graph, index, root, bootstrap, finalize);
758
759        for arg in &item.fn_args {
760            let found = mapped_types.keys().any(|ty_item| ty_item.id_matches(&arg.used_ty.ty_id))
761                || mapped_enums.keys().any(|ty_item| ty_item.id_matches(&arg.used_ty.ty_id));
762
763            if !found {
764                mapped_builtin_types.entry(arg.used_ty.full_path.to_string()).or_insert_with(
765                    || {
766                        graph.add_node(SqlGraphEntity::BuiltinType(
767                            arg.used_ty.full_path.to_string(),
768                        ))
769                    },
770                );
771            }
772        }
773
774        match &item.fn_return {
775            PgExternReturnEntity::None | PgExternReturnEntity::Trigger => (),
776            PgExternReturnEntity::Type { ty, .. } | PgExternReturnEntity::SetOf { ty, .. } => {
777                let found = mapped_types.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id))
778                    || mapped_enums.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id));
779
780                if !found {
781                    mapped_builtin_types.entry(ty.full_path.to_string()).or_insert_with(|| {
782                        graph.add_node(SqlGraphEntity::BuiltinType(ty.full_path.to_string()))
783                    });
784                }
785            }
786            PgExternReturnEntity::Iterated { tys: iterated_returns, .. } => {
787                for PgExternReturnEntityIteratedItem { ty, .. } in iterated_returns {
788                    let found = mapped_types.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id))
789                        || mapped_enums.keys().any(|ty_item| ty_item.id_matches(&ty.ty_id));
790
791                    if !found {
792                        mapped_builtin_types.entry(ty.ty_source.to_string()).or_insert_with(|| {
793                            graph.add_node(SqlGraphEntity::BuiltinType(ty.ty_source.to_string()))
794                        });
795                    }
796                }
797            }
798        }
799    }
800    Ok((mapped_externs, mapped_builtin_types))
801}
802
803fn connect_externs(
804    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
805    externs: &HashMap<PgExternEntity, NodeIndex>,
806    hashes: &HashMap<PostgresHashEntity, NodeIndex>,
807    schemas: &HashMap<SchemaEntity, NodeIndex>,
808    types: &HashMap<PostgresTypeEntity, NodeIndex>,
809    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
810    builtin_types: &HashMap<String, NodeIndex>,
811    extension_sqls: &HashMap<ExtensionSqlEntity, NodeIndex>,
812    triggers: &HashMap<PgTriggerEntity, NodeIndex>,
813) -> eyre::Result<()> {
814    for (item, &index) in externs {
815        let mut found_schema_declaration = false;
816        let mut has_explicit_requires = false;
817        for extern_attr in &item.extern_attrs {
818            match extern_attr {
819                crate::ExternArgs::Requires(requirements) => {
820                    for requires in requirements {
821                        if let Some(target) = find_positioning_ref_target(
822                            requires,
823                            types,
824                            enums,
825                            externs,
826                            schemas,
827                            extension_sqls,
828                            triggers,
829                        ) {
830                            graph.add_edge(*target, index, SqlGraphRequires::By);
831                            has_explicit_requires = true;
832                        } else {
833                            return Err(eyre!("Could not find `requires` target: {:?}", requires));
834                        }
835                    }
836                }
837                crate::ExternArgs::Schema(declared_schema_name) => {
838                    for (schema, schema_index) in schemas {
839                        if schema.name == declared_schema_name {
840                            graph.add_edge(*schema_index, index, SqlGraphRequires::By);
841                            found_schema_declaration = true;
842                        }
843                    }
844                    if !found_schema_declaration {
845                        return Err(eyre!("Got manual `schema = \"{declared_schema_name}\"` setting, but that schema did not exist."));
846                    }
847                }
848                _ => (),
849            }
850        }
851
852        if !found_schema_declaration {
853            make_schema_connection(
854                graph,
855                "Extern",
856                index,
857                &item.rust_identifier(),
858                item.module_path,
859                schemas,
860            );
861        }
862
863        // The hash function must be defined after the {typename}_eq function.
864        for (hash_item, &hash_index) in hashes {
865            if item.module_path == hash_item.module_path
866                && item.name == hash_item.name.to_lowercase() + "_eq"
867            {
868                graph.add_edge(index, hash_index, SqlGraphRequires::By);
869            }
870        }
871
872        for arg in &item.fn_args {
873            let found = types
874                .iter()
875                .map(type_keyed)
876                .chain(enums.iter().map(type_keyed))
877                .find(|(item, _)| item.id_matches(&arg.used_ty.ty_id));
878            if let Some((_, ty_index)) = found {
879                graph.add_edge(*ty_index, index, SqlGraphRequires::ByArg);
880            } else {
881                let builtin_index = builtin_types.get(arg.used_ty.full_path).unwrap_or_else(|| {
882                    panic!("Could not fetch Builtin Type {}.", arg.used_ty.full_path)
883                });
884                graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
885            }
886            for (ext_item, ext_index) in extension_sqls {
887                if ext_item
888                    .has_sql_declared_entity(&SqlDeclared::Type(arg.used_ty.full_path.to_string()))
889                    .is_some()
890                {
891                    if !has_explicit_requires {
892                        graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
893                    }
894                } else if ext_item
895                    .has_sql_declared_entity(&SqlDeclared::Enum(arg.used_ty.full_path.to_string()))
896                    .is_some()
897                {
898                    graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
899                }
900            }
901        }
902
903        match &item.fn_return {
904            PgExternReturnEntity::None | PgExternReturnEntity::Trigger => (),
905            PgExternReturnEntity::Type { ty, .. } | PgExternReturnEntity::SetOf { ty, .. } => {
906                let found_index =
907                    types.iter().map(type_keyed).chain(enums.iter().map(type_keyed)).find_map(
908                        |(ty_item, index)| ty_item.id_matches(&ty.ty_id).then_some(index),
909                    );
910                if let Some(ty_index) = found_index {
911                    graph.add_edge(*ty_index, index, SqlGraphRequires::ByReturn);
912                } else {
913                    let builtin_index =
914                        builtin_types.get(&ty.full_path.to_string()).unwrap_or_else(|| {
915                            panic!("Could not fetch Builtin Type {}.", ty.full_path)
916                        });
917                    graph.add_edge(*builtin_index, index, SqlGraphRequires::ByReturn);
918                    for (ext_item, ext_index) in extension_sqls {
919                        if ext_item
920                            .has_sql_declared_entity(&SqlDeclared::Type(ty.full_path.into()))
921                            .is_some()
922                            || ext_item
923                                .has_sql_declared_entity(&SqlDeclared::Enum(ty.full_path.into()))
924                                .is_some()
925                        {
926                            graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
927                        }
928                    }
929                }
930            }
931            PgExternReturnEntity::Iterated { tys: iterated_returns, .. } => {
932                for PgExternReturnEntityIteratedItem { ty, .. } in iterated_returns {
933                    let found_index =
934                        types.iter().map(type_keyed).chain(enums.iter().map(type_keyed)).find_map(
935                            |(ty_item, index)| ty_item.id_matches(&ty.ty_id).then_some(index),
936                        );
937                    if let Some(ty_index) = found_index {
938                        graph.add_edge(*ty_index, index, SqlGraphRequires::ByReturn);
939                    } else {
940                        let builtin_index = builtin_types.get(ty.ty_source).unwrap_or_else(|| {
941                            panic!("Could not fetch Builtin Type {}.", ty.ty_source)
942                        });
943                        graph.add_edge(*builtin_index, index, SqlGraphRequires::ByReturn);
944                        for (ext_item, ext_index) in extension_sqls {
945                            if ext_item
946                                .has_sql_declared_entity(&SqlDeclared::Type(
947                                    ty.ty_source.to_string(),
948                                ))
949                                .is_some()
950                                || ext_item
951                                    .has_sql_declared_entity(&SqlDeclared::Enum(
952                                        ty.ty_source.to_string(),
953                                    ))
954                                    .is_some()
955                            {
956                                graph.add_edge(*ext_index, index, SqlGraphRequires::ByArg);
957                            }
958                        }
959                    }
960                }
961            }
962        }
963    }
964    Ok(())
965}
966
967fn initialize_ords(
968    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
969    root: NodeIndex,
970    bootstrap: Option<NodeIndex>,
971    finalize: Option<NodeIndex>,
972    ords: Vec<PostgresOrdEntity>,
973) -> eyre::Result<HashMap<PostgresOrdEntity, NodeIndex>> {
974    let mut mapped_ords = HashMap::default();
975    for item in ords {
976        let entity = item.clone().into();
977        let index = graph.add_node(entity);
978        mapped_ords.insert(item.clone(), index);
979        build_base_edges(graph, index, root, bootstrap, finalize);
980    }
981    Ok(mapped_ords)
982}
983
984fn connect_ords(
985    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
986    ords: &HashMap<PostgresOrdEntity, NodeIndex>,
987    schemas: &HashMap<SchemaEntity, NodeIndex>,
988    types: &HashMap<PostgresTypeEntity, NodeIndex>,
989    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
990    externs: &HashMap<PgExternEntity, NodeIndex>,
991) {
992    for (item, &index) in ords {
993        make_schema_connection(
994            graph,
995            "Ord",
996            index,
997            &item.rust_identifier(),
998            item.module_path,
999            schemas,
1000        );
1001
1002        make_type_or_enum_connection(
1003            graph,
1004            "Ord",
1005            index,
1006            &item.rust_identifier(),
1007            &item.id,
1008            types,
1009            enums,
1010        );
1011
1012        // Make PostgresOrdEntities (which will be translated into `CREATE OPERATOR CLASS` statements) depend
1013        // on the operators which they will reference. For example, a pgrx-defined Postgres type `parakeet`
1014        // which has `#[derive(PostgresOrd)]` will emit a `parakeet_btree_ops` operator class, which references
1015        // a definition of a < operator (among others) on the `parakeet` type. This code should ensure that the
1016        // < operator (along with all the others) is emitted before the `OPERATOR CLASS` itself.
1017
1018        for (extern_item, &extern_index) in externs {
1019            let fn_matches = |fn_name| {
1020                item.module_path == extern_item.module_path && extern_item.name == fn_name
1021            };
1022            let cmp_fn_matches = fn_matches(item.cmp_fn_name());
1023            let lt_fn_matches = fn_matches(item.lt_fn_name());
1024            let lte_fn_matches = fn_matches(item.le_fn_name());
1025            let eq_fn_matches = fn_matches(item.eq_fn_name());
1026            let gt_fn_matches = fn_matches(item.gt_fn_name());
1027            let gte_fn_matches = fn_matches(item.ge_fn_name());
1028            if cmp_fn_matches
1029                || lt_fn_matches
1030                || lte_fn_matches
1031                || eq_fn_matches
1032                || gt_fn_matches
1033                || gte_fn_matches
1034            {
1035                graph.add_edge(extern_index, index, SqlGraphRequires::By);
1036            }
1037        }
1038    }
1039}
1040
1041fn initialize_hashes(
1042    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1043    root: NodeIndex,
1044    bootstrap: Option<NodeIndex>,
1045    finalize: Option<NodeIndex>,
1046    hashes: Vec<PostgresHashEntity>,
1047) -> eyre::Result<HashMap<PostgresHashEntity, NodeIndex>> {
1048    let mut mapped_hashes = HashMap::default();
1049    for item in hashes {
1050        let entity: SqlGraphEntity = item.clone().into();
1051        let index = graph.add_node(entity);
1052        mapped_hashes.insert(item, index);
1053        build_base_edges(graph, index, root, bootstrap, finalize);
1054    }
1055    Ok(mapped_hashes)
1056}
1057
1058fn connect_hashes(
1059    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1060    hashes: &HashMap<PostgresHashEntity, NodeIndex>,
1061    schemas: &HashMap<SchemaEntity, NodeIndex>,
1062    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1063    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1064    externs: &HashMap<PgExternEntity, NodeIndex>,
1065) {
1066    for (item, &index) in hashes {
1067        make_schema_connection(
1068            graph,
1069            "Hash",
1070            index,
1071            &item.rust_identifier(),
1072            item.module_path,
1073            schemas,
1074        );
1075
1076        make_type_or_enum_connection(
1077            graph,
1078            "Hash",
1079            index,
1080            &item.rust_identifier(),
1081            &item.id,
1082            types,
1083            enums,
1084        );
1085
1086        if let Some((_, extern_index)) = externs.iter().find(|(extern_item, _)| {
1087            item.module_path == extern_item.module_path && extern_item.name == item.fn_name()
1088        }) {
1089            graph.add_edge(*extern_index, index, SqlGraphRequires::By);
1090        }
1091    }
1092}
1093
1094fn initialize_aggregates(
1095    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1096    root: NodeIndex,
1097    bootstrap: Option<NodeIndex>,
1098    finalize: Option<NodeIndex>,
1099    aggregates: Vec<PgAggregateEntity>,
1100    mapped_builtin_types: &mut HashMap<String, NodeIndex>,
1101    mapped_enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1102    mapped_types: &HashMap<PostgresTypeEntity, NodeIndex>,
1103) -> eyre::Result<HashMap<PgAggregateEntity, NodeIndex>> {
1104    let mut mapped_aggregates = HashMap::default();
1105    for item in aggregates {
1106        let entity: SqlGraphEntity = item.clone().into();
1107        let index = graph.add_node(entity);
1108
1109        for arg in &item.args {
1110            let found = mapped_types
1111                .iter()
1112                .map(type_keyed)
1113                .chain(mapped_enums.iter().map(type_keyed))
1114                .find(|(item, _)| item.id_matches(&arg.used_ty.ty_id));
1115
1116            if found.is_none() {
1117                mapped_builtin_types.entry(arg.used_ty.full_path.to_string()).or_insert_with(
1118                    || {
1119                        graph.add_node(SqlGraphEntity::BuiltinType(
1120                            arg.used_ty.full_path.to_string(),
1121                        ))
1122                    },
1123                );
1124            }
1125        }
1126
1127        mapped_aggregates.insert(item, index);
1128        build_base_edges(graph, index, root, bootstrap, finalize);
1129    }
1130    Ok(mapped_aggregates)
1131}
1132
1133fn connect_aggregate(
1134    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1135    item: &PgAggregateEntity,
1136    index: NodeIndex,
1137    schemas: &HashMap<SchemaEntity, NodeIndex>,
1138    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1139    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1140    builtin_types: &HashMap<String, NodeIndex>,
1141    externs: &HashMap<PgExternEntity, NodeIndex>,
1142) -> eyre::Result<()> {
1143    make_schema_connection(
1144        graph,
1145        "Aggregate",
1146        index,
1147        &item.rust_identifier(),
1148        item.module_path,
1149        schemas,
1150    );
1151
1152    make_type_or_enum_connection(
1153        graph,
1154        "Aggregate",
1155        index,
1156        &item.rust_identifier(),
1157        &item.ty_id,
1158        types,
1159        enums,
1160    );
1161
1162    for arg in &item.args {
1163        let found = make_type_or_enum_connection(
1164            graph,
1165            "Aggregate",
1166            index,
1167            &item.rust_identifier(),
1168            &arg.used_ty.ty_id,
1169            types,
1170            enums,
1171        );
1172        if !found {
1173            let builtin_index = builtin_types.get(arg.used_ty.full_path).unwrap_or_else(|| {
1174                panic!("Could not fetch Builtin Type {}.", arg.used_ty.full_path)
1175            });
1176            graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
1177        }
1178    }
1179
1180    for arg in item.direct_args.as_ref().unwrap_or(&vec![]) {
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    if let Some(arg) = &item.mstype {
1199        let found = make_type_or_enum_connection(
1200            graph,
1201            "Aggregate",
1202            index,
1203            &item.rust_identifier(),
1204            &arg.ty_id,
1205            types,
1206            enums,
1207        );
1208        if !found {
1209            let builtin_index = builtin_types
1210                .get(arg.full_path)
1211                .unwrap_or_else(|| panic!("Could not fetch Builtin Type {}.", arg.full_path));
1212            graph.add_edge(*builtin_index, index, SqlGraphRequires::ByArg);
1213        }
1214    }
1215
1216    make_extern_connection(
1217        graph,
1218        "Aggregate",
1219        index,
1220        &item.rust_identifier(),
1221        &(item.module_path.to_string() + "::" + item.sfunc),
1222        externs,
1223    )?;
1224
1225    if let Some(value) = item.finalfunc {
1226        make_extern_connection(
1227            graph,
1228            "Aggregate",
1229            index,
1230            &item.rust_identifier(),
1231            &(item.module_path.to_string() + "::" + value),
1232            externs,
1233        )?;
1234    }
1235    if let Some(value) = item.combinefunc {
1236        make_extern_connection(
1237            graph,
1238            "Aggregate",
1239            index,
1240            &item.rust_identifier(),
1241            &(item.module_path.to_string() + "::" + value),
1242            externs,
1243        )?;
1244    }
1245    if let Some(value) = item.serialfunc {
1246        make_extern_connection(
1247            graph,
1248            "Aggregate",
1249            index,
1250            &item.rust_identifier(),
1251            &(item.module_path.to_string() + "::" + value),
1252            externs,
1253        )?;
1254    }
1255    if let Some(value) = item.deserialfunc {
1256        make_extern_connection(
1257            graph,
1258            "Aggregate",
1259            index,
1260            &item.rust_identifier(),
1261            &(item.module_path.to_string() + "::" + value),
1262            externs,
1263        )?;
1264    }
1265    if let Some(value) = item.msfunc {
1266        make_extern_connection(
1267            graph,
1268            "Aggregate",
1269            index,
1270            &item.rust_identifier(),
1271            &(item.module_path.to_string() + "::" + value),
1272            externs,
1273        )?;
1274    }
1275    if let Some(value) = item.minvfunc {
1276        make_extern_connection(
1277            graph,
1278            "Aggregate",
1279            index,
1280            &item.rust_identifier(),
1281            &(item.module_path.to_string() + "::" + value),
1282            externs,
1283        )?;
1284    }
1285    if let Some(value) = item.mfinalfunc {
1286        make_extern_connection(
1287            graph,
1288            "Aggregate",
1289            index,
1290            &item.rust_identifier(),
1291            &(item.module_path.to_string() + "::" + value),
1292            externs,
1293        )?;
1294    }
1295    if let Some(value) = item.sortop {
1296        make_extern_connection(
1297            graph,
1298            "Aggregate",
1299            index,
1300            &item.rust_identifier(),
1301            &(item.module_path.to_string() + "::" + value),
1302            externs,
1303        )?;
1304    }
1305    Ok(())
1306}
1307
1308fn connect_aggregates(
1309    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1310    aggregates: &HashMap<PgAggregateEntity, NodeIndex>,
1311    schemas: &HashMap<SchemaEntity, NodeIndex>,
1312    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1313    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1314    builtin_types: &HashMap<String, NodeIndex>,
1315    externs: &HashMap<PgExternEntity, NodeIndex>,
1316) -> eyre::Result<()> {
1317    for (item, &index) in aggregates {
1318        connect_aggregate(graph, item, index, schemas, types, enums, builtin_types, externs)?
1319    }
1320    Ok(())
1321}
1322
1323fn initialize_triggers(
1324    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1325    root: NodeIndex,
1326    bootstrap: Option<NodeIndex>,
1327    finalize: Option<NodeIndex>,
1328    triggers: Vec<PgTriggerEntity>,
1329) -> eyre::Result<HashMap<PgTriggerEntity, NodeIndex>> {
1330    let mut mapped_triggers = HashMap::default();
1331    for item in triggers {
1332        let entity: SqlGraphEntity = item.clone().into();
1333        let index = graph.add_node(entity);
1334
1335        mapped_triggers.insert(item, index);
1336        build_base_edges(graph, index, root, bootstrap, finalize);
1337    }
1338    Ok(mapped_triggers)
1339}
1340
1341fn connect_triggers(
1342    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1343    triggers: &HashMap<PgTriggerEntity, NodeIndex>,
1344    schemas: &HashMap<SchemaEntity, NodeIndex>,
1345) {
1346    for (item, &index) in triggers {
1347        make_schema_connection(
1348            graph,
1349            "Trigger",
1350            index,
1351            &item.rust_identifier(),
1352            item.module_path,
1353            schemas,
1354        );
1355    }
1356}
1357
1358fn make_schema_connection(
1359    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1360    _kind: &str,
1361    index: NodeIndex,
1362    _rust_identifier: &str,
1363    module_path: &str,
1364    schemas: &HashMap<SchemaEntity, NodeIndex>,
1365) -> bool {
1366    let mut found = false;
1367    for (schema_item, &schema_index) in schemas {
1368        if module_path == schema_item.module_path {
1369            graph.add_edge(schema_index, index, SqlGraphRequires::By);
1370            found = true;
1371            break;
1372        }
1373    }
1374    found
1375}
1376
1377fn make_extern_connection(
1378    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1379    _kind: &str,
1380    index: NodeIndex,
1381    _rust_identifier: &str,
1382    full_path: &str,
1383    externs: &HashMap<PgExternEntity, NodeIndex>,
1384) -> eyre::Result<()> {
1385    match externs.iter().find(|(extern_item, _)| full_path == extern_item.full_path) {
1386        Some((_, extern_index)) => {
1387            graph.add_edge(*extern_index, index, SqlGraphRequires::By);
1388            Ok(())
1389        }
1390        None => Err(eyre!("Did not find connection `{full_path}` in {:#?}", {
1391            let mut paths = externs.iter().map(|(v, _)| v.full_path).collect::<Vec<_>>();
1392            paths.sort();
1393            paths
1394        })),
1395    }
1396}
1397
1398fn make_type_or_enum_connection(
1399    graph: &mut StableGraph<SqlGraphEntity, SqlGraphRequires>,
1400    _kind: &str,
1401    index: NodeIndex,
1402    _rust_identifier: &str,
1403    ty_id: &TypeId,
1404    types: &HashMap<PostgresTypeEntity, NodeIndex>,
1405    enums: &HashMap<PostgresEnumEntity, NodeIndex>,
1406) -> bool {
1407    types
1408        .iter()
1409        .map(type_keyed)
1410        .chain(enums.iter().map(type_keyed))
1411        .find(|(ty, _)| ty.id_matches(ty_id))
1412        .map(|(_, ty_index)| graph.add_edge(*ty_index, index, SqlGraphRequires::By))
1413        .is_some()
1414}