Files
yellow-bank-soal/SQLALCHEMY_QUOTING_FIX.md

3.0 KiB

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:

# 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:

# CORRECT - Preserves case
NM: Mapped[Union[int, None]] = mapped_column(
    Integer,
    name='"NM"',  # Generates: CREATE TABLE ... ("NM" INTEGER, ...)
    nullable=True,
)

This generates:

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:

NM: Mapped[Union[int, None]] = mapped_column(
    Integer,
    name="NM",  # ❌ Gets lowercased
    nullable=True,
    comment="Nilai Mentah (raw score) [0, 1000]",
)

After:

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:

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.