Skip to main content

datasources_postgres/
driver.rs

1use crate::connection::PostgresConnection;
2use crate::converters;
3use crate::sql_generate;
4use crate::statement_renderer::PostgresStatementRenderer;
5use crate::transaction::PostgresTransaction;
6use datasources::{
7    downcast_connection, Command, Connection, DatasourceError, DefaultValueConverter,
8    DeterministicTypeConverter, Driver, Information, IsolationLevel, Mapping, Result,
9    StatementTextRenderer, Transaction, TypeConverter, Value,
10};
11use std::collections::HashMap;
12use std::sync::{Arc, LazyLock};
13
14pub struct PostgresDriver;
15
16impl PostgresDriver {
17    pub const QUOTE_LEFT: &'static str = sql_generate::QUOTE_LEFT;
18    pub const QUOTE_RIGHT: &'static str = sql_generate::QUOTE_RIGHT;
19
20    pub fn statement_text_renderer() -> &'static StatementTextRenderer {
21        static DRIVER: PostgresDriver = PostgresDriver;
22        Driver::statement_renderer(&DRIVER).expect("postgres")
23    }
24
25    pub fn types() -> &'static DeterministicTypeConverter {
26        &*sql_generate::TYPES
27    }
28
29    pub fn defaults() -> &'static DefaultValueConverter {
30        &sql_generate::DEFAULTS
31    }
32}
33
34static STATEMENTS: LazyLock<StatementTextRenderer> =
35    LazyLock::new(|| StatementTextRenderer::new(Some(Box::new(PostgresStatementRenderer)), None));
36
37static INFORMATION: LazyLock<Information> = LazyLock::new(|| {
38    Information::new(
39        [
40            "PostgreSQL server driver.",
41            "Full connection, command, transaction, and repository support.",
42        ],
43        [
44            "Overrides SelectInsertedIdStatement to SELECT setval(pg_get_serial_sequence('{table}', '{column}'), MAX(\"{column}\")) AS {column}",
45            "Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY",
46            "Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)",
47            "Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)",
48            "Overrides CreateCreateStoredProcedureStatement to PROCEDURE Create{Plural}",
49            "Overrides CreateReadStoredProcedureStatement to PROCEDURE Read{Plural}By{Properties}",
50            "Overrides CreateUpdateStoredProcedureStatement to PROCEDURE Update{Plural}By{Properties}",
51            "Overrides CreateDeleteStoredProcedureStatement to PROCEDURE Delete{Plural}By{Properties}",
52            "Overrides CreateBulkCreateStoredProcedureStatement to PROCEDURE BulkCreate{Plural}",
53            "Overrides CreateBulkUpdateStoredProcedureStatement to PROCEDURE BulkUpdate{Plural}",
54            "Overrides CreateBulkDeleteStoredProcedureStatement to PROCEDURE BulkDelete{Plural}",
55        ],
56        [] as [&str; 0],
57    )
58});
59
60pub(crate) fn column_sql(name: &str) -> Option<&'static str> {
61    Some(match name {
62        "int" => "INTEGER",
63        "uuid" => "UUID",
64        "string" => "TEXT",
65        "bool" => "BOOLEAN",
66        "decimal" => "NUMERIC",
67        "float" => "DOUBLE PRECISION",
68        "datetime" => "TIMESTAMPTZ",
69        "binary" => "BYTEA",
70        _ => return None,
71    })
72}
73
74impl Driver for PostgresDriver {
75    fn connection(&self, options: Option<&HashMap<String, String>>) -> Arc<dyn Connection> {
76        PostgresConnection::from_options(options)
77    }
78
79    fn command(&self, connection: Arc<dyn Connection>) -> Arc<dyn Command> {
80        connection.create_command()
81    }
82
83    fn transaction(
84        &self,
85        connection: Arc<dyn Connection>,
86        isolation: IsolationLevel,
87    ) -> Arc<dyn Transaction> {
88        let conn = downcast_connection::<PostgresConnection>(connection).expect("postgres");
89        Arc::new(PostgresTransaction::new(conn.share(), isolation))
90    }
91
92    fn row_type_converters(
93        &self,
94    ) -> HashMap<String, Box<dyn TypeConverter<Value, Value> + Send + Sync>> {
95        converters::row_type_converters()
96    }
97
98    fn column_type(&self, name: &str) -> Result<String> {
99        column_sql(name)
100            .map(str::to_string)
101            .ok_or_else(|| DatasourceError::message(format!("Unknown postgres type \"{name}\"")))
102    }
103
104    fn type_converter(&self) -> Option<&dyn Mapping<String, String>> {
105        Some(&*sql_generate::TYPES)
106    }
107
108    fn default_value_converter(&self) -> Option<&DefaultValueConverter> {
109        Some(&sql_generate::DEFAULTS)
110    }
111
112    fn statement_renderer(&self) -> Option<&StatementTextRenderer> {
113        Some(&STATEMENTS)
114    }
115
116    fn information(&self) -> Option<&Information> {
117        Some(&INFORMATION)
118    }
119}
120
121datasources::export_driver!(datasources_postgres_driver, PostgresDriver);
122
123#[cfg(test)]
124mod tests {
125    use super::PostgresDriver;
126    use datasources::Driver;
127
128    #[test]
129    fn maps_column_types() {
130        let driver = PostgresDriver;
131        assert_eq!(driver.column_type("int").unwrap(), "INTEGER");
132        assert_eq!(driver.column_type("uuid").unwrap(), "UUID");
133        assert_eq!(driver.column_type("string").unwrap(), "TEXT");
134        assert_eq!(driver.column_type("bool").unwrap(), "BOOLEAN");
135        assert_eq!(driver.column_type("decimal").unwrap(), "NUMERIC");
136        assert_eq!(driver.column_type("float").unwrap(), "DOUBLE PRECISION");
137        assert_eq!(driver.column_type("datetime").unwrap(), "TIMESTAMPTZ");
138        assert_eq!(driver.column_type("binary").unwrap(), "BYTEA");
139        assert!(driver
140            .column_type("xml")
141            .unwrap_err()
142            .to_string()
143            .contains("Unknown postgres type \"xml\""));
144    }
145
146    #[test]
147    fn maps_property_targets() {
148        use datasources::converters::RowTypeConverter;
149        use datasources::Value;
150        use std::collections::HashMap;
151
152        let driver = PostgresDriver;
153        let columns = HashMap::from([
154            ("Key".into(), "uuid".into()),
155            ("Active".into(), "bool".into()),
156            ("Login".into(), "string".into()),
157            ("Created".into(), "datetime".into()),
158        ]);
159        let converter = RowTypeConverter::from(|| driver.row_type_converters(), &columns);
160        let row = HashMap::from([
161            (
162                "Key".into(),
163                Value::from("11111111-1111-1111-1111-111111111111"),
164            ),
165            ("Active".into(), Value::Int(1)),
166            ("Login".into(), Value::from("ada")),
167            ("Created".into(), Value::from("2020-01-02T03:04:05.000Z")),
168        ]);
169        let converted = converter.convert(&row).unwrap();
170        assert!(matches!(converted.get("Key"), Some(Value::Uuid(_))));
171        assert_eq!(converted.get("Active"), Some(&Value::Bool(true)));
172        assert_eq!(converted.get("Login"), Some(&Value::from("ada")));
173        assert!(matches!(converted.get("Created"), Some(Value::DateTime(_))));
174        let stored = converter.reverse_convert(&converted).unwrap();
175        assert!(matches!(stored.get("Key"), Some(Value::Uuid(_))));
176        assert_eq!(stored.get("Active"), Some(&Value::Bool(true)));
177        assert_eq!(stored.get("Login"), Some(&Value::from("ada")));
178        assert_eq!(
179            stored.get("Created"),
180            Some(&Value::from("2020-01-02T03:04:05.000Z"))
181        );
182    }
183}