To transform how movie and TV enthusiasts discover information, share news, and connect with one another, aiming to become the largest collaborative platform for entertainment lovers to engage and stay updated with the latest entertainment news.
A4: Conceptual Data Model
This section provides a description of the entities and relationships within the NewsHub project and its database specification.
1. Class diagram
2. Additional Business Rules
Identifier
Description
BR08
A user can only like a news item once
BR09
A user can only like a comment once
BR10
A blocked user must provide an appeal description if they have appealled to be unblocked
BR11
A blocked user can only provide an appeal description if they have appealled to be unblocked
BR12
An administrator is the only one able to grant an influencer with a privilege
BR13
An administrator is the only one able to access the reports
A5: Relational Schema, validation and schema refinement
This section contains the relational schema derived from the conceptual data model. The schema defines the structure of the relational database, ensures its integrity, and improves its design.
1. Relational Schema
Relation reference
Relation Compact Notation
R01
user(id, name NN, email UK NN, password NN, reputation NN DF 0 CK reputation >= 0, image_id -> image)
R02
administrator(id, name NN, email UK NN, password NN, image_id -> image)
R03
influencer(user_id -> user, start_date NN DF Today, has_privilege NN DF false)
R04
blocked(user_id -> user, date NN DF Today, appeal NN DF false, appeal_description)
notification(id, sender_id -> user_id NN, receiver_id -> user_id NN, date NN DF Today, viewed NN DF False, notification_type NN CK notification_type IN Notification_types)
R15
notification_comment(notification_id -> notification, comment_id -> comment UK NN)
R16
notification_like(notification_id -> notification, like_id -> like UK NN)
id → {content, published_date, author_id, image_id}
NORMAL FORM
BCNF
TABLE R06
comment
Keys
{ id }
Functional Dependencies:
FD0601
id → { content, published_date, news_id, author_id, image_id}
NORMAL FORM
BCNF
TABLE R07
tag
Keys
{ id }, { name }
Functional Dependencies:
FD0701
id → {name}
FD0702
name → {id}
NORMAL FORM
BCNF
TABLE R08
follow
Keys
{ follower_id, followed_id }
Functional Dependencies:
None
NORMAL FORM
BCNF
TABLE R09
follow_tag
Keys
{ user_id, tag_id }
Functional Dependencies:
None
NORMAL FORM
BCNF
TABLE R10
tag_news
Keys
{ news_id, tag_id }
Functional Dependencies:
None
NORMAL FORM
BCNF
TABLE R11
favorite
Keys
{ user_id, news_id }
Functional Dependencies:
None
NORMAL FORM
BCNF
TABLE R12
report
Keys
{ id }
Functional Dependencies:
FD1201
id → {description, created_date, user_id, comment_id, news_id}
NORMAL FORM
BCNF
TABLE R13
like
Keys
{ id }
Functional Dependencies:
FD1301
id → { sender_id, comment_id, news_id}
NORMAL FORM
BCNF
TABLE R14
notification
Keys
{ id }
Functional Dependencies:
FD1401
id → { sender_id, receiver_id, date, viewed, notification_type }
NORMAL FORM
BCNF
TABLE R15
notification_comment
Keys
{ notification_id }, { comment_id}
Functional Dependencies:
FD1501
notification_id → { comment_id }
FD1502
comment_id → { notification_id }
NORMAL FORM
BCNF
TABLE R16
notification_like
Keys
{ notification_id }, { like_id }
Functional Dependencies:
FD1601
notification_id → { like_id }
FD1602
like_id → { notification_id }
NORMAL FORM
BCNF
TABLE R17
image
Keys
{ id }, { image_path }
Functional Dependencies:
FD1701
id → {image_path}
FD1702
image_path → { id }
NORMAL FORM
BCNF
Because all relations are in the Boyce–Codd Normal Form (BCNF), the relational schema is also in the BCNF and, therefore, the schema does not need to be further normalized.
A6: Indexes, triggers, transactions and database population
This artifact contains the database workload, the physical schema, indexes, triggers, and the definitions of functions and transactions required to ensure data integrity. It also includes the complete database creation script, including indexes and triggers.
1. Database Workload
Relation reference
Relation Name
Order of magnitude
Estimated growth
R01
user
10 k
10 / day
R02
administrator
10
1 / semester
R03
influencer
1 k
1 / day
R04
blocked
100
1 / month
R05
news
100 k
1 k / day
R06
comment
1 M
10 k / day
R07
tag
10
1 / month
R08
follow
100 k
100 / day
R09
follow_tag
10 k
10 / day
R10
tag_news
100 k
1 k / day
R11
favorite
100 k
1 k / day
R12
report
100
10 / month
R13
like
10 M
100 k / day
R14
notification
10 M
100 k / day
R15
notification_comment
1 M
10 k / day
R16
notification_like
10 M
100 k / day
R17
image
100 k
1 k / day
2. Proposed Indices
2.1. Performance Indices
Index
IDX01
Relation
news
Attribute
published_date
Type
B-tree
Cardinality
medium
Clustering
No
Justification
The 'news' table is very large, and several queries frequently sort the news by date to generate the recent news feed. Since sorting by date is required, a B-tree index is the most appropriate choice. However, due to the high update frequency, clustering is not ideal as it could negatively impact performance.
SQL code
CREATEINDEXidx_news_dateON news USING btree (published_date);
Index
IDX02
Relation
like
Attribute
news_id, sender_id
Type
B-tree
Cardinality
medium
Clustering
No
Justification
The 'like' table is one of the largest, and many queries frequently filter by news to generate the top news feed. Additionally, several queries need to filter by sender to check whether the news has already been liked by the user or not. Since it is a multi-column index, hash indexing is not supported. Due to the high update frequency, clustering is not ideal, as it could negatively impact performance.
SQL code
CREATEINDEXidx_like_newsON likes USING btree (news_id, sender_id) WHERE news_id IS NOT NULL;
Index
IDX03
Relation
notification
Attribute
receiver_id, viewed
Type
B-tree
Cardinality
medium
Clustering
No
Justification
The 'notification' table is one of the largest, and many queries are frequently made to check if a user has received a new notification. Since it uses a multi-column index, hash indexing is not supported. Due to the high update frequency, clustering is not ideal, as it could negatively impact performance.
SQL code
CREATEINDEXidx_notificationON notifications USING btree (receiver_id, viewed);
2.2. Full-text Search Indices
Index
IDX04
Relation
news
Attribute
content, author_id
Type
GIN
Clustering
No
Justification
To provide full-text search features to look for news based on matching content or author. The index type is GIN because the indexed fields are not expected to change often.
SQL code
ALTERTABLE news ADD COLUMN ts_content_user TSVECTOR;
CREATE OR REPLACEFUNCTIONnews_content_user_update() RETURNS TRIGGER AS $$
DECLARE
user_name_value TEXT;
BEGINSELECT COALESCE(user_name, 'Anonymous') INTO user_name_value FROM users WHEREusers.id=NEW.author_id;
IF TG_OP ='INSERT' THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
IF TG_OP ='UPDATE' THEN
IF (NEW.content<>OLD.contentORNEW.author_id<>OLD.author_id) THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
END IF;
RETURN NEW;
END $$
LANGUAGE plpgsql;
CREATETRIGGERnews_content_user_update
BEFORE INSERT ORUPDATEON news
FOR EACH ROW
EXECUTE PROCEDURE news_content_user_update();
CREATEINDEXidx_news_contentON news USING GIN (ts_content_user);
Index
IDX05
Relation
comment
Attribute
content, author_id
Type
GIN
Clustering
No
Justification
To provide full-text search features to look for comments based on matching content or author. The index type is GIN because the indexed fields are not expected to change often
SQL code
ALTERTABLE comment ADD COLUMN ts_content_user TSVECTOR;
CREATE OR REPLACEFUNCTIONcomment_content_user_update() RETURNS TRIGGER AS $$
DECLARE
user_name_value TEXT;
BEGINSELECT COALESCE(user_name, 'Anonymous') INTO user_name_value FROM users WHEREusers.id=NEW.author_id;
IF TG_OP ='INSERT' THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
IF TG_OP ='UPDATE' THEN
IF (NEW.content<>OLD.contentORNEW.author_id<>OLD.author_id) THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
END IF;
RETURN NEW;
END $$
LANGUAGE plpgsql;
CREATETRIGGERcomment_content_user_update
BEFORE INSERT ORUPDATEON comment
FOR EACH ROW
EXECUTE PROCEDURE comment_content_user_update();
CREATEINDEXidx_comment_contentON comment USING GIN (ts_content_user);
3. Triggers
Trigger
TRIGGER01
Description
Influencers are a percentage of the users with the highest reputation on the platform
SQL code
CREATEFUNCTIONmanage_influencers()
RETURNS TRIGGER AS $$
DECLARE
total_users INTEGER;
total_influencers INTEGER;
min_influencer RECORD;
max_user RECORD;
BEGINSELECTCOUNT(*) INTO total_users FROM users;
SELECTCOUNT(*) INTO total_influencers FROM influencer;
SELECT id, reputation
INTO min_influencer
FROM users
WHERE id IN (SELECT user_id FROM influencer)
ORDER BY reputation ASCLIMIT1;
IF TG_OP ='INSERT' THEN
IF total_influencers < CEIL(0.1* total_users) THEN
IF NEW.reputation>=100 THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
END IF;
ELSIF NEW.reputation>min_influencer.reputation THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
DELETEFROM influencer
WHERE user_id =min_influencer.id;
END IF;
END IF;
IF TG_OP ='UPDATE' THEN
IF total_influencers < CEIL(0.1* total_users) THEN
IF NEW.reputation>=100ANDNEW.id NOT IN (SELECT user_id FROM influencer) THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
END IF;
ELSIF NEW.reputation>OLD.reputationANDNEW.id NOT IN (SELECT user_id FROM influencer) THEN
IF NEW.reputation>min_influencer.reputation THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
DELETEFROM influencer
WHERE user_id =min_influencer.id;
END IF;
ELSIF NEW.reputation<OLD.reputationANDNEW.idIN (SELECT user_id FROM influencer) THEN
IF NEW.reputation<100 THEN
DELETEFROM influencer
WHERE user_id =NEW.id;
SELECT id, reputation INTO max_user
FROM users
WHERE id NOT IN (SELECT user_id FROM influencer)
ORDER BY reputation DESCLIMIT1;
IF max_user IS NOT NULLANDmax_user.reputation>=100 THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (max_user.id, now(), FALSE);
END IF;
ELSIF NEW.reputation<min_influencer.reputation THEN
SELECT id, reputation INTO max_user
FROM users
WHERE id NOT IN (SELECT user_id FROM influencer)
ORDER BY reputation DESCLIMIT1;
IF max_user IS NOT NULLANDNEW.reputation<max_user.reputationANDmax_user.reputation>=100 THEN
DELETEFROM influencer
WHERE user_id =NEW.id;
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (max_user.id, now(), FALSE);
END IF;
END IF;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_influencers
AFTER INSERT ORUPDATEON users
FOR EACH ROW
EXECUTE FUNCTION manage_influencers();
Trigger
TRIGGER02
Description
Likes generate notifications for the users
SQL code
CREATEFUNCTIONmanage_like_notification()
RETURNS TRIGGER AS $$
DECLARE
notification_id INTEGER;
receiver_id INTEGER;
BEGIN
IF NEW.news_idIS NOT NULL THEN
SELECT author_id INTO receiver_id
FROM news
WHERE id =NEW.news_id;
INSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.sender_id, receiver_id, now(), FALSE, 'Like_notification')
RETURNING id INTO notification_id;
INSERT INTO notifications_like (notification_id, like_id)
VALUES (notification_id, NEW.id);
ELSIF NEW.comment_idIS NOT NULL THEN
SELECT author_id INTO receiver_id
FROM comment
WHERE id =NEW.comment_id;
INSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.sender_id, receiver_id, now(), FALSE, 'Like_notification')
RETURNING id INTO notification_id;
INSERT INTO notifications_like (notification_id, like_id)
VALUES (notification_id, NEW.id);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_like_notification
AFTER INSERT ON likes
FOR EACH ROW
EXECUTE FUNCTION manage_like_notification();
Trigger
TRIGGER03
Description
Comments generate notifications for the users
SQL code
CREATEFUNCTIONmanage_comment_notification()
RETURNS TRIGGER AS $$
DECLARE
notification_id INTEGER;
receiver_id INTEGER;
BEGINSELECT author_id INTO receiver_id
FROM news
WHERE id =NEW.news_id;
INSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.author_id, receiver_id, now(), FALSE, 'Comment_notification')
RETURNING id INTO notification_id;
INSERT INTO notifications_comment (notification_id, comment_id)
VALUES (notification_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_comment_notification
AFTER INSERT ON comment
FOR EACH ROW
EXECUTE FUNCTION manage_comment_notification();
Trigger
TRIGGER04
Description
Follows generate notifications for the users
SQL code
CREATEFUNCTIONmanage_follow_notification()
RETURNS TRIGGER AS $$
DECLARE
notification_id INTEGER;
BEGININSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.follower_id, NEW.followed_id, now(), FALSE, 'Follow_notification')
RETURNING id INTO notification_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_follow_notification
AFTER INSERT ON follow
FOR EACH ROW
EXECUTE FUNCTION manage_follow_notification();
Trigger
TRIGGER05
Description
Deleting the like notification should remove the associated main notification
SQL code
CREATEFUNCTIONdelete_notification()
RETURNS TRIGGER AS $$
BEGINDELETEFROM notifications
WHERE id =OLD.notification_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_notification_like
AFTER DELETEON notifications_like
FOR EACH ROW
EXECUTE FUNCTION delete_notification();
Trigger
TRIGGER06
Description
Deleting the comment notification should remove the associated main notification
SQL code
CREATEFUNCTIONdelete_notification()
RETURNS TRIGGER AS $$
BEGINDELETEFROM notifications
WHERE id =OLD.notification_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_notification_comment
AFTER DELETEON notifications_comment
FOR EACH ROW
EXECUTE FUNCTION delete_notification();
Trigger
TRIGGER07
Description
Deleting a follow should remove the associated notification
SQL code
CREATEFUNCTIONdelete_notification_follow()
RETURNS TRIGGER AS $$
BEGINDELETEFROM notifications
WHERE sender_id =OLD.follower_idAND receiver_id =OLD.followed_idAND notification_type ='Follow_notification';
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_notification_follow
AFTER DELETEON follow
FOR EACH ROW
EXECUTE FUNCTION delete_notification_follow();
Trigger
TRIGGER08
Description
A user can like a news item only once
SQL code
CREATEFUNCTIONlike_news_once()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP ='UPDATE'AND (OLD.sender_id=NEW.sender_idANDOLD.news_id=NEW.news_id) THEN
RETURN NEW;
END IF;
IF EXISTS (
SELECT1FROM likes
WHERE sender_id =NEW.sender_idAND news_id =NEW.news_id
) THEN
RAISE EXCEPTION 'User already liked this news';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERlike_news_once
BEFORE INSERT ORUPDATEON likes
FOR EACH ROW
EXECUTE FUNCTION like_news_once();
Trigger
TRIGGER09
Description
A user can like a comment only once
SQL code
CREATEFUNCTIONlike_comment_once()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP ='UPDATE'AND (OLD.sender_id=NEW.sender_idANDOLD.comment_id=NEW.comment_id) THEN
RETURN NEW;
END IF;
IF EXISTS (
SELECT1FROM likes
WHERE sender_id =NEW.sender_idAND comment_id =NEW.comment_id
) THEN
RAISE EXCEPTION 'User already liked this comment';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERlike_comment_once
BEFORE INSERT ORUPDATEON likes
FOR EACH ROW
EXECUTE FUNCTION like_comment_once();
Trigger
TRIGGER10
Description
A user cannot follow themselves
SQL code
CREATEFUNCTIONmanage_self_follow()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.follower_id=NEW.followed_id THEN
RAISE EXCEPTION 'User cannot follow themselves';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_self_follow
BEFORE INSERT ORUPDATEON follow
FOR EACH ROW
EXECUTE FUNCTION manage_self_follow();
Trigger
TRIGGER11
Description
A news item cannot be deleted if it has any likes or comments
SQL code
CREATEFUNCTIONdelete_news()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT1FROM likes
WHERE news_id =OLD.id
) THEN
RAISE EXCEPTION 'News has likes';
ELSIF EXISTS (
SELECT1FROM comment
WHERE news_id =OLD.id
) THEN
RAISE EXCEPTION 'News has comments';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_news
BEFORE DELETEON news
FOR EACH ROW
EXECUTE FUNCTION delete_news();
Trigger
TRIGGER12
Description
A comment cannot be deleted if it has any likes
SQL code
CREATEFUNCTIONdelete_comment()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT1FROM likes
WHERE comment_id =OLD.id
) THEN
RAISE EXCEPTION 'Comment has likes';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_comment
BEFORE DELETEON comment
FOR EACH ROW
EXECUTE FUNCTION delete_comment();
Trigger
TRIGGER13
Description
A user's reputation should be calculated based on the likes they receive on their news and comments
SQL code
CREATEFUNCTIONupdate_reputation()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP ='INSERT' THEN
IF NEW.news_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation+1FROM news n
WHEREn.id=NEW.news_idANDu.id=n.author_id;
ELSIF NEW.comment_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation+1FROM comment c
WHEREc.id=NEW.comment_idANDu.id=c.author_id;
END IF;
ELSIF TG_OP ='DELETE' THEN
IF OLD.news_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation-1FROM news n
WHEREn.id=OLD.news_idANDu.id=n.author_id;
ELSIF OLD.comment_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation-1FROM comment c
WHEREc.id=OLD.comment_idANDu.id=c.author_id;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERupdate_reputation
AFTER INSERT ORDELETEON likes
FOR EACH ROW
EXECUTE FUNCTION update_reputation();
Trigger
TRIGGER14
Description
A comment publication date must be later than the news publication date
SQL code
CREATEFUNCTIONcheck_comment_date()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT1FROM news n
WHEREn.id=NEW.news_idANDn.published_date>NEW.published_date
) THEN
RAISE EXCEPTION 'Comment publication date must be later than the news publication date';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERcheck_comment_date
BEFORE INSERT ORUPDATEON comment
FOR EACH ROW
EXECUTE FUNCTION check_comment_date();
4. Transactions
SQL Reference
Deleting a user
Justification
To maintain consistency, a transaction is used to ensure all operations execute as a single unit, preventing partial updates if any errors occur. In case of an error, the transaction will ROLLBACK, leaving the user's data unchanged. When deleting a user, it’s essential to anonymize their shared contributions (such as news, comments, and likes). The Read Committed isolation level provides the necessary consistency for this transaction without the added overhead of higher isolation levels, as no data is read during this process.
Isolation level
READ COMMITTED
Complete SQL Code
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DELETEFROM users
WHERE id = $user_id;
UPDATE news
SET author_id =NULLWHERE author_id = $user_id;
UPDATE comment
SET author_id =NULLWHERE author_id = $user_id;
UPDATE likes
SET sender_id =NULLWHERE sender_id = $user_id;
END TRANSACTION;
SQL Reference
Get news and the comments associated with the news
Justification
To maintain consistency, a transaction is used to ensure all operations execute seamlessly. If an error occurs, the transaction will ROLLBACK, preventing any partial results (e.g., ensuring the user does not see a news without its comments). The transaction includes viewing a news item and its associated comments, both of which require only read operations, making it suitable for a Read Only transaction that uses only select statements. Repeatable Read gives data consistency without increasing the overhead because the data does not change frequently.
Isolation level
REPEATABLE READ READ ONLY
Complete SQL Code
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY;
SELECT*FROM news
WHERE id = $news_id;
SELECT*FROM comment
WHERE news_id = $news_id;
END TRANSACTION;
SQL Reference
See other users new feed
Justification
To ensure consistency, a transaction is used so that all operations execute together without errors. If an error occurs, a ROLLBACK will be issued, ensuring that users do not see incomplete or inconsistent data (e.g., news from different users). This transaction retrieves the user's info and their news item, and since it only requires select statements, it can be set as Read Only. Repeatable Read gives data consistency without increasing the overhead because the data does not change frequently.
Isolation level
REPEATABLE READ READ ONLY
Complete SQL Code
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY;
SELECT*FROM users
WHERE id = $user_id;
SELECT*FROM news
WHERE author_id = $user_id;
END TRANSACTION;
SQL Reference
See news details
Justification
To maintain consistency, a transaction is used to ensure that all operations execute successfully as a single unit. If an error occurs, a ROLLBACK is issued, preventing users from seeing incomplete data (e.g., a partially loaded news item). This transaction retrieves the news details. Since it only involves select statements, it is designated as Read Only.
Isolation level
SERIALIZABLE READ ONLY
Complete SQL Code
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
SELECT content, published_date, author_id, image_id
FROM news
WHERE id = $news_id;
SELECT user_name
FROM users
WHERE id = $author_id;
SELECT image_path
FROM images
WHERE id = $image_id;
SELECT like_count
FROM news_like_counts
WHERE news_id = $news_id;
SELECTtag.nameFROM tag_news JOIN tag ON tag_id =tag.idWHERE news_id = $news_id;
END TRANSACTION;
SQL Reference
See comment details
Justification
To ensure consistency, a transaction is used to guarantee that all operations execute successfully as a single unit. If an error occurs, a ROLLBACK is issued, preventing users from seeing incomplete data (e.g., a partially loaded comment). This transaction retrieves the comment details and is designated as Read Only since it only involves select statements.
Isolation level
SERIALIZABLE READ ONLY
Complete SQL Code
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
SELECT content, published_date, news_id, author_id, img_id FROM comment WHERE id = $comment_id;
SELECT user_name
FROM users
WHERE id = $author_id;
SELECT image_path
FROM images
WHERE id = $img_id;
SELECT like_count
FROM comments_like_counts
WHERE comment_id = $comment_id;
END TRANSACTION;
CREATESCHEMAIF NOT EXISTS lbaw24142;
SET search_path TO lbaw24142;
------------------------------------------- Drop previous schema-----------------------------------------DROPTRIGGER IF EXISTS news_content_user_update ON news;
DROPTRIGGER IF EXISTS comment_content_user_update ON comment;
DROPTRIGGER IF EXISTS manage_influencers ON users;
DROPTRIGGER IF EXISTS manage_like_notification ON likes;
DROPTRIGGER IF EXISTS manage_comment_notification ON comment;
DROPTRIGGER IF EXISTS manage_follow_notification ON follow;
DROPTRIGGER IF EXISTS delete_notification_like ON notifications_like;
DROPTRIGGER IF EXISTS delete_notification_comment ON notifications_comment;
DROPTRIGGER IF EXISTS delete_notification_follow ON follow;
DROPTRIGGER IF EXISTS like_news_once ON likes;
DROPTRIGGER IF EXISTS like_comment_once ON likes;
DROPTRIGGER IF EXISTS manage_self_follow ON follow;
DROPTRIGGER IF EXISTS delete_news ON news;
DROPTRIGGER IF EXISTS delete_comment ON comment;
DROPTRIGGER IF EXISTS update_reputation ON likes;
DROPTRIGGER IF EXISTS check_comment_date ON comment;
DROPINDEX IF EXISTS idx_news_date;
DROPINDEX IF EXISTS idx_like_news;
DROPINDEX IF EXISTS idx_notification;
DROPINDEX IF EXISTS idx_news_content;
DROPINDEX IF EXISTS idx_comment_content;
DROP MATERIALIZED VIEW IF EXISTS news_like_counts CASCADE;
DROP MATERIALIZED VIEW IF EXISTS comments_like_counts CASCADE;
DROPFUNCTION IF EXISTS manage_influencers CASCADE;
DROPFUNCTION IF EXISTS manage_like_notification CASCADE;
DROPFUNCTION IF EXISTS manage_comment_notification CASCADE;
DROPFUNCTION IF EXISTS manage_follow_notification CASCADE;
DROPFUNCTION IF EXISTS delete_notification CASCADE;
DROPFUNCTION IF EXISTS delete_notification_follow CASCADE;
DROPFUNCTION IF EXISTS like_news_once CASCADE;
DROPFUNCTION IF EXISTS like_comment_once CASCADE;
DROPFUNCTION IF EXISTS manage_self_follow CASCADE;
DROPFUNCTION IF EXISTS delete_news CASCADE;
DROPFUNCTION IF EXISTS delete_comment CASCADE;
DROPFUNCTION IF EXISTS update_reputation CASCADE;
DROPFUNCTION IF EXISTS check_comment_date CASCADE;
DROPTABLE IF EXISTS notifications_like CASCADE;
DROPTABLE IF EXISTS notifications_comment CASCADE;
DROPTABLE IF EXISTS notifications CASCADE;
DROPTABLE IF EXISTS likes CASCADE;
DROPTABLE IF EXISTS report CASCADE;
DROPTABLE IF EXISTS favorite CASCADE;
DROPTABLE IF EXISTS tag_news CASCADE;
DROPTABLE IF EXISTS follow_tag CASCADE;
DROPTABLE IF EXISTS follow CASCADE;
DROPTABLE IF EXISTS comment CASCADE;
DROPTABLE IF EXISTS news CASCADE;
DROPTABLE IF EXISTS blocked CASCADE;
DROPTABLE IF EXISTS influencer CASCADE;
DROPTABLE IF EXISTS administrator CASCADE;
DROPTABLE IF EXISTS users CASCADE;
DROPTABLE IF EXISTS tag CASCADE;
DROPTABLE IF EXISTS images CASCADE;
DROPTYPE IF EXISTS Notification_types CASCADE;
------------------------------------------- Types-----------------------------------------CREATETYPENotification_typesAS ENUM(
'Like_notification',
'Comment_notification',
'Follow_notification'
);
------------------------------------------- Tables-----------------------------------------CREATETABLEimages (
id SERIALPRIMARY KEY,
image_path VARCHAR(255) NOT NULL UNIQUE
);
CREATETABLEtag (
id SERIALPRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATETABLEusers (
id SERIALPRIMARY KEY,
user_name VARCHAR(30) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
user_password VARCHAR(255) NOT NULL,
reputation INTEGERNOT NULL DEFAULT 0CHECK (reputation >=0),
image_id INTEGERREFERENCES images(id) ON DELETESETNULL
);
CREATETABLEadministrator (
id SERIALPRIMARY KEY,
adm_name VARCHAR(30) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
adm_password VARCHAR(255) NOT NULL,
image_id INTEGERREFERENCES images(id) ON DELETESETNULL
);
CREATETABLEinfluencer (
user_id INTEGERPRIMARY KEYREFERENCES users(id) ON DELETE CASCADE,
started_date TIMESTAMP DEFAULT now() NOT NULL,
has_privilege BOOLEANNOT NULL DEFAULT FALSE
);
CREATETABLEblocked (
user_id INTEGERPRIMARY KEYREFERENCES users(id) ON DELETE CASCADE,
blocked_date TIMESTAMP DEFAULT now() NOT NULL,
appeal BOOLEANNOT NULL DEFAULT FALSE,
appeal_description TEXTCHECK (
(appeal = TRUE AND appeal_description IS NOT NULL) OR
(appeal = FALSE AND appeal_description IS NULL)
)
);
CREATETABLEnews (
id SERIALPRIMARY KEY,
content TEXTNOT NULL,
published_date TIMESTAMP DEFAULT now() NOT NULL,
author_id INTEGERREFERENCES users(id) ON DELETESETNULL,
image_id INTEGERREFERENCES images(id) ON DELETESETNULL
);
CREATETABLEcomment (
id SERIALPRIMARY KEY,
content TEXTNOT NULL,
published_date TIMESTAMP DEFAULT now() NOT NULL,
news_id INTEGERNOT NULLREFERENCES news(id) ON DELETE CASCADE,
author_id INTEGERREFERENCES users(id) ON DELETESETNULL,
img_id INTEGERREFERENCES images(id) ON DELETESETNULL
);
CREATETABLEfollow (
follower_id INTEGERNOT NULLREFERENCES users(id) ON DELETE CASCADE,
followed_id INTEGERNOT NULLREFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY (follower_id, followed_id)
);
CREATETABLEfollow_tag (
user_id INTEGERNOT NULLREFERENCES users(id) ON DELETE CASCADE,
tag_id INTEGERNOT NULLREFERENCES tag(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, tag_id)
);
CREATETABLEtag_news (
news_id INTEGERNOT NULLREFERENCES news(id) ON DELETE CASCADE,
tag_id INTEGERNOT NULLREFERENCES tag(id) ON DELETE CASCADE,
PRIMARY KEY (news_id, tag_id)
);
CREATETABLEfavorite (
user_id INTEGERREFERENCES users(id) ON DELETE CASCADE,
news_id INTEGERREFERENCES news(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, news_id)
);
CREATETABLEreport (
id SERIALPRIMARY KEY,
report_description TEXTNOT NULL,
created_date TIMESTAMP DEFAULT now() NOT NULL,
user_id INTEGERREFERENCES users(id) ON DELETE CASCADE,
comment_id INTEGERREFERENCES comment(id) ON DELETE CASCADE,
news_id INTEGERREFERENCES news(id) ON DELETE CASCADE,
CHECK (
(user_id IS NOT NULLAND comment_id IS NULLAND news_id IS NULL) OR
(user_id IS NULLAND comment_id IS NOT NULLAND news_id IS NULL) OR
(user_id IS NULLAND comment_id IS NULLAND news_id IS NOT NULL)
)
);
CREATETABLElikes (
id SERIALPRIMARY KEY,
sender_id INTEGERREFERENCES users(id) ON DELETESETNULL,
news_id INTEGERREFERENCES news(id) ON DELETE CASCADE,
comment_id INTEGERREFERENCES comment(id) ON DELETE CASCADE,
CHECK (
(news_id IS NOT NULLAND comment_id IS NULL) OR
(news_id IS NULLAND comment_id IS NOT NULL)
)
);
CREATETABLEnotifications (
id SERIALPRIMARY KEY,
sender_id INTEGERNOT NULLREFERENCES users(id) ON DELETE CASCADE,
receiver_id INTEGERNOT NULLREFERENCES users(id) ON DELETE CASCADE,
notification_date TIMESTAMP DEFAULT now() NOT NULL,
viewed BOOLEANNOT NULL DEFAULT FALSE,
notification_type Notification_types NOT NULL
);
CREATETABLEnotifications_comment (
notification_id INTEGERPRIMARY KEYREFERENCES notifications(id) ON DELETE CASCADE,
comment_id INTEGERNOT NULL UNIQUE REFERENCES comment(id) ON DELETE CASCADE
);
CREATETABLEnotifications_like (
notification_id INTEGERPRIMARY KEYREFERENCES notifications(id) ON DELETE CASCADE,
like_id INTEGERNOT NULL UNIQUE REFERENCES likes(id) ON DELETE CASCADE
);
------------------------------------------- Views-----------------------------------------
CREATE MATERIALIZED VIEW news_like_counts ASSELECT
news_id,
COUNT(*) AS like_count
FROM likes
WHERE news_id IS NOT NULLGROUP BY news_id;
CREATE MATERIALIZED VIEW comments_like_counts ASSELECT
comment_id,
COUNT(*) AS like_count
FROM likes
WHERE comment_id IS NOT NULLGROUP BY comment_id;
------------------------------------------- Indexes-----------------------------------------CREATEINDEXidx_news_dateON news USING btree (published_date);
CREATEINDEXidx_like_newsON likes USING btree (news_id, sender_id) WHERE news_id IS NOT NULL;
CREATEINDEXidx_notificationON notifications USING btree (receiver_id, viewed);
--- FTS Indexes-- FTS01ALTERTABLE news ADD COLUMN ts_content_user TSVECTOR;
CREATE OR REPLACEFUNCTIONnews_content_user_update() RETURNS TRIGGER AS $$
DECLARE
user_name_value TEXT;
BEGINSELECT COALESCE(user_name, 'Anonymous') INTO user_name_value FROM users WHEREusers.id=NEW.author_id;
IF TG_OP ='INSERT' THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
IF TG_OP ='UPDATE' THEN
IF (NEW.content<>OLD.contentORNEW.author_id<>OLD.author_id) THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
END IF;
RETURN NEW;
END $$
LANGUAGE plpgsql;
CREATETRIGGERnews_content_user_update
BEFORE INSERT ORUPDATEON news
FOR EACH ROW
EXECUTE PROCEDURE news_content_user_update();
CREATEINDEXidx_news_contentON news USING GIN (ts_content_user);
-- FTS02ALTERTABLE comment ADD COLUMN ts_content_user TSVECTOR;
CREATE OR REPLACEFUNCTIONcomment_content_user_update() RETURNS TRIGGER AS $$
DECLARE
user_name_value TEXT;
BEGINSELECT COALESCE(user_name, 'Anonymous') INTO user_name_value FROM users WHEREusers.id=NEW.author_id;
IF TG_OP ='INSERT' THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
IF TG_OP ='UPDATE' THEN
IF (NEW.content<>OLD.contentORNEW.author_id<>OLD.author_id) THEN
NEW.ts_content_user= (
setweight(to_tsvector('english', NEW.content), 'A') ||
setweight(to_tsvector('english', user_name_value), 'B')
);
END IF;
END IF;
RETURN NEW;
END $$
LANGUAGE plpgsql;
CREATETRIGGERcomment_content_user_update
BEFORE INSERT ORUPDATEON comment
FOR EACH ROW
EXECUTE PROCEDURE comment_content_user_update();
CREATEINDEXidx_comment_contentON comment USING GIN (ts_content_user);
------------------------------------------- Triggers and UDFs------------------------------------------- TRIGGER01CREATEFUNCTIONmanage_influencers()
RETURNS TRIGGER AS $$
DECLARE
total_users INTEGER;
total_influencers INTEGER;
min_influencer RECORD;
max_user RECORD;
BEGINSELECTCOUNT(*) INTO total_users FROM users;
SELECTCOUNT(*) INTO total_influencers FROM influencer;
SELECT id, reputation
INTO min_influencer
FROM users
WHERE id IN (SELECT user_id FROM influencer)
ORDER BY reputation ASCLIMIT1;
IF TG_OP ='INSERT' THEN
IF total_influencers < CEIL(0.1* total_users) THEN
IF NEW.reputation>=100 THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
END IF;
ELSIF NEW.reputation>min_influencer.reputation THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
DELETEFROM influencer
WHERE user_id =min_influencer.id;
END IF;
END IF;
IF TG_OP ='UPDATE' THEN
IF total_influencers < CEIL(0.1* total_users) THEN
IF NEW.reputation>=100ANDNEW.id NOT IN (SELECT user_id FROM influencer) THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
END IF;
ELSIF NEW.reputation>OLD.reputationANDNEW.id NOT IN (SELECT user_id FROM influencer) THEN
IF NEW.reputation>min_influencer.reputation THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (NEW.id, now(), FALSE);
DELETEFROM influencer
WHERE user_id =min_influencer.id;
END IF;
ELSIF NEW.reputation<OLD.reputationANDNEW.idIN (SELECT user_id FROM influencer) THEN
IF NEW.reputation<100 THEN
DELETEFROM influencer
WHERE user_id =NEW.id;
SELECT id, reputation INTO max_user
FROM users
WHERE id NOT IN (SELECT user_id FROM influencer)
ORDER BY reputation DESCLIMIT1;
IF max_user IS NOT NULLANDmax_user.reputation>=100 THEN
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (max_user.id, now(), FALSE);
END IF;
ELSIF NEW.reputation<min_influencer.reputation THEN
SELECT id, reputation INTO max_user
FROM users
WHERE id NOT IN (SELECT user_id FROM influencer)
ORDER BY reputation DESCLIMIT1;
IF max_user IS NOT NULLANDNEW.reputation<max_user.reputationANDmax_user.reputation>=100 THEN
DELETEFROM influencer
WHERE user_id =NEW.id;
INSERT INTO influencer (user_id, started_date, has_privilege)
VALUES (max_user.id, now(), FALSE);
END IF;
END IF;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_influencers
AFTER INSERT ORUPDATEON users
FOR EACH ROW
EXECUTE FUNCTION manage_influencers();
-- TRIGGER02CREATEFUNCTIONmanage_like_notification()
RETURNS TRIGGER AS $$
DECLARE
notification_id INTEGER;
receiver_id INTEGER;
BEGIN
IF NEW.news_idIS NOT NULL THEN
SELECT author_id INTO receiver_id
FROM news
WHERE id =NEW.news_id;
INSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.sender_id, receiver_id, now(), FALSE, 'Like_notification')
RETURNING id INTO notification_id;
INSERT INTO notifications_like (notification_id, like_id)
VALUES (notification_id, NEW.id);
ELSIF NEW.comment_idIS NOT NULL THEN
SELECT author_id INTO receiver_id
FROM comment
WHERE id =NEW.comment_id;
INSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.sender_id, receiver_id, now(), FALSE, 'Like_notification')
RETURNING id INTO notification_id;
INSERT INTO notifications_like (notification_id, like_id)
VALUES (notification_id, NEW.id);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_like_notification
AFTER INSERT ON likes
FOR EACH ROW
EXECUTE FUNCTION manage_like_notification();
-- TRIGGER03CREATEFUNCTIONmanage_comment_notification()
RETURNS TRIGGER AS $$
DECLARE
notification_id INTEGER;
receiver_id INTEGER;
BEGINSELECT author_id INTO receiver_id
FROM news
WHERE id =NEW.news_id;
INSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.author_id, receiver_id, now(), FALSE, 'Comment_notification')
RETURNING id INTO notification_id;
INSERT INTO notifications_comment (notification_id, comment_id)
VALUES (notification_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_comment_notification
AFTER INSERT ON comment
FOR EACH ROW
EXECUTE FUNCTION manage_comment_notification();
-- TRIGGER04CREATEFUNCTIONmanage_follow_notification()
RETURNS TRIGGER AS $$
DECLARE
notification_id INTEGER;
BEGININSERT INTO notifications (sender_id, receiver_id, notification_date, viewed, notification_type)
VALUES (NEW.follower_id, NEW.followed_id, now(), FALSE, 'Follow_notification')
RETURNING id INTO notification_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_follow_notification
AFTER INSERT ON follow
FOR EACH ROW
EXECUTE FUNCTION manage_follow_notification();
-- TRIGGER05CREATEFUNCTIONdelete_notification()
RETURNS TRIGGER AS $$
BEGINDELETEFROM notifications
WHERE id =OLD.notification_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_notification_like
AFTER DELETEON notifications_like
FOR EACH ROW
EXECUTE FUNCTION delete_notification();
-- TRIGGER06 CREATETRIGGERdelete_notification_comment
AFTER DELETEON notifications_comment
FOR EACH ROW
EXECUTE FUNCTION delete_notification();
-- TRIGGER07 CREATEFUNCTIONdelete_notification_follow()
RETURNS TRIGGER AS $$
BEGINDELETEFROM notifications
WHERE sender_id =OLD.follower_idAND receiver_id =OLD.followed_idAND notification_type ='Follow_notification';
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_notification_follow
AFTER DELETEON follow
FOR EACH ROW
EXECUTE FUNCTION delete_notification_follow();
-- TRIGGER08CREATEFUNCTIONlike_news_once()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP ='UPDATE'AND (OLD.sender_id=NEW.sender_idANDOLD.news_id=NEW.news_id) THEN
RETURN NEW;
END IF;
IF EXISTS (
SELECT1FROM likes
WHERE sender_id =NEW.sender_idAND news_id =NEW.news_id
) THEN
RAISE EXCEPTION 'User already liked this news';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERlike_news_once
BEFORE INSERT ORUPDATEON likes
FOR EACH ROW
EXECUTE FUNCTION like_news_once();
-- TRIGGER09CREATEFUNCTIONlike_comment_once()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP ='UPDATE'AND (OLD.sender_id=NEW.sender_idANDOLD.comment_id=NEW.comment_id) THEN
RETURN NEW;
END IF;
IF EXISTS (
SELECT1FROM likes
WHERE sender_id =NEW.sender_idAND comment_id =NEW.comment_id
) THEN
RAISE EXCEPTION 'User already liked this comment';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERlike_comment_once
BEFORE INSERT ORUPDATEON likes
FOR EACH ROW
EXECUTE FUNCTION like_comment_once();
-- TRIGGER10CREATEFUNCTIONmanage_self_follow()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.follower_id=NEW.followed_id THEN
RAISE EXCEPTION 'User cannot follow themselves';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERmanage_self_follow
BEFORE INSERT ORUPDATEON follow
FOR EACH ROW
EXECUTE FUNCTION manage_self_follow();
-- TRIGGER11CREATEFUNCTIONdelete_news()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT1FROM likes
WHERE news_id =OLD.id
) THEN
RAISE EXCEPTION 'News has likes';
ELSIF EXISTS (
SELECT1FROM comment
WHERE news_id =OLD.id
) THEN
RAISE EXCEPTION 'News has comments';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_news
BEFORE DELETEON news
FOR EACH ROW
EXECUTE FUNCTION delete_news();
-- TRIGGER012CREATEFUNCTIONdelete_comment()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT1FROM likes
WHERE comment_id =OLD.id
) THEN
RAISE EXCEPTION 'Comment has likes';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERdelete_comment
BEFORE DELETEON comment
FOR EACH ROW
EXECUTE FUNCTION delete_comment();
-- TRIGGER13CREATEFUNCTIONupdate_reputation()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP ='INSERT' THEN
IF NEW.news_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation+1FROM news n
WHEREn.id=NEW.news_idANDu.id=n.author_id;
ELSIF NEW.comment_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation+1FROM comment c
WHEREc.id=NEW.comment_idANDu.id=c.author_id;
END IF;
ELSIF TG_OP ='DELETE' THEN
IF OLD.news_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation-1FROM news n
WHEREn.id=OLD.news_idANDu.id=n.author_id;
ELSIF OLD.comment_idIS NOT NULL THEN
UPDATE users u
SET reputation =u.reputation-1FROM comment c
WHEREc.id=OLD.comment_idANDu.id=c.author_id;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERupdate_reputation
AFTER INSERT ORDELETEON likes
FOR EACH ROW
EXECUTE FUNCTION update_reputation();
-- TRIGGER14CREATEFUNCTIONcheck_comment_date()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT1FROM news n
WHEREn.id=NEW.news_idANDn.published_date>NEW.published_date
) THEN
RAISE EXCEPTION 'Comment publication date must be later than the news publication date';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATETRIGGERcheck_comment_date
BEFORE INSERT ORUPDATEON comment
FOR EACH ROW
EXECUTE FUNCTION check_comment_date();
------------------------------------------- end-----------------------------------------
A.2. Database population
SET search_path TO lbaw24142;
------------------------------------------- Populate the database-----------------------------------------INSERT INTO images (image_path) VALUES
('images/avatar1.jpg'),
('images/avatar2.jpg'),
('images/avatar3.jpg'),
('images/avatar4.jpg'),
('images/avatar5.jpg'),
('images/avatar6.jpg');
INSERT INTO tag (name) VALUES
('Action'),
('Comedy'),
('Drama'),
('Horror'),
('Thriller'),
('Sci-Fi'),
('Romance'),
('Documentary'),
('Animation'),
('Fantasy');
INSERT INTO users (user_name, email, user_password, reputation, image_id) VALUES
('movie_buff91', '[email protected]', 'password123', 50, 1),
('tv_series_queen', '[email protected]', 'password456', 100, 2),
('action_fanatic', '[email protected]', 'password789', 75, 3),
('horror_lover', '[email protected]', 'securepass1', 120, 4),
('comedy_guru', '[email protected]', 'password987', 40, 5),
('fantasy_enthusiast', '[email protected]', 'password654', 90, 3),
('film_critic_01', '[email protected]', 'password123', 80, 1),
('tvfanatic_89', '[email protected]', 'password456', 60, 2),
('blockbuster_boy', '[email protected]', 'password789', 100, 3),
('binge_watcher', '[email protected]', 'password321', 150, 4);
INSERT INTO administrator (adm_name, email, adm_password, image_id) VALUES
('João', 'joã[email protected]', 'adminpass123', 1),
('Carlos', '[email protected]', 'adminpass456', 2),
('Pedro', '[email protected]', 'adminpass456', 3),
('Manuel', '[email protected]', 'adminpass456', 4);
INSERT INTO blocked (user_id, blocked_date, appeal, appeal_description) VALUES
(3, '2024-01-01 12:00:00', FALSE, NULL),
(4, '2024-01-02 14:00:00', TRUE, 'I was trying to defend myself from an offensive comment about my family.'),
(10, '2024-01-03 16:00:00', FALSE, NULL);
INSERT INTO news (content, author_id, image_id) VALUES
('The latest Spider-Man movie breaks box office records!', 1, 1),
('Breaking News: The new Game of Thrones prequel is confirmed for 2025!', 2, 2),
('Top 10 Comedy Movies of 2024 that you must watch!', 3, NULL),
('How to watch all the Star Wars movies in chronological order', 4, 4),
('The scariest horror movies on Netflix right now', 5, NULL),
('New trailer for the upcoming "Dune" movie drops today!', 1, 1),
('Is "The Witcher" Season 3 the best yet? Here’s everything we know', 2, 2),
('Marvel announces new "Fantastic Four" casting lineup', 3, 3),
('The most anticipated movies of 2025', 4, 4),
('5 hidden gems on Netflix you may have missed in 2024', 5, 5);
INSERT INTO comment (content, news_id, author_id, img_id) VALUES
('I’m so excited for this new Spider-Man movie!', 1, 2, 1),
('Can’t believe they’re bringing back Game of Thrones, but with a twist!', 2, 3, NULL),
('Great list! I’ve seen most of them, but some are still on my watchlist.', 3, 4, NULL),
('Star Wars will always be my favorite, love this list!', 4, 5, 4),
('I’ll never be able to watch horror movies like I used to after seeing that list!', 5, 1, NULL),
('I can’t wait for "Dune"! The visuals in the first movie were stunning.', 1, 2, 1),
('The new trailer for Dune looks even better than the first one. So excited!', 1, 3, 2),
('The Witcher Season 3 is shaping up to be amazing. Can’t wait to see more of Geralt!', 2, 4, 3),
('I’m hoping for more character development in Season 3. The first two were amazing!', 2, 5, 4),
('The Marvel "Fantastic Four" casting news has me excited! I wonder who will be playing Reed Richards.', 3, 1, 5);
INSERT INTO follow (follower_id, followed_id) VALUES
(1, 2),
(2, 1),
(2, 3),
(3, 4),
(4, 5),
(5, 1),
(1, 3),
(2, 4),
(2, 5),
(3, 6);
INSERT INTO follow_tag (user_id, tag_id) VALUES
(1, 11),
(1, 4),
(3, 1),
(4, 3),
(5, 2),
(5, 10),
(1, 1),
(2, 3),
(3, 2),
(4, 4);
INSERT INTO tag_news (news_id, tag_id) VALUES
(1, 11),
(2, 2),
(2, 1),
(3, 2),
(4, 5),
(1, 13),
(2, 14),
(4, 13),
(5, 4),
(6, 13);
INSERT INTO favorite (user_id, news_id) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(10, 15),
(11, 14),
(12, 13);
INSERT INTO report (report_description, user_id, comment_id, news_id) VALUES
('Offensive comment about a TV series', 1, NULL, NULL),
('False information about a movie release', NULL, 1, NULL),
('Clickbait headline in the news post', NULL, NULL, 3),
('Inaccurate review of the movie plot', NULL, NULL, 2),
('Offensive language in the comment section', 3, NULL, NULL),
('Clickbait title misleading readers', NULL, NULL, 4),
('Hate speech in a comment about a TV show', NULL, 3, NULL),
('Fake information about the movie plot', NULL, NULL, 5),
('Racist comment in the discussion', NULL, 4, NULL),
('Personal attack in a comment on the news post', NULL, 5, NULL);
INSERT INTO likes (sender_id, news_id, comment_id) VALUES
(1, 1, NULL),
(2, 2, NULL),
(3, NULL, 1),
(4, NULL, 2),
(5, 5, NULL),
(10, 10, NULL),
(1, 6, NULL),
(2, 7, NULL),
(3, 8, NULL),
(4, 9, NULL);
------------------------------------------- end-----------------------------------------