Changes between Initial Version and Version 1 of AdvancedApp


Ignore:
Timestamp:
04/05/26 20:05:06 (6 days ago)
Author:
221511
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedApp

    v1 v1  
     1= Advanced Application Development =
     2
     3== Transactions ==
     4
     5A database transaction groups multiple operations into a single atomic unit: either all operations succeed and are committed, or all are rolled back if any error occurs. This prevents the database from being left in an inconsistent state.
     6
     7In the FRRUAS prototype (Python + psycopg2), we implement transactions using a context manager {{{get_transaction()}}} that automatically commits on success and rolls back on exception:
     8
     9{{{
     10@contextmanager
     11def get_transaction():
     12    p = _get_pool()
     13    conn = p.getconn()
     14    try:
     15        conn.autocommit = False
     16        yield conn
     17        conn.commit()
     18    except Exception:
     19        conn.rollback()
     20        raise
     21    finally:
     22        p.putconn(conn)
     23}}}
     24
     25=== Transaction 1: Make a Reservation (conflict check + insert) ===
     26
     27When a user creates a reservation, two operations must happen atomically: checking for time conflicts and inserting the reservation. Without a transaction, a race condition exists: two users could both pass the conflict check simultaneously, and both insert overlapping reservations.
     28
     29{{{
     30# uc_reserve.py — make_reservation()
     31
     32with get_transaction() as conn:
     33    with conn.cursor() as cur:
     34        # Re-check conflicts inside the transaction to prevent
     35        # race conditions (another user inserting between our
     36        # check and insert)
     37        cur.execute("""
     38            SELECT COUNT(*) FROM reservations
     39            WHERE resource_id = %s
     40              AND status IN ('approved', 'pending')
     41              AND start_time < %s AND end_time > %s
     42        """, (rid, end_dt, start_dt))
     43
     44        if cur.fetchone()[0] > 0:
     45            raise RuntimeError("Conflict: another reservation was just created for this time slot")
     46
     47        cur.execute("""
     48            INSERT INTO reservations
     49                (start_time, end_time, status, purpose, created_at, user_id, resource_id)
     50            VALUES (%s, %s, 'pending', %s, CURRENT_TIMESTAMP, %s, %s)
     51            RETURNING reservation_id, status, created_at
     52        """, (start_dt, end_dt, purpose, user_id, rid))
     53
     54        result = cur.fetchone()
     55# Transaction committed automatically on successful exit
     56}}}
     57
     58=== Transaction 2: Approve or Reject a Reservation (conflict re-check + status update) ===
     59
     60When an administrator approves a reservation, the system must verify no conflicting reservation was approved in the meantime. The conflict check and status update are wrapped in a single transaction to prevent two administrators from simultaneously approving overlapping reservations.
     61
     62{{{
     63# uc_approve.py — _review_reservation()
     64
     65with get_transaction() as conn:
     66    with conn.cursor() as cur:
     67        # For approvals, re-check conflicts within the transaction
     68        if new_status == "approved":
     69            cur.execute("""
     70                SELECT COUNT(*) FROM reservations
     71                WHERE resource_id = %s
     72                  AND reservation_id != %s
     73                  AND status = 'approved'
     74                  AND start_time < %s AND end_time > %s
     75            """, (res["resource_id"], res["reservation_id"],
     76                  res["end_time"], res["start_time"]))
     77
     78            if cur.fetchone()[0] > 0:
     79                raise RuntimeError("Conflict: another reservation was approved for this time slot")
     80
     81        cur.execute("""
     82            UPDATE reservations
     83            SET status = %s, approved_by = %s
     84            WHERE reservation_id = %s AND status = 'pending'
     85            RETURNING reservation_id, status
     86        """, (new_status, admin_user_id, res["reservation_id"]))
     87
     88        result = cur.fetchone()
     89# Transaction committed automatically
     90}}}
     91
     92=== Transaction 3: Register a New User (email uniqueness check + insert) ===
     93
     94User registration checks email uniqueness and inserts the new user atomically. Without a transaction, two simultaneous registrations with the same email could both pass the uniqueness check and both insert, violating the UNIQUE constraint.
     95
     96{{{
     97# uc_users.py — register_user()
     98
     99with get_transaction() as conn:
     100    with conn.cursor() as cur:
     101        cur.execute("SELECT COUNT(*) FROM users WHERE email = %s", (email,))
     102
     103        if cur.fetchone()[0] > 0:
     104            raise ValueError(f"A user with email '{email}' already exists.")
     105
     106        cur.execute("""
     107            INSERT INTO users (first_name, last_name, email, password, type_id)
     108            VALUES (%s, %s, %s, %s, %s)
     109            RETURNING user_id, first_name, last_name, email
     110        """, (first_name, last_name, email, hashed, type_id))
     111
     112        result = cur.fetchone()
     113# Transaction committed automatically
     114}}}
     115
     116== Database Connection Pooling ==
     117
     118Connection pooling reuses open database connections instead of creating a new one for every query. Opening a database connection involves TCP handshake, authentication, and session setup, which adds overhead. With pooling, these costs are paid once, and subsequent operations borrow pre-established connections from the pool.
     119
     120=== Implementation ===
     121
     122The prototype uses {{{psycopg2.pool.SimpleConnectionPool}}} to maintain a pool of reusable connections:
     123
     124{{{
     125# db.py
     126
     127from psycopg2 import pool
     128
     129_pool = None
     130
     131def _get_pool():
     132    global _pool
     133    if _pool is None:
     134        _pool = pool.SimpleConnectionPool(minconn=2, maxconn=10, **DB_CONFIG)
     135    return _pool
     136}}}
     137
     138'''Pool configuration:'''
     139
     140||'''Parameter'''||'''Value'''||'''Meaning'''||
     141||minconn||2||Minimum connections kept open and ready||
     142||maxconn||10||Maximum concurrent connections allowed||
     143
     144The pool is initialized lazily on first use and cleaned up when the application exits via {{{close_pool()}}}.
     145
     146=== Connection Usage ===
     147
     148Two context managers provide connections from the pool:
     149
     150'''Read operations''' use {{{get_connection()}}} with autocommit enabled:
     151{{{
     152@contextmanager
     153def get_connection():
     154    p = _get_pool()
     155    conn = p.getconn()
     156    try:
     157        conn.autocommit = True
     158        yield conn
     159    finally:
     160        conn.autocommit = False
     161        p.putconn(conn)
     162}}}
     163
     164'''Write operations''' use {{{get_transaction()}}} with explicit commit/rollback:
     165{{{
     166@contextmanager
     167def get_transaction():
     168    p = _get_pool()
     169    conn = p.getconn()
     170    try:
     171        conn.autocommit = False
     172        yield conn
     173        conn.commit()
     174    except Exception:
     175        conn.rollback()
     176        raise
     177    finally:
     178        p.putconn(conn)
     179}}}
     180
     181In both cases, the connection is automatically returned to the pool when the {{{with}}} block exits, ensuring no connection leaks.
     182
     183=== Example: Read Operation Using Pool ===
     184
     185{{{
     186# uc_browse.py — fetching resources
     187
     188with get_connection() as conn:
     189    with conn.cursor() as cur:
     190        cur.execute("""
     191            SELECT r.resource_id, r.name, rt.type_name, l.building, l.room
     192            FROM resources r
     193            JOIN resource_types rt ON r.type_id = rt.type_id
     194            LEFT JOIN locations l ON r.location_id = l.location_id
     195            ORDER BY rt.type_name, r.name
     196        """)
     197        resources = cur.fetchall()
     198# Connection returned to pool automatically
     199}}}
     200
     201=== Pool Lifecycle ===
     202
     203{{{
     204# main.py
     205
     206def main():
     207    try:
     208        while True:
     209            user = login()       # uses pooled connections
     210            if user is None:
     211                break
     212            main_menu(...)       # all operations use pooled connections
     213    except KeyboardInterrupt:
     214        pass
     215    finally:
     216        close_pool()             # close all connections on exit
     217}}}