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, Driver, Information, IsolationLevel,
8    Column, Mapping, Result, RoutineParameter, StatementTextRenderer, Transaction, TypeConverter,
9    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    pub const PROCEDURE_PAYLOAD_TYPE: &'static str = sql_generate::PROCEDURE_PAYLOAD_TYPE;
20
21    pub fn apply_keywords(sql: &str, keyword: &dyn Fn(&str) -> String) -> String {
22        sql_generate::apply_keywords(sql, keyword)
23    }
24
25    pub fn statement_text_renderer() -> &'static StatementTextRenderer {
26        static DRIVER: PostgresDriver = PostgresDriver;
27        Driver::statement_renderer(&DRIVER).expect("postgres")
28    }
29
30    pub fn types() -> &'static crate::type_map::TypeMap {
31        &*sql_generate::TYPES
32    }
33
34    pub fn defaults() -> &'static crate::default_value_converter::DefaultValueConverter {
35        &sql_generate::DEFAULTS
36    }
37
38    pub fn convert_type(field: &datasources::SpecField) -> Result<String> {
39        sql_generate::convert_type(field)
40    }
41
42    pub fn convert_default(spec_type: &str, text: &str) -> Result<Option<String>> {
43        sql_generate::convert_default(spec_type, text)
44    }
45
46    pub fn quote(name: &str) -> String {
47        format!("\"{name}\"")
48    }
49
50    pub fn native_type(column: &Column) -> String {
51        column
52            .field
53            .as_ref()
54            .and_then(|field| sql_generate::TYPES.convert_field(field).ok())
55            .unwrap_or_else(|| {
56                column_sql(&column.type_name)
57                    .map(str::to_string)
58                    .unwrap_or_else(|| column.type_name.clone())
59            })
60    }
61
62    pub fn native_default(column: &Column) -> String {
63        let Some(default) = &column.default else {
64            return String::new();
65        };
66        let Some(field) = &column.field else {
67            return default.value.clone();
68        };
69        sql_generate::DEFAULTS
70            .convert(&field.type_name, &default.value)
71            .ok()
72            .flatten()
73            .unwrap_or_else(|| default.value.clone())
74    }
75
76    pub fn utc_now() -> String {
77        sql_generate::DEFAULTS
78            .convert("datetime", "UtcNow")
79            .ok()
80            .flatten()
81            .unwrap_or_default()
82    }
83
84    pub fn native_param(parameter: &RoutineParameter) -> String {
85        if parameter.r#type == "payload" {
86            return Self::PROCEDURE_PAYLOAD_TYPE.to_string();
87        }
88        parameter
89            .field
90            .as_ref()
91            .and_then(|field| sql_generate::TYPES.convert_field(field).ok())
92            .unwrap_or_else(|| parameter.r#type.clone())
93    }
94}
95
96static STATEMENTS: LazyLock<StatementTextRenderer> =
97    LazyLock::new(|| {
98        StatementTextRenderer::new(Some(Box::new(PostgresStatementRenderer)), None)
99            .with_keyword_apply(sql_generate::apply_keywords)
100    });
101
102static INFORMATION: LazyLock<Information> = LazyLock::new(|| {
103    Information::new(
104        [
105            "PostgreSQL server driver.",
106            "Full connection, command, transaction, and repository support.",
107        ],
108        [
109            "Overrides SelectInsertedIdStatement to SELECT setval(pg_get_serial_sequence('{table}', '{column}'), MAX(\"{column}\")) AS {column}",
110            "Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY",
111            "Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)",
112            "Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)",
113            "Overrides CreateCreateStoredProcedureStatement to PROCEDURE Create{Plural}",
114            "Overrides CreateReadStoredProcedureStatement to PROCEDURE Read{Plural}By{Properties}",
115            "Overrides CreateUpdateStoredProcedureStatement to PROCEDURE Update{Plural}By{Properties}",
116            "Overrides CreateDeleteStoredProcedureStatement to PROCEDURE Delete{Plural}By{Properties}",
117            "Overrides CreateBulkCreateStoredProcedureStatement to PROCEDURE BulkCreate{Plural}",
118            "Overrides CreateBulkUpdateStoredProcedureStatement to PROCEDURE BulkUpdate{Plural}",
119            "Overrides CreateBulkDeleteStoredProcedureStatement to PROCEDURE BulkDelete{Plural}",
120        ],
121        [] as [&str; 0],
122    )
123});
124
125pub(crate) fn column_sql(name: &str) -> Option<&'static str> {
126    Some(match name {
127        "int" => "INTEGER",
128        "uuid" => "UUID",
129        "string" => "TEXT",
130        "bool" => "BOOLEAN",
131        "decimal" => "NUMERIC",
132        "float" => "DOUBLE PRECISION",
133        "datetime" => "TIMESTAMPTZ",
134        "binary" => "BYTEA",
135        _ => return None,
136    })
137}
138
139impl Driver for PostgresDriver {
140    fn connection(&self, options: Option<&HashMap<String, String>>) -> Arc<dyn Connection> {
141        PostgresConnection::from_options(options)
142    }
143
144    fn command(&self, connection: Arc<dyn Connection>) -> Arc<dyn Command> {
145        connection.create_command()
146    }
147
148    fn transaction(
149        &self,
150        connection: Arc<dyn Connection>,
151        isolation: IsolationLevel,
152    ) -> Arc<dyn Transaction> {
153        let conn = downcast_connection::<PostgresConnection>(connection).expect("postgres");
154        Arc::new(PostgresTransaction::new(conn.share(), isolation))
155    }
156
157    fn row_type_converters(
158        &self,
159    ) -> HashMap<String, Box<dyn TypeConverter<Value, Value> + Send + Sync>> {
160        converters::row_type_converters()
161    }
162
163    fn column_type(&self, name: &str) -> Result<String> {
164        column_sql(name)
165            .map(str::to_string)
166            .ok_or_else(|| DatasourceError::message(format!("Unknown postgres type \"{name}\"")))
167    }
168
169    fn type_converter(&self) -> Option<&dyn Mapping<String, String>> {
170        Some(&*sql_generate::TYPES)
171    }
172
173    fn statement_renderer(&self) -> Option<&StatementTextRenderer> {
174        Some(&STATEMENTS)
175    }
176
177    fn information(&self) -> Option<&Information> {
178        Some(&INFORMATION)
179    }
180}
181
182datasources::export_driver!(datasources_postgres_driver, PostgresDriver);
183
184#[cfg(test)]
185mod tests {
186    use super::PostgresDriver;
187    use datasources::Driver;
188
189    #[test]
190    fn maps_column_types() {
191        let driver = PostgresDriver;
192        assert_eq!(driver.column_type("int").unwrap(), "INTEGER");
193        assert_eq!(driver.column_type("uuid").unwrap(), "UUID");
194        assert_eq!(driver.column_type("string").unwrap(), "TEXT");
195        assert_eq!(driver.column_type("bool").unwrap(), "BOOLEAN");
196        assert_eq!(driver.column_type("decimal").unwrap(), "NUMERIC");
197        assert_eq!(driver.column_type("float").unwrap(), "DOUBLE PRECISION");
198        assert_eq!(driver.column_type("datetime").unwrap(), "TIMESTAMPTZ");
199        assert_eq!(driver.column_type("binary").unwrap(), "BYTEA");
200        assert!(driver
201            .column_type("xml")
202            .unwrap_err()
203            .to_string()
204            .contains("Unknown postgres type \"xml\""));
205    }
206
207    #[test]
208    fn maps_property_targets() {
209        use datasources::converters::RowTypeConverter;
210        use datasources::Value;
211        use std::collections::HashMap;
212
213        let driver = PostgresDriver;
214        let columns = HashMap::from([
215            ("Key".into(), "uuid".into()),
216            ("Active".into(), "bool".into()),
217            ("Login".into(), "string".into()),
218            ("Created".into(), "datetime".into()),
219        ]);
220        let converter = RowTypeConverter::from(|| driver.row_type_converters(), &columns);
221        let row = HashMap::from([
222            (
223                "Key".into(),
224                Value::from("11111111-1111-1111-1111-111111111111"),
225            ),
226            ("Active".into(), Value::Int(1)),
227            ("Login".into(), Value::from("ada")),
228            ("Created".into(), Value::from("2020-01-02T03:04:05.000Z")),
229        ]);
230        let converted = converter.convert(&row).unwrap();
231        assert!(matches!(converted.get("Key"), Some(Value::Uuid(_))));
232        assert_eq!(converted.get("Active"), Some(&Value::Bool(true)));
233        assert_eq!(converted.get("Login"), Some(&Value::from("ada")));
234        assert!(matches!(converted.get("Created"), Some(Value::DateTime(_))));
235        let stored = converter.reverse_convert(&converted).unwrap();
236        assert!(matches!(stored.get("Key"), Some(Value::Uuid(_))));
237        assert_eq!(stored.get("Active"), Some(&Value::Bool(true)));
238        assert_eq!(stored.get("Login"), Some(&Value::from("ada")));
239        assert_eq!(
240            stored.get("Created"),
241            Some(&Value::from("2020-01-02T03:04:05.000Z"))
242        );
243    }
244}