250 lines
6.5 KiB
Markdown
250 lines
6.5 KiB
Markdown
|
|
# Mejores Prácticas para PostgreSQL en OxiCloud
|
||
|
|
|
||
|
|
Este documento describe las mejores prácticas para el uso de PostgreSQL en OxiCloud, siguiendo recomendaciones oficiales y la guía ["Don't Do This"](https://wiki.postgresql.org/wiki/Don%27t_Do_This) de PostgreSQL.
|
||
|
|
|
||
|
|
## Diseño de Esquema
|
||
|
|
|
||
|
|
### Tipos de Datos
|
||
|
|
|
||
|
|
#### Uso de TEXT en lugar de VARCHAR(n)
|
||
|
|
|
||
|
|
OxiCloud utiliza el tipo `TEXT` en lugar de `VARCHAR(n)` con límites arbitrarios para campos de texto:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Recomendado ✅
|
||
|
|
username TEXT NOT NULL UNIQUE
|
||
|
|
|
||
|
|
-- Evitar ❌
|
||
|
|
username VARCHAR(32) NOT NULL UNIQUE
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- `TEXT` y `VARCHAR` tienen el mismo rendimiento y ocupan el mismo espacio.
|
||
|
|
- `VARCHAR(n)` impone un límite arbitrario que puede causar errores inesperados.
|
||
|
|
- PostgreSQL optimiza internamente ambos tipos de manera idéntica.
|
||
|
|
|
||
|
|
#### Uso de TIMESTAMPTZ para Fechas y Horas
|
||
|
|
|
||
|
|
OxiCloud utiliza `TIMESTAMP WITH TIME ZONE` (o `TIMESTAMPTZ`) para todos los campos de fecha/hora:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Recomendado ✅
|
||
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
|
|
|
||
|
|
-- Evitar ❌
|
||
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- `TIMESTAMPTZ` almacena un punto en el tiempo unívoco.
|
||
|
|
- Gestiona correctamente las zonas horarias y cambios de horario de verano.
|
||
|
|
- Evita problemas de ambigüedad al trabajar con diferentes husos horarios.
|
||
|
|
|
||
|
|
#### Evitar CHAR(n)
|
||
|
|
|
||
|
|
OxiCloud no utiliza el tipo `CHAR(n)` en ningún caso:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Recomendado ✅
|
||
|
|
country_code TEXT NOT NULL CHECK (length(country_code) = 2)
|
||
|
|
|
||
|
|
-- Evitar ❌
|
||
|
|
country_code CHAR(2) NOT NULL
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- `CHAR(n)` rellena con espacios hasta la longitud declarada.
|
||
|
|
- Este comportamiento puede causar problemas sutiles en comparaciones.
|
||
|
|
- Para valores de longitud fija, es mejor usar `TEXT` con una restricción CHECK.
|
||
|
|
|
||
|
|
#### Usar SERIAL con Precaución
|
||
|
|
|
||
|
|
OxiCloud utiliza `SERIAL` solo en casos específicos, prefiriendo `IDENTITY` cuando es posible:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Recomendado para PostgreSQL 10+ ✅
|
||
|
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
|
||
|
|
|
||
|
|
-- Alternativa aceptable para compatibilidad ✅
|
||
|
|
id SERIAL PRIMARY KEY
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- `SERIAL` tiene comportamientos extraños con gestión de dependencias y permisos.
|
||
|
|
- Las columnas `IDENTITY` (PostgreSQL 10+) ofrecen mejor integración con el sistema.
|
||
|
|
|
||
|
|
### Índices y Restricciones
|
||
|
|
|
||
|
|
#### Nombrado Consistente de Índices
|
||
|
|
|
||
|
|
OxiCloud sigue una convención de nomenclatura para índices:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Índice en una columna
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_table_column ON schema.table(column);
|
||
|
|
|
||
|
|
-- Índice en múltiples columnas
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_table_col1_col2 ON schema.table(col1, col2);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Uso de Restricciones Explícitas
|
||
|
|
|
||
|
|
OxiCloud define restricciones explícitas en lugar de depender de convenciones implícitas:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Restricción de unicidad
|
||
|
|
UNIQUE(user_id, item_id, item_type)
|
||
|
|
|
||
|
|
-- Restricción de comprobación
|
||
|
|
CHECK (storage_quota_bytes >= 0)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Consultas SQL
|
||
|
|
|
||
|
|
### Evitar NOT IN con Subconsultas
|
||
|
|
|
||
|
|
OxiCloud evita el uso de `NOT IN` con subconsultas:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Recomendado ✅
|
||
|
|
SELECT * FROM files
|
||
|
|
WHERE NOT EXISTS (SELECT 1 FROM deleted_files WHERE deleted_files.id = files.id);
|
||
|
|
|
||
|
|
-- Evitar ❌
|
||
|
|
SELECT * FROM files
|
||
|
|
WHERE id NOT IN (SELECT id FROM deleted_files);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- `NOT IN` se comporta de manera inesperada con valores NULL.
|
||
|
|
- `NOT EXISTS` es más eficiente y predecible.
|
||
|
|
|
||
|
|
### Usar BETWEEN con Precaución
|
||
|
|
|
||
|
|
OxiCloud evita `BETWEEN` para rangos de fechas, prefiriendo comparaciones explícitas:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Recomendado ✅
|
||
|
|
WHERE timestamp_col >= '2025-01-01' AND timestamp_col < '2025-01-02'
|
||
|
|
|
||
|
|
-- Evitar ❌
|
||
|
|
WHERE timestamp_col BETWEEN '2025-01-01' AND '2025-01-02'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- `BETWEEN` incluye ambos extremos, lo que puede ser problemático para rangos de tiempo.
|
||
|
|
- Usar `>=` y `<` es más claro para expresar rangos de tiempo.
|
||
|
|
|
||
|
|
## Transacciones
|
||
|
|
|
||
|
|
### Uso Explícito de Transacciones
|
||
|
|
|
||
|
|
OxiCloud implementa transacciones explícitas para operaciones que deben ser atómicas:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Ejemplo de transacción explícita
|
||
|
|
let mut tx = pool.begin().await?;
|
||
|
|
|
||
|
|
// Operaciones dentro de la transacción
|
||
|
|
sqlx::query("INSERT INTO users (id, username) VALUES ($1, $2)")
|
||
|
|
.bind(id)
|
||
|
|
.bind(username)
|
||
|
|
.execute(&mut *tx)
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
sqlx::query("INSERT INTO profiles (user_id, display_name) VALUES ($1, $2)")
|
||
|
|
.bind(id)
|
||
|
|
.bind(display_name)
|
||
|
|
.execute(&mut *tx)
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
// Confirmar la transacción
|
||
|
|
tx.commit().await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Manejo de Errores en Transacciones
|
||
|
|
|
||
|
|
Las transacciones incluyen manejo adecuado de errores con rollback automático:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let result = sqlx::Transaction::begin(&pool).await.and_then(|mut tx| async move {
|
||
|
|
// Operaciones dentro de la transacción
|
||
|
|
let result1 = operation1(&mut tx).await?;
|
||
|
|
let result2 = operation2(&mut tx).await?;
|
||
|
|
|
||
|
|
// Confirmar la transacción si todo fue exitoso
|
||
|
|
tx.commit().await?;
|
||
|
|
|
||
|
|
Ok((result1, result2))
|
||
|
|
}).await;
|
||
|
|
|
||
|
|
// Si ocurre un error, la transacción se revierte automáticamente
|
||
|
|
if let Err(e) = &result {
|
||
|
|
log::error!("Error en la transacción: {}", e);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Migraciones y Gestión de Esquema
|
||
|
|
|
||
|
|
### Separación del Esquema del Código
|
||
|
|
|
||
|
|
OxiCloud separa la definición del esquema del código de la aplicación:
|
||
|
|
|
||
|
|
```
|
||
|
|
OxiCloud/
|
||
|
|
├── migrations/ # Archivos SQL de migración
|
||
|
|
├── src/
|
||
|
|
├── bin/migrate.rs # Herramienta de migración
|
||
|
|
├── common/db.rs # Solo conecta a la BD, no crea esquema
|
||
|
|
```
|
||
|
|
|
||
|
|
### Uso de Migraciones Versionadas
|
||
|
|
|
||
|
|
Las migraciones siguen un formato versionado y se aplican secuencialmente:
|
||
|
|
|
||
|
|
```
|
||
|
|
20250408000000_initial_schema.sql
|
||
|
|
20250408000001_default_users.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
## Seguridad
|
||
|
|
|
||
|
|
### Uso de Consultas Parametrizadas
|
||
|
|
|
||
|
|
OxiCloud utiliza consultas parametrizadas para todas las operaciones SQL:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Recomendado ✅
|
||
|
|
sqlx::query("SELECT * FROM users WHERE username = $1")
|
||
|
|
.bind(username)
|
||
|
|
.fetch_one(&pool)
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
// Evitar ❌
|
||
|
|
sqlx::query(&format!("SELECT * FROM users WHERE username = '{}'", username))
|
||
|
|
.fetch_one(&pool)
|
||
|
|
.await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Razones:**
|
||
|
|
- Previene ataques de inyección SQL.
|
||
|
|
- Permite la reutilización de planes de consulta.
|
||
|
|
- Mejora el rendimiento general.
|
||
|
|
|
||
|
|
### Configuración de Autenticación Segura
|
||
|
|
|
||
|
|
OxiCloud evita el uso de autenticación `trust` para conexiones TCP/IP:
|
||
|
|
|
||
|
|
```
|
||
|
|
# pg_hba.conf recomendado ✅
|
||
|
|
hostssl all all 0.0.0.0/0 scram-sha-256
|
||
|
|
|
||
|
|
# Evitar ❌
|
||
|
|
host all all 0.0.0.0/0 trust
|
||
|
|
```
|
||
|
|
|
||
|
|
## Recursos Adicionales
|
||
|
|
|
||
|
|
- [Wiki PostgreSQL - Don't Do This](https://wiki.postgresql.org/wiki/Don%27t_Do_This)
|
||
|
|
- [Documentación oficial de PostgreSQL](https://www.postgresql.org/docs/)
|
||
|
|
- [Guía de migraciones de OxiCloud](DATABASE-MIGRATIONS.md)
|