1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//! PostgreSQL-specific rendering.

use crate::introspection::sanitize_datamodel_names::{needs_sanitation, sanitize_string};
use datamodel_renderer as render;
use psl::{builtin_connectors::PostgresDatasourceProperties, Configuration, PreviewFeature};
use sql_schema_describer::{postgres::PostgresSchemaExt, SqlSchema};

const EXTENSION_ALLOW_LIST: &[&str] = &["citext", "postgis", "pg_crypto", "uuid-ossp"];

/// Renders PostgreSQL extensions definition to the datasource.
pub(super) fn add_extensions<'a>(
    datasource: &mut render::configuration::Datasource<'a>,
    schema: &'a SqlSchema,
    config: &'a Configuration,
) {
    if !config.preview_features().contains(PreviewFeature::PostgresqlExtensions) {
        return;
    }

    let pg_schema_ext: &PostgresSchemaExt = schema.downcast_connector_data();
    let previous_datasource = config.datasources.first().unwrap();

    let connector_data = previous_datasource
        .connector_data
        .downcast_ref::<PostgresDatasourceProperties>();

    let previous_extensions = connector_data.and_then(|p| p.extensions());
    let mut next_extensions = render::value::Array::new();

    for ext in pg_schema_ext.extension_walkers() {
        let mut next_extension = {
            if needs_sanitation(ext.name()) {
                let sanitized_name = sanitize_string(ext.name());
                let mut func = render::value::Function::new(sanitized_name);
                func.push_param(("map", render::value::Text::new(ext.name())));
                func
            } else {
                render::value::Function::new(ext.name())
            }
        };

        match previous_extensions.and_then(|e| e.find_by_name(ext.name())) {
            Some(prev) => {
                match prev.version() {
                    Some(previous_version) if previous_version != ext.version() => {
                        next_extension.push_param(("version", render::value::Text::new(ext.version())));
                    }
                    Some(previous_version) => {
                        next_extension.push_param(("version", render::value::Text::new(previous_version)));
                    }
                    None => (),
                };

                match prev.schema() {
                    Some(previous_schema) if previous_schema != ext.schema() => {
                        next_extension.push_param(("schema", render::value::Text::new(ext.schema())));
                    }
                    Some(previous_schema) => {
                        next_extension.push_param(("schema", render::value::Text::new(previous_schema)));
                    }
                    None => (),
                }

                next_extensions.push(next_extension);
            }
            None if EXTENSION_ALLOW_LIST.contains(&ext.name()) => {
                next_extension.push_param(("schema", render::value::Text::new(ext.schema())));
                next_extensions.push(next_extension);
            }
            None => (),
        }
    }

    if !next_extensions.is_empty() {
        datasource.push_custom_property("extensions", next_extensions);
    }
}