use super::base::DatabaseBackend;
#[derive(Debug, Default, Clone, Copy)]
pub struct PostgreSqlBackend;
#[must_use]
fn parse_optional_length(field_type: &str, base: &str) -> Option<Option<usize>> {
let field_type = field_type.trim();
if field_type == base {
return Some(None);
}
let rest = field_type.strip_prefix(base)?;
let length = rest.strip_prefix('(')?.strip_suffix(')')?.parse().ok()?;
Some(Some(length))
}
#[must_use]
fn escape_identifier(name: &str) -> String {
name.replace('"', "\"\"")
}
#[must_use]
fn escape_literal(value: &str) -> String {
value.replace('\'', "''")
}
impl DatabaseBackend for PostgreSqlBackend {
fn name(&self) -> &str {
"postgresql"
}
fn quote_name(&self, name: &str) -> String {
format!("\"{}\"", escape_identifier(name))
}
fn supports_covering_indexes(&self) -> bool {
true
}
fn supports_expression_indexes(&self) -> bool {
true
}
fn db_type(&self, field_type: &str) -> String {
if let Some(length) = parse_optional_length(field_type, "CharField") {
let length = length.unwrap_or(255);
return format!("varchar({length})");
}
match field_type.trim() {
"AutoField" => "serial".to_string(),
"BigAutoField" => "bigserial".to_string(),
"SmallAutoField" => "smallserial".to_string(),
"IntegerField" | "PositiveIntegerField" | "PositiveSmallIntegerField" => {
"integer".to_string()
}
"BigIntegerField" | "PositiveBigIntegerField" => "bigint".to_string(),
"SmallIntegerField" => "smallint".to_string(),
"BooleanField" | "NullBooleanField" => "boolean".to_string(),
"FloatField" => "double precision".to_string(),
"DateTimeField" => "timestamp with time zone".to_string(),
"TextField" => "text".to_string(),
"JSONField" => "jsonb".to_string(),
"UUIDField" => "uuid".to_string(),
"BinaryField" => "bytea".to_string(),
other => other.to_string(),
}
}
fn last_insert_id_sql(&self, table: &str, pk_name: &str) -> String {
format!(
"SELECT currval(pg_get_serial_sequence('{}', '{}'))",
escape_literal(table),
escape_literal(pk_name)
)
}
}
impl PostgreSqlBackend {
#[must_use]
pub fn supports_json(&self) -> bool {
self.supports_json_field()
}
#[must_use]
pub fn max_query_params(&self) -> usize {
65_535
}
#[must_use]
pub fn supports_returning(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::{DatabaseBackend, PostgreSqlBackend};
#[test]
fn postgres_backend_reports_feature_flags() {
let backend = PostgreSqlBackend;
assert_eq!(backend.name(), "postgresql");
assert!(backend.supports_transactions());
assert!(backend.supports_json());
assert_eq!(backend.max_query_params(), 65_535);
assert!(backend.supports_returning());
assert!(backend.supports_covering_indexes());
assert!(backend.supports_expression_indexes());
}
#[test]
fn test_postgresql_quote_name() {
let backend = PostgreSqlBackend;
assert_eq!(backend.quote_name("table_name"), "\"table_name\"");
assert_eq!(backend.quote_name("table\"name"), "\"table\"\"name\"");
}
#[test]
fn test_postgresql_db_type_mappings() {
let backend = PostgreSqlBackend;
assert_eq!(backend.db_type("AutoField"), "serial");
assert_eq!(backend.db_type("BigAutoField"), "bigserial");
assert_eq!(backend.db_type("CharField"), "varchar(255)");
assert_eq!(backend.db_type("CharField(64)"), "varchar(64)");
assert_eq!(backend.db_type("BooleanField"), "boolean");
assert_eq!(backend.db_type("DateTimeField"), "timestamp with time zone");
assert_eq!(backend.db_type("JSONField"), "jsonb");
assert_eq!(backend.db_type("UUIDField"), "uuid");
assert_eq!(backend.db_type("BinaryField"), "bytea");
}
#[test]
fn test_postgresql_last_insert_id() {
let backend = PostgreSqlBackend;
assert_eq!(
backend.last_insert_id_sql("widgets", "id"),
"SELECT currval(pg_get_serial_sequence('widgets', 'id'))"
);
}
}