Changes between Initial Version and Version 1 of DatabaseProgramming


Ignore:
Timestamp:
06/07/26 22:04:33 (10 days ago)
Author:
231062
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • DatabaseProgramming

    v1 v1  
     1**Functions of common actions**
     2
     3Renting a book\\
     4Checks if user is eligible to rent a copy
     5{{{
     6CREATE OR REPLACE FUNCTION fn_rent_copy(
     7    p_user_id BIGINT,
     8    p_copy_id BIGINT,
     9    p_employee_id BIGINT
     10)
     11RETURNS BIGINT LANGUAGE plpgsql AS $$
     12DECLARE
     13    v_membership_id BIGINT;
     14    v_max_days INT;
     15    v_due TIMESTAMP;
     16    v_rental_id BIGINT;
     17BEGIN
     18    SELECT m.id, mp.max_rent_period_days
     19    INTO v_membership_id, v_max_days
     20    FROM Membership m
     21    JOIN MembershipPlan mp ON mp.id = m.membership_plan_id
     22    WHERE m.user_id = p_user_id
     23      AND m.cancelled_at IS NULL
     24      AND m.expires_at > NOW()
     25    LIMIT 1;
     26
     27    IF v_membership_id IS NULL THEN
     28        RAISE EXCEPTION 'No active membership';
     29    END IF;
     30
     31    IF NOT fn_can_rent(p_user_id) THEN
     32        RAISE EXCEPTION 'Max concurrent rentals reached';
     33    END IF;
     34
     35
     36    IF EXISTS (
     37        SELECT 1 FROM Rental
     38        WHERE resource_copy_id = p_copy_id
     39          AND returned_at IS NULL
     40    ) THEN
     41        RAISE EXCEPTION 'Copy already rented';
     42    END IF;
     43
     44    v_due := NOW() + (v_max_days || ' days')::INTERVAL;
     45
     46    INSERT INTO Rental(resource_copy_id, rented_at, due_at, membership_id, employee_id)
     47    VALUES (p_copy_id, NOW(), v_due, v_membership_id, p_employee_id)
     48    RETURNING id INTO v_rental_id;
     49
     50    RETURN v_rental_id;
     51END;
     52$$;
     53}}}
     54
     55Helper function to check if a user can rent
     56{{{
     57CREATE OR REPLACE FUNCTION fn_can_rent(p_user_id BIGINT)
     58RETURNS BOOLEAN LANGUAGE plpgsql AS $$
     59DECLARE
     60    v_max INT;
     61    v_current INT;
     62BEGIN
     63    SELECT mp.max_concurrent_rentals
     64    INTO v_max
     65    FROM Membership m
     66    JOIN MembershipPlan mp ON mp.id = m.membership_plan_id
     67    WHERE m.user_id = p_user_id
     68      AND m.cancelled_at IS NULL
     69      AND m.expires_at > NOW()
     70    LIMIT 1;
     71
     72    IF v_max IS NULL THEN
     73        RETURN FALSE;
     74    END IF;
     75
     76    SELECT COUNT(*)
     77    INTO v_current
     78    FROM Rental r
     79    JOIN Membership m ON m.id = r.membership_id
     80    WHERE m.user_id = p_user_id
     81      AND r.returned_at IS NULL;
     82
     83    RETURN v_current < v_max;
     84END;
     85$$;
     86}}}
     87
     88Returning copies
     89Calculates penalty upon book return if applicable
     90{{{
     91CREATE OR REPLACE FUNCTION fn_return_copy(p_rental_id BIGINT)
     92RETURNS NUMERIC LANGUAGE plpgsql AS $$
     93DECLARE
     94    v_due TIMESTAMP;
     95    v_penalty NUMERIC := 0;
     96    v_days_late INT;
     97BEGIN
     98    SELECT due_at INTO v_due
     99    FROM Rental
     100    WHERE id = p_rental_id;
     101
     102    IF v_due IS NULL THEN
     103        RAISE EXCEPTION 'Rental not found';
     104    END IF;
     105
     106    IF NOW() > v_due THEN
     107        v_days_late := EXTRACT(DAY FROM NOW() - v_due);
     108        v_penalty := v_days_late * 10; -- configurable
     109    END IF;
     110
     111    UPDATE Rental
     112    SET returned_at = NOW(),
     113        penalty = v_penalty
     114    WHERE id = p_rental_id;
     115
     116    RETURN v_penalty;
     117END;
     118$$;
     119}}}
     120
     121Registering to an event
     122{{{
     123CREATE OR REPLACE FUNCTION fn_register_to_event(
     124    p_user_id BIGINT,
     125    p_event_id BIGINT
     126)
     127RETURNS VOID LANGUAGE plpgsql AS $$
     128DECLARE
     129    v_capacity INT;
     130    v_count INT;
     131BEGIN
     132    SELECT capacity INTO v_capacity
     133    FROM Event
     134    WHERE id = p_event_id;
     135
     136    SELECT COUNT(*) INTO v_count
     137    FROM EventRegistration
     138    WHERE event_id = p_event_id;
     139
     140    IF v_count >= v_capacity THEN
     141        RAISE EXCEPTION 'Event is full';
     142    END IF;
     143
     144    INSERT INTO EventRegistration(user_id, event_id, registered_at)
     145    VALUES (p_user_id, p_event_id, NOW());
     146END;
     147$$;
     148}}}
     149
     150Adding an edition to the wishlist
     151{{{
     152CREATE OR REPLACE FUNCTION fn_add_to_wishlist(
     153    p_user_id BIGINT,
     154    p_isbn VARCHAR
     155)
     156RETURNS VOID LANGUAGE plpgsql AS $$
     157DECLARE
     158    v_wishlist_id BIGINT;
     159BEGIN
     160    SELECT id INTO v_wishlist_id
     161    FROM Wishlist
     162    WHERE user_id = p_user_id;
     163
     164    IF v_wishlist_id IS NULL THEN
     165        INSERT INTO Wishlist(user_id)
     166        VALUES (p_user_id)
     167        RETURNING id INTO v_wishlist_id;
     168    END IF;
     169
     170    INSERT INTO Wishlist_entry(edition_isbn, wishlist_id)
     171    VALUES (p_isbn, v_wishlist_id)
     172    ON CONFLICT DO NOTHING;
     173END;
     174$$;
     175}}}
     176
     177
     178Active membership check
     179{{{
     180CREATE OR REPLACE FUNCTION fn_has_active_membership(
     181    p_user_id BIGINT
     182)
     183RETURNS BOOLEAN
     184LANGUAGE plpgsql
     185AS $$
     186BEGIN
     187    RETURN EXISTS (
     188        SELECT 1
     189        FROM Membership
     190        WHERE user_id = p_user_id
     191          AND cancelled_at IS NULL
     192          AND expires_at > NOW()
     193    );
     194END;
     195$$;
     196}}}
     197
     198Create membership
     199{{{
     200CREATE OR REPLACE FUNCTION fn_create_membership(
     201    p_user_id BIGINT,
     202    p_membership_plan_id BIGINT
     203)
     204RETURNS BIGINT
     205LANGUAGE plpgsql
     206AS $$
     207DECLARE
     208    v_duration_days INT;
     209    v_membership_id BIGINT;
     210    v_started_at TIMESTAMP := NOW();
     211    v_expires_at TIMESTAMP;
     212BEGIN
     213    IF fn_has_active_membership(p_user_id) THEN
     214        RAISE EXCEPTION 'User already has an active membership';
     215    END IF;
     216
     217    SELECT duration_days
     218    INTO v_duration_days
     219    FROM MembershipPlan
     220    WHERE id = p_membership_plan_id;
     221
     222    IF v_duration_days IS NULL THEN
     223        RAISE EXCEPTION 'Membership plan not found';
     224    END IF;
     225
     226    v_expires_at := v_started_at +
     227                     (v_duration_days || ' days')::INTERVAL;
     228
     229    INSERT INTO Membership(
     230        user_id,
     231        membership_plan_id,
     232        started_at,
     233        expires_at,
     234        created_at
     235    )
     236    VALUES (
     237        p_user_id,
     238        p_membership_plan_id,
     239        v_started_at,
     240        v_expires_at,
     241        NOW()
     242    )
     243    RETURNING id INTO v_membership_id;
     244
     245    RETURN v_membership_id;
     246END;
     247$$;
     248}}}
     249
     250Renew membership
     251{{{
     252CREATE OR REPLACE FUNCTION fn_renew_membership(
     253    p_user_id BIGINT,
     254    p_new_plan_id BIGINT
     255)
     256RETURNS BIGINT
     257LANGUAGE plpgsql
     258AS $$
     259DECLARE
     260    v_current_membership_id BIGINT;
     261    v_duration_days INT;
     262    v_start_date TIMESTAMP;
     263    v_expires_at TIMESTAMP;
     264    v_new_membership_id BIGINT;
     265BEGIN
     266    SELECT id, expires_at
     267    INTO v_current_membership_id, v_start_date
     268    FROM Membership
     269    WHERE user_id = p_user_id
     270      AND cancelled_at IS NULL
     271    ORDER BY expires_at DESC
     272    LIMIT 1;
     273
     274    SELECT duration_days
     275    INTO v_duration_days
     276    FROM MembershipPlan
     277    WHERE id = p_new_plan_id;
     278
     279    IF v_duration_days IS NULL THEN
     280        RAISE EXCEPTION 'Membership plan not found';
     281    END IF;
     282
     283    IF v_start_date IS NULL OR v_start_date < NOW() THEN
     284        v_start_date := NOW();
     285    END IF;
     286
     287    v_expires_at := v_start_date +
     288                    (v_duration_days || ' days')::INTERVAL;
     289
     290    IF v_current_membership_id IS NOT NULL THEN
     291        UPDATE Membership
     292        SET cancelled_at = NOW()
     293        WHERE id = v_current_membership_id;
     294    END IF;
     295
     296    INSERT INTO Membership(
     297        user_id,
     298        membership_plan_id,
     299        started_at,
     300        expires_at,
     301        created_at
     302    )
     303    VALUES (
     304        p_user_id,
     305        p_new_plan_id,
     306        NOW(),
     307        v_expires_at,
     308        NOW()
     309    )
     310    RETURNING id INTO v_new_membership_id;
     311
     312    RETURN v_new_membership_id;
     313END;
     314$$;
     315}}}
     316
     317Cancel membership
     318{{{
     319CREATE OR REPLACE FUNCTION fn_cancel_membership(
     320    p_membership_id BIGINT
     321)
     322RETURNS VOID
     323LANGUAGE plpgsql
     324AS $$
     325BEGIN
     326    IF NOT EXISTS (
     327        SELECT 1
     328        FROM Membership
     329        WHERE id = p_membership_id
     330    ) THEN
     331        RAISE EXCEPTION 'Membership not found';
     332    END IF;
     333
     334    UPDATE Membership
     335    SET cancelled_at = NOW()
     336    WHERE id = p_membership_id
     337      AND cancelled_at IS NULL;
     338END;
     339$$;
     340}}}
     341
     342Get average book rating
     343{{{
     344CREATE OR REPLACE FUNCTION fn_avg_rating(p_isbn VARCHAR)
     345RETURNS NUMERIC LANGUAGE plpgsql AS $$
     346BEGIN
     347    RETURN (
     348        SELECT ROUND(AVG(rating), 2)
     349        FROM Review
     350        WHERE resource_edition_isbn = p_isbn
     351    );
     352END;
     353$$;
     354}}}
     355
     356**Triggers**
     357
     358Automatic penalty calculation on rental update
     359{{{
     360CREATE OR REPLACE FUNCTION trg_rental_return()
     361RETURNS TRIGGER
     362LANGUAGE plpgsql
     363AS $$
     364DECLARE
     365    v_days_late INT;
     366BEGIN
     367    IF OLD.returned_at IS NULL AND NEW.returned_at IS NOT NULL THEN
     368        IF NEW.due_at IS NOT NULL AND NEW.returned_at > NEW.due_at THEN
     369            v_days_late := EXTRACT(DAY FROM NEW.returned_at - NEW.due_at);
     370            NEW.penalty := COALESCE(NEW.penalty, 0) + (v_days_late * 10);
     371        END IF;
     372    END IF;
     373
     374    RETURN NEW;
     375END;
     376$$;
     377
     378CREATE TRIGGER trg_rental_return_update
     379BEFORE UPDATE OF returned_at ON Rental
     380FOR EACH ROW
     381EXECUTE FUNCTION trg_rental_return();
     382}}}
     383
     384User soft deletion
     385{{{
     386CREATE OR REPLACE FUNCTION trg_soft_delete_user()
     387RETURNS TRIGGER
     388LANGUAGE plpgsql
     389AS $$
     390BEGIN
     391    IF OLD.deleted THEN
     392        RETURN NULL;
     393    END IF;
     394
     395    UPDATE "User"
     396    SET
     397        first_name = '[deleted_user]',
     398        last_name  = '[deleted_user]',
     399        email      = '[deleted_user@example.com]',
     400        password   = '[deleted]',
     401        deleted    = TRUE,
     402        deleted_at = NOW(),
     403        updated_at = NOW()
     404    WHERE id = OLD.id;
     405
     406    RETURN NULL;
     407END;
     408$$;
     409
     410CREATE TRIGGER trg_soft_delete_user
     411BEFORE DELETE ON "User"
     412FOR EACH ROW
     413EXECUTE FUNCTION trg_soft_delete_user();
     414}}}