fix: use double-quoted identifiers for NM/NN columns to preserve PostgreSQL case sensitivity
This commit is contained in:
121
SQLALCHEMY_QUOTING_FIX.md
Normal file
121
SQLALCHEMY_QUOTING_FIX.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# SQLAlchemy Column Name Quoting Fix for PostgreSQL
|
||||
|
||||
**Date:** March 22, 2026
|
||||
**Issue:** PostgreSQL case sensitivity with `mapped_column` parameter
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
SQLAlchemy's `name=` parameter generates unquoted column names by default, which PostgreSQL lowercases. When column definitions use uppercase CHECK constraints, they don't match the lowercased column names.
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
# Model definition
|
||||
NM: Mapped[Union[int, None]] = mapped_column(
|
||||
Integer,
|
||||
name="NM", # Generates: CREATE TABLE ... ( "NM" INTEGER, ...)
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
# SQL generated
|
||||
CREATE TABLE sessions (
|
||||
"NM" INTEGER, -- PostgreSQL lowercases to "nm"
|
||||
...
|
||||
CONSTRAINT ck_nm_range CHECK (NM IS NULL OR ...) -- References "NM" (uppercase)
|
||||
)
|
||||
```
|
||||
|
||||
**Error:** `column "nm" does not exist` (PostgreSQL can't find the uppercase constraint reference)
|
||||
|
||||
---
|
||||
|
||||
## Solution
|
||||
|
||||
Use **double-quoted identifiers** to force PostgreSQL to preserve case:
|
||||
|
||||
```python
|
||||
# CORRECT - Preserves case
|
||||
NM: Mapped[Union[int, None]] = mapped_column(
|
||||
Integer,
|
||||
name='"NM"', # Generates: CREATE TABLE ... ("NM" INTEGER, ...)
|
||||
nullable=True,
|
||||
)
|
||||
```
|
||||
|
||||
This generates:
|
||||
```sql
|
||||
CREATE TABLE sessions (
|
||||
"NM" INTEGER, -- Preserves "NM" in SQL
|
||||
...
|
||||
CONSTRAINT ck_nm_range CHECK ("NM" IS NULL OR ...) -- Matches quoted name
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Applied Fixes
|
||||
|
||||
### File: app/models/session.py
|
||||
|
||||
**Lines 108-119**: Fixed NM and NN column definitions to use `name='"NM"'` and `name='"NN"'`
|
||||
|
||||
**Before:**
|
||||
```python
|
||||
NM: Mapped[Union[int, None]] = mapped_column(
|
||||
Integer,
|
||||
name="NM", # ❌ Gets lowercased
|
||||
nullable=True,
|
||||
comment="Nilai Mentah (raw score) [0, 1000]",
|
||||
)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```python
|
||||
NM: Mapped[Union[int, None]] = mapped_column(
|
||||
Integer,
|
||||
name='"NM"', # ✅ Preserves case in PostgreSQL
|
||||
nullable=True,
|
||||
comment="Nilai Mentah (raw score) [0, 1000]",
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why This Works
|
||||
|
||||
1. **Double quotes in Python string** → SQL receives `"NM"` as literal
|
||||
2. **SQL parser preserves literal inside double quotes** → Column name stays `"NM"` (uppercase)
|
||||
3. **CHECK constraints match** → Both column and constraint use `"NM"`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This fix is **PostgreSQL-specific** - Other databases may handle this differently
|
||||
- For **MySQL**: Use backticks: ``NM`` instead of quotes
|
||||
- Best practice: Use lowercase identifiers with underscores: `nm_score`, `nn_score`
|
||||
- This issue affects **all uppercase column names** in CHECK constraints
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
After applying this fix, restart the application and verify:
|
||||
```bash
|
||||
cd /www/wwwroot/irt-bank-soal
|
||||
git pull
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pm2 restart irt-bank-soal
|
||||
pm2 logs irt-bank-soal --lines 20
|
||||
```
|
||||
|
||||
Expected: Application starts successfully without `"column nm does not exist"` error.
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [PostgreSQL Identifier Syntax](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-IDENTIFIERS)
|
||||
- [SQLAlchemy Identifier Quoting](https://docs.sqlalchemy.org/en/20/core/metadata.html#sqlalchemy.schema.sequence.Sequence)
|
||||
Reference in New Issue
Block a user