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 default_value_converter(&self) -> &dyn datasources::DefaultValueConverter {
174 Self::defaults()
175 }
176
177 fn statement_renderer(&self) -> Option<&StatementTextRenderer> {
178 Some(&STATEMENTS)
179 }
180
181 fn information(&self) -> Option<&Information> {
182 Some(&INFORMATION)
183 }
184}
185
186datasources::export_driver!(datasources_postgres_driver, PostgresDriver);
187
188#[cfg(test)]
189mod tests {
190 use super::PostgresDriver;
191 use datasources::Driver;
192
193 #[test]
194 fn maps_column_types() {
195 let driver = PostgresDriver;
196 assert_eq!(driver.column_type("int").unwrap(), "INTEGER");
197 assert_eq!(driver.column_type("uuid").unwrap(), "UUID");
198 assert_eq!(driver.column_type("string").unwrap(), "TEXT");
199 assert_eq!(driver.column_type("bool").unwrap(), "BOOLEAN");
200 assert_eq!(driver.column_type("decimal").unwrap(), "NUMERIC");
201 assert_eq!(driver.column_type("float").unwrap(), "DOUBLE PRECISION");
202 assert_eq!(driver.column_type("datetime").unwrap(), "TIMESTAMPTZ");
203 assert_eq!(driver.column_type("binary").unwrap(), "BYTEA");
204 assert!(driver
205 .column_type("xml")
206 .unwrap_err()
207 .to_string()
208 .contains("Unknown postgres type \"xml\""));
209 }
210
211 #[test]
212 fn maps_property_targets() {
213 use datasources::converters::RowTypeConverter;
214 use datasources::Value;
215 use std::collections::HashMap;
216
217 let driver = PostgresDriver;
218 let columns = HashMap::from([
219 ("Key".into(), "uuid".into()),
220 ("Active".into(), "bool".into()),
221 ("Login".into(), "string".into()),
222 ("Created".into(), "datetime".into()),
223 ]);
224 let converter = RowTypeConverter::from(|| driver.row_type_converters(), &columns);
225 let row = HashMap::from([
226 (
227 "Key".into(),
228 Value::from("11111111-1111-1111-1111-111111111111"),
229 ),
230 ("Active".into(), Value::Int(1)),
231 ("Login".into(), Value::from("ada")),
232 ("Created".into(), Value::from("2020-01-02T03:04:05.000Z")),
233 ]);
234 let converted = converter.convert(&row).unwrap();
235 assert!(matches!(converted.get("Key"), Some(Value::Uuid(_))));
236 assert_eq!(converted.get("Active"), Some(&Value::Bool(true)));
237 assert_eq!(converted.get("Login"), Some(&Value::from("ada")));
238 assert!(matches!(converted.get("Created"), Some(Value::DateTime(_))));
239 let stored = converter.reverse_convert(&converted).unwrap();
240 assert!(matches!(stored.get("Key"), Some(Value::Uuid(_))));
241 assert_eq!(stored.get("Active"), Some(&Value::Bool(true)));
242 assert_eq!(stored.get("Login"), Some(&Value::from("ada")));
243 assert_eq!(
244 stored.get("Created"),
245 Some(&Value::from("2020-01-02T03:04:05.000Z"))
246 );
247 }
248}