# Savepoint dari aplikasi: conn.transaction() yang dipanggil di dalam
# transaksi yang sedang berjalan otomatis menjadi SAVEPOINT.
import psycopg
from psycopg import errors

conn = psycopg.connect()
conn.execute("""
    CREATE TABLE stok (
        id     integer PRIMARY KEY,
        barang text NOT NULL,
        jumlah integer NOT NULL
    )
""")
conn.execute(
    "INSERT INTO stok VALUES (1, 'Gula pasir', 100), (2, 'Kopi bubuk', 80)")
conn.commit()

with conn.transaction():
    # Transaksi luar: mengurangi stok untuk satu pesanan.
    conn.execute("UPDATE stok SET jumlah = jumlah - 10 WHERE id = 1")
    try:
        with conn.transaction():
            # Transaksi dalam -> SAVEPOINT: pekerjaan tambahan
            # yang boleh gagal sendiri tanpa membatalkan pesanan.
            # Pembagian dengan nol mensimulasikan bug di kode diskon.
            conn.execute(
                "UPDATE stok SET jumlah = jumlah - 100 / 0 WHERE id = 2")
    except errors.DivisionByZero as e:
        print(f"bug diskon tertangkap, rollback ke savepoint "
              f"(sqlstate {e.sqlstate})")
    # Pekerjaan luar berlanjut seolah bagian dalam tidak pernah terjadi.
    conn.execute("UPDATE stok SET jumlah = jumlah - 5 WHERE id = 1")

cur = conn.execute("SELECT id, barang, jumlah FROM stok ORDER BY id")
for id_barang, barang, jumlah in cur.fetchall():
    print(f"{id_barang} {barang}: {jumlah}")

conn.close()
