# Menangkap error PostgreSQL sebagai exception terklasifikasi di psycopg.
# Setiap class di psycopg.errors punya atribut .sqlstate; .diag membawa
# konteks tambahan (nama constraint, nama kolom) dari server.
import psycopg
from psycopg import errors

conn = psycopg.connect()
cur = conn.cursor()

cur.execute("""
    CREATE TABLE pelanggan (
        id    integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
        nama  text NOT NULL,
        email text NOT NULL UNIQUE,
        CHECK (length(nama) >= 3)
    )
""")
cur.execute("""
    CREATE TABLE pesanan (
        id           integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
        pelanggan_id integer NOT NULL REFERENCES pelanggan (id)
    )
""")
conn.commit()


def jalankan(label, query, params):
    """Jalankan satu pernyataan dan tangkap errornya per klasifikasi."""
    try:
        with conn.transaction():
            cur.execute(query, params)
        print(f"OK    : {label}")
    except errors.UniqueViolation as e:
        print(f"TOLAK : {label} -- email sudah dipakai "
              f"(sqlstate {e.sqlstate}, constraint {e.diag.constraint_name})")
    except errors.CheckViolation as e:
        print(f"TOLAK : {label} -- nama minimal 3 karakter "
              f"(sqlstate {e.sqlstate}, constraint {e.diag.constraint_name})")
    except errors.NotNullViolation as e:
        print(f"TOLAK : {label} -- kolom {e.diag.column_name} wajib diisi "
              f"(sqlstate {e.sqlstate})")
    except errors.ForeignKeyViolation as e:
        print(f"TOLAK : {label} -- pelanggan tidak ditemukan "
              f"(sqlstate {e.sqlstate}, constraint {e.diag.constraint_name})")
    except errors.UndefinedColumn as e:
        print(f"BUG   : {label} -- kolom tidak ada "
              f"(sqlstate {e.sqlstate})")
    except errors.DivisionByZero as e:
        print(f"BUG   : {label} -- pembagian dengan nol "
              f"(sqlstate {e.sqlstate})")
    except psycopg.Error as e:
        # Kode yang tidak kita antisipasi: catat sqlstate-nya,
        # bungkus ulang sebagai error aplikasi.
        print(f"ERROR : {label} -- sqlstate {e.sqlstate}")


jalankan("Budi mendaftar",
         "INSERT INTO pelanggan (nama, email) VALUES (%s, %s)",
         ("Budi", "budi@example.com"))
jalankan("Budi Duplikat mendaftar",
         "INSERT INTO pelanggan (nama, email) VALUES (%s, %s)",
         ("Budi Duplikat", "budi@example.com"))
jalankan("An mendaftar",
         "INSERT INTO pelanggan (nama, email) VALUES (%s, %s)",
         ("An", "ani@example.com"))
jalankan("pelanggan tanpa nama",
         "INSERT INTO pelanggan (nama, email) VALUES (%s, %s)",
         (None, "siti@example.com"))
jalankan("pesanan untuk pelanggan 1",
         "INSERT INTO pesanan (pelanggan_id) VALUES (%s)", (1,))
jalankan("pesanan untuk pelanggan 99",
         "INSERT INTO pesanan (pelanggan_id) VALUES (%s)", (99,))
jalankan("query dengan salah ketik kolom",
         "SELECT emel FROM pelanggan", ())
jalankan("query pembagian nol",
         "SELECT 1 / jumlah_nol FROM (VALUES (0)) AS t (jumlah_nol)", ())

cur.close()
conn.close()
