---
# System prepended metadata

title: 'A06. Indexes, triggers, transactions and database population'

---

**4. A4: Conceptual Data Model**

The presented UML below shows the organisation and structures of the various objects that make part of Askit's system, as well as its attributes, domains, relations between them and their multiplicity.
Our Business Rules are not represented in the UML but in a text box below the diagram.

**4.1 Class Diagram**

The UML diagram in Figure 1 presents the main entities and the relationships between them, attributes and some domains, and the multiplicity of relationships for the **Askitt** platform.


![](https://i.imgur.com/YjcIGgg.jpg)


<p align= "center">
 <b><i>Fig.1 Askit DB UML</i></b>
</p>

**4.2 Additional Business Rules**

- BR01. A user cannot follow his own question.
- BR02. Only one answer can be verified for each question.

**5. A5: Relational Schema, Validation and Schema Refinement**

We mapped the classes, attributes and multiplicities of our UML in our relation schema using the relation compact notation.<br>
We identified the functional dependencies and showed the relational schema below. It was necessary to make any decomposition since they were already in BCNF.

**5.1 Relational Schema**

|Rule number|RelationName|
|---|---|
|R01|user(<u>idUser</u>, email **UK NN**, name **NN**, password **NN**, bio, isModerator **NN**)
|R02|post(<u>idPost</u>, idUser->user, date **NN CK** date > 2022-01-01, score **NN CK** score > 0, content **NN**)|
|R03|answer(<u>idPost</u>->post, idQuestion->question, isVerified **NN**)|
|R04|question(<u>idPost</u>->post, title **NN**)|
|R05|tag(<u>idTag</u>, name **NN**)|
|R06|community(<u>idCommunity</u> , name **NN**, date **NN**)|
|R07|assUserTag(<u>idUserTag</u>, idUser->user, idTag->tag,isFavorite **NN**, isBlocked **NN CK** isFavorite!=isBlocked)
|R08|followed(<u>idUser</u>->user, <u>idPost</u>->question)|
|R09|userCommunity(<u>idUser</u>->user, <u>idCommunity</u>->community|
|R10|questionTag(<u>idQuestionTag</u>, idTag->tag, idQuestion->question)
|R11|interact(<u>idInteract</u>, idUser->user, idPost->post, isLiked **NN**, isDisliked **NN CK** isLiked!=isDisliked)



We chose to map the generalizations in the Object Oriented strategy (O-O-S), given that the child classes have their own different attributes and as both will have a large number of rows in the table, it would be a waste of space and time.

Legend
- UK = UNIQUE KEY
- NN = NOT NULL
- CK = CHECK



**5.2 Domains**


|Domain Name | Domain Specification|
|---|---|
|Post|ENUM('Question', 'Answer')|
|User |ENUM('Guest', 'Askitter' ,'Moderator')|
|content|VARCHAR(500)|
|bio|VARCHAR(250)|
|name|VARCHAR(25)|



**5.3 Schema validation**


|TABLE R01 | User |
|---|---|
| Keys | {idUser}, {email} |
|Functional Dependencies:||
|FD0101| {idUser} -> {email, name, password, bio, isModerator}|
|FD0102| {email}->{idUser, name, password, bio, isModerator} |
|Normal Form|BCNF|

|TABLE R02 | Post |
|---|---|
| Keys | {idPost}|
|Functional Dependencies:||
|FD0201| {idPost} -> {date, score, content}|
|Normal Form|BCNF|


|TABLE R03 | Answer |
|---|---|
| Keys | {idPost}|
|Functional Dependencies:||
|FD0301| {idPost} -> {idQuestion, isVerified}|
|Normal Form|BCNF|


|TABLE R04 | Question |
|---|---|
| Keys | {idPost}|
|Functional Dependencies:||
|FD0401| {idPost} -> {title}|
|Normal Form|BCNF|


|TABLE R05 | Tag |
|---|---|
| Keys | {idTag}|
|Functional Dependencies:||
|FD0501| {idTag} -> {name}|
|Normal Form|BCNF|

|TABLE R06 | Community |
|---|---|
| Keys | {idCommunity}|
|Functional Dependencies:||
|FD0601| {idCommunity} -> {name, date}|
|Normal Form|BCNF|

|TABLE R07 | AssUserTag |
|---|---|
| Keys | {idUserTag}|
|Functional Dependencies:||
|FD0701|{idUserTag} -> {idUser, idTag, isBlocked, isFavorite} |
|Normal Form|BCNF|

|TABLE R08 | Followed |
|---|---|
| Keys | {idUser, idPost}|
|Functional Dependencies:|none|
|Normal Form|BCNF|

|TABLE R09 | userCommunity |
|---|---|
| Keys | {idUser, idCommunity}|
|Functional Dependencies:|none|
|Normal Form|BCNF|

|TABLE R10 | questionTag |
|---|---|
| Keys | {idQuestionTag}|
|Functional Dependencies:||
|FD1001 {idQuestionTag} -> {idQuestion, idTag}|
|Normal Form|BCNF|

|TABLE R11 | interact |
|---|---|
| Keys | {idInteract}|
|Functional Dependencies:||
|FD1001 {idInteract} -> {idPost, idUser, isLiked, isDisliked}|
|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 normalised.




**SQL CODE**

```sql
---- TYPES -----

CREATE TYPE User_ AS ENUM ('Askitter', 'Guest', 'Moderator');

--- TABLE ----

DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS post CASCADE;
DROP TABLE IF EXISTS answer CASCADE;
DROP TABLE IF EXISTS question CASCADE;
DROP TABLE IF EXISTS tag CASCADE;
DROP TABLE IF EXISTS community CASCADE;
DROP TABLE IF EXISTS assUserTag CASCADE;
DROP TABLE IF EXISTS followed CASCADE;
DROP TABLE IF EXISTS userCommunity CASCADE;
DROP TABLE IF EXISTS interact CASCADE;


CREATE TABLE users (
idUser SERIAL PRIMARY KEY,
email TEXT NOT NULL CONSTRAINT user_email_uk UNIQUE,
name TEXT NOT NULL,
password TEXT NOT NULL,
bio TEXT,
isModerador BOOL NOT NULL
);

CREATE TABLE post (
idPost SERIAL PRIMARY KEY,
idUser INTEGER REFERENCES users (idUser) ON UPDATE CASCADE,
date DATE NOT NULL,
score INTEGER NOT NULL,
content TEXT NOT NULL
);

CREATE TABLE question (
idPost INTEGER PRIMARY KEY REFERENCES post (idPost) ON UPDATE CASCADE,
title TEXT NOT NULL
);

CREATE TABLE answer (
idPost INTEGER PRIMARY KEY REFERENCES post (idPost) ON UPDATE CASCADE,
idQuestion INTEGER REFERENCES question (idPost) ON UPDATE CASCADE,
isVerified BOOLEAN NOT NULL
);

CREATE TABLE tag (
idTag SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);

CREATE TABLE community (
idCommunity SERIAL PRIMARY KEY,
name TEXT NOT NULL,
date DATE NOT NULL
);

CREATE TABLE assUserTag (
idUserTag SERIAL PRIMARY KEY,
idUser INTEGER REFERENCES users (idUser) ON UPDATE CASCADE,
idTag INTEGER REFERENCES tag (idTag) ON UPDATE CASCADE,
isFavorite BOOLEAN NOT NULL,
isBlocked BOOLEAN NOT NULL,
CONSTRAINT fav_blocked_ck CHECK (isFavorite != isBlocked)
);

CREATE TABLE followed (
idUser INTEGER NOT NULL REFERENCES users (idUser) ON UPDATE CASCADE,
idPost INTEGER NOT NULL REFERENCES post (idPost) ON UPDATE CASCADE,
PRIMARY KEY (idUser, idPost)
);

CREATE TABLE userCommunity (
idUser INTEGER NOT NULL REFERENCES users (idUser) ON UPDATE CASCADE,
idCommunity INTEGER NOT NULL REFERENCES community (idCommunity) ON UPDATE CASCADE,
PRIMARY KEY (idUser, idCommunity)
);

CREATE TABLE questionTag (
idQuestionTag SERIAL PRIMARY KEY,
idQuestion INTEGER REFERENCES question (idPost) ON UPDATE CASCADE, 
idTag INTEGER REFERENCES tag (idTag) ON UPDATE CASCADE
);

CREATE TABLE interact (
idInteract SERIAL PRIMARY KEY,
idPost INTEGER REFERENCES post (idPost) ON UPDATE CASCADE,
idUser INTEGER REFERENCES users (idUser) ON UPDATE CASCADE
isLiked BOOLEAN NOT NULL, 
isDisliked BOOLEAN NOT NULL,
CONSTRAINT like_dislike_ck CHECK (isLiked != isDisliked)
);


```


# A06. Indexes, triggers, transactions and database population

The database workload shows the number of rows we estimate to be in each table, as well as their expected growth. <br>
We also created 3 performance indexes and 4 full text-search indexes in order to improve our DB performance and access information in a more efficient way, given that it has some tables with enormous amount of data. <br>
Furthermore, we developed a couple of triggers and transactions so that the integrity of data was maintained throughout the whole process.



**1. Database workload**

|Relation|RelationName|Order of magnitude|Estimated growth
|---|---|---|---|
|R01|user| 10k(tens of thousands) | 15(tens)/day
|R02|post| 100k | 100 (hundreds)/day
|R03|answer| 60k | 60/day
|R04|question| 40k | 40/day
|R05|tag| 500 | 5/day
|R06|community| 2k | 10/day
|R07|assUserTag| 1k | 10/day
|R08|followed| 10k | 50/day
|R09|userCommunity| 10k | 50/day
|R10|questionTag|40k| 40/day
|R11|interact|300k|300/day

**2. Proposed Indexes**

**2.1 Performance indices**

|Index| IDX01 |
|---|---|
|**Index relation** | post |
|**Index attribute** | idUser |
|**Index type** | B-tree |
|**Cardinality** | Medium |
|**Clustering** | Yes |
|**Justification**| Table 'post' is very large. Several queris need to frequently filter access to the posts by its owner (user). Filtering is done by exact match, thus an hash type index would be best suited. However, since we also want to apply clustering based on this index, and clustering is not possible on hash type indexes, we opted for a b-tree index. Update frequency is low and cardinality is medium so it's a good candidate for clustering.|

**SQL Code**

``` sql
CREATE INDEX user_post ON post USING btree (idPost); 
CLUSTER post USING idPost 
```

|Index| IDX02 |
|---|---|
|**Index relation** | answer |
|**Index attribute** | idQuestion |
|**Index type** | B-tree |
|**Cardinality** | Medium |
|**Clustering** | Yes |
|**Justification**| Several queries need to frequently filter access to the answers by its respective question. Filtering is done by exact match, thus an hash type index would be best suited. However, since we also want to apply clustering based on this index, and clustering is not possible on hash type indexes, we opted for a b-tree index. Update frequency is low and cardinality is medium so it’s a good candidate for clustering. |

**SQL Code**
``` sql
CREATE INDEX answer_question ON answer USING btree (idQuestion);
CLUSTER answer USING idQuestion
```

|Index| IDX03 |
|---|---|
|**Index relation** | questionTag |
|**Index attribute** | idTag |
|**Index type** | B-tree |
|**Cardinality** | Medium |
|**Clustering** | Yes |
|**Justification**| Several queries need to frequently filter access to the questions by the respective tag. Filtering is done by exact match, thus an hash type index would be best suited. However, since we also want to apply clustering based on this index, and clustering is not possible on hash type indexes, we opted for a b-tree index. Update frequency is low and cardinality is medium so it’s a good candidate for clustering. |

**SQL Code**

``` sql
CREATE INDEX questionbytag ON questionTag USING btree (idTag);
CLUSTER questionTag USING idTag
```


**2.2 Full Text Search Index**

|Index |IDX04 |
|---|---|
|**Index relation**|question |
|**Index attribute**|title |
|**Index type** |GIN |
|**Clustering** | No |
|**Justification**| To provide full-text search features to look for questions based on matching titles. The index type is GIN because the indexed fields are not expected to change often. |

**SQL Code**
``` sql
-- Add column to question to store computed ts_vectors.
ALTER TABLE question
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION question_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.title), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.title <> OLD.title) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.title), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER question_search_update
 BEFORE INSERT OR UPDATE ON question
 FOR EACH ROW
 EXECUTE PROCEDURE question_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx ON question USING GIN (tsvectors);
```


|Index |IDX05 |
|---|---|
|**Index relation**|community |
|**Index attribute**|name |
|**Index type** |GIN |
|**Clustering** |No |
|**Justification**| To provide full-text search features to look for communities based on matching names. The index type is GIN because the indexed fields are not expected to change often. |

**SQL Code**
``` sql
-- Add column to question to store computed ts_vectors.
ALTER TABLE community
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION community_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.name), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.name <> OLD.name) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.name), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER community_search_update
 BEFORE INSERT OR UPDATE ON community
 FOR EACH ROW
 EXECUTE PROCEDURE community_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx ON community USING GIN (tsvectors);
```

|Index |IDX06 |
|---|---|
|**Index relation**|user |
|**Index attribute**|name |
|**Index type** |GIN |
|**Clustering** |No |
|**Justification**| To provide full-text search features to look for users based on matching names. The index type is GIN because the indexed fields are not expected to change often. |

**SQL Code**
``` sql
-- Add column to question to store computed ts_vectors.
ALTER TABLE user
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION user_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.name), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.name <> OLD.name) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.name), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER user_search_update
 BEFORE INSERT OR UPDATE ON user
 FOR EACH ROW
 EXECUTE PROCEDURE user_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx ON user USING GIN (tsvectors);
```

**3. Triggers**


|Trigger| TRIGGER01 |
|---|---|
| Description    | A user cannot follow his/hers own question|
| Justification | A user should only be able to follow questions of other people, as he will already be notified when their question is interacted with. | 

**SQL Code**
```sql
CREATE FUNCTION not_follow() RETURN TRIGGER AS 

$BODY$ 

BEGIN 
  IF EXISTS (SELECT * FROM followed WHERE NEW.idUser = followed.idUser && SELECT * FROM question WHERE NEW.idQuestion = question.idQuestion) THEN  

       RAISE EXCEPTION 'A user cannot follow his own question';

       END IF; 

       RETURN NEW; 

END 

$BODY$ 

LANGUAGE plpgsql;

CREATE TRIGGER not_follow

      BEFORE INSERT OR UPDATE ON followed

      FOR EACH ROW 

      EXECUTE PROCEDURE not_follow(); 

```

|Trigger| TRIGGER02 |
|---|---|
|Description|Delete user records after he is deleted. |
| Justification | The entirety of the user's data should be removed if/when he chooses to delete his account. | 

**SQL Code**

``` sql
CREATE FUNCTION delete_user() RETURNS TRIGGER AS
$BODY$
BEGIN
        DELETE FROM assUsertag where (NEW.idUser = idUser);
        DELETE FROM userCommunity where (NEW.idUser = idUser);
        DELETE FROM posts where (NEW.idUser = idUser);
        DELETE FROM followed where (NEW.idUser = idUser);
        RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;

CREATE TRIGGER delete_user
        BEFORE DELETE ON user
        FOR EACH ROW
        EXECUTE PROCEDURE delete_user();
```

**4. Transactions**

|Transaction| TR01 |
|---|---|
|Description| Insert a new question |
|Justification| In order to maintain consistency, it's necessary to use a transaction to ensure that all the code executes without errors. If an error occurs, a ROLLBACK is issued (e.g. when the insertion of a post fails). The isolation level is Repeatable Read, because, otherwise, an update of post_id_seq could happen, due to an insert in the table post committed by a concurrent transaction, and as a result, inconsistent data would be stored. |
|Isolation level| REPEATABLE READ |


**SQL Code**

```sql 
BEGIN TRANSACTION; 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ 
-- Insert question 
INSERT INTO question (idPost, title)
VALUES (currval('post_id_seq'), $title) 
-- Insert post 
INSERT INTO post (idPost, idUser, date, score, content) VALUES (currval('post_id_seq'), $idUser, $date, $score, $content); END TRANSACTION;
```



|Transaction| TR02 |
|---|---|
|Description| Get current number of user's posts |
|Justification| In the middle of the transaction, the insertion of new rows in the post table can occur, which implies that the information retrieved in both selects is different, consequently resulting in a Phantom Read. It's READ ONLY because it only uses Selects. |
|Isolation level| SERIALIZABLE READ ONLY |

**SQL Code**
```sql
BEGIN TRANSACTION; 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY SELECT COUNT(*) 
FROM post 
WHERE NEW.idUser = post.idUser AND date < now();
END TRANSACTION;
```

|Transaction| TR03 |
|---|---|
|Description| Insert a new answer |
|Justification| In order to maintain consistency, it's necessary to use a transaction to ensure that all the code executes without errors. As an answer is also a post, we need to make sure that when there is an insertion in the table answer, there also needs to be an insertion in the table post. If an error occurs, a ROLLBACK is issued (e.g. when the insertion of an answer fails). The isolation level is Repeatable Read, because, otherwise, an update of post_id_seq could happen, due to an insert in the table post committed by a concurrent transaction, and as a result, inconsistent data would be stored. |
|Isolation level| REPEATABLE READ |

**SQL Code**
```sql
BEGIN TRANSACTION; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ 
-- Insert answer 
INSERT INTO answer (idPost, idQuestion, isVerified ) 
VALUES (currval('post_id_seq'), $idQuestion, $isVerified) 
-- Insert post
INSERT INTO post (idPost, idUser, date, score, content) VALUES (currval('post_id_seq'), $idUser, $date, $score, $content); 
END TRANSACTION;
```
------------------

**SQL CODE**

```sql
DROP SCHEMA IF EXISTS local CASCADE;
CREATE SCHEMA local;

SET search_path to local;

DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS post CASCADE;
DROP TABLE IF EXISTS answer CASCADE;
DROP TABLE IF EXISTS question CASCADE;
DROP TABLE IF EXISTS tag CASCADE;
DROP TABLE IF EXISTS community CASCADE;
DROP TABLE IF EXISTS assUserTag CASCADE;
DROP TABLE IF EXISTS followed CASCADE;
DROP TABLE IF EXISTS userCommunity CASCADE;


CREATE TABLE users (
idUser SERIAL PRIMARY KEY,
email TEXT NOT NULL CONSTRAINT user_email_uk UNIQUE,
name TEXT NOT NULL,
password TEXT NOT NULL,
bio TEXT,
isModerador BOOL NOT NULL
);

CREATE TABLE post (
idPost SERIAL PRIMARY KEY,
idUser INTEGER REFERENCES users (idUser) ON UPDATE CASCADE,
date DATE NOT NULL,
score INTEGER NOT NULL,
content TEXT NOT NULL
);

CREATE TABLE question (
idPost INTEGER PRIMARY KEY REFERENCES post (idPost) ON UPDATE CASCADE,
title TEXT NOT NULL
);

CREATE TABLE answer (
idPost INTEGER PRIMARY KEY REFERENCES post (idPost) ON UPDATE CASCADE,
idQuestion INTEGER REFERENCES question (idPost) ON UPDATE CASCADE,
isVerified BOOLEAN NOT NULL
);

CREATE TABLE tag (
idTag SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);

CREATE TABLE community (
idCommunity SERIAL PRIMARY KEY,
name TEXT NOT NULL,
date DATE NOT NULL
);

CREATE TABLE assUserTag (
idUserTag SERIAL PRIMARY KEY,
idUser INTEGER REFERENCES users (idUser) ON UPDATE CASCADE,
idTag INTEGER REFERENCES tag (idTag) ON UPDATE CASCADE,
isFavorite BOOLEAN NOT NULL,
isBlocked BOOLEAN NOT NULL,
CONSTRAINT fav_blocked_ck CHECK (isFavorite != isBlocked)
);

CREATE TABLE followed (
idUser INTEGER NOT NULL REFERENCES users (idUser) ON UPDATE CASCADE,
idPost INTEGER NOT NULL REFERENCES post (idPost) ON UPDATE CASCADE,
PRIMARY KEY (idUser, idPost)
);

CREATE TABLE userCommunity (
idUser INTEGER NOT NULL REFERENCES users (idUser) ON UPDATE CASCADE,
idCommunity INTEGER NOT NULL REFERENCES community (idCommunity) ON UPDATE CASCADE,
PRIMARY KEY (idUser, idCommunity)
);

CREATE TABLE questionTag (
idQuestionTag SERIAL PRIMARY KEY,
idQuestion INTEGER REFERENCES question (idPost) ON UPDATE CASCADE, 
idTag INTEGER REFERENCES tag (idTag) ON UPDATE CASCADE
);

CREATE TABLE interact (
idInteract SERIAL PRIMARY KEY,
idPost INTEGER REFERENCES post (idPost) ON UPDATE CASCADE,
idUser INTEGER REFERENCES users (idUser) ON UPDATE CASCADE
isLiked BOOLEAN NOT NULL, 
isDisliked BOOLEAN NOT NULL,
CONSTRAINT like_dislike_ck CHECK (isLiked != isDisliked)
);


-- Indexes --

CREATE INDEX user_post ON post USING btree (idPost); 
CLUSTER post USING user_post;

CREATE INDEX answer_question ON answer USING btree (idQuestion);
CLUSTER answer USING answer_question;

CREATE INDEX questionbytag ON questionTag USING btree (idTag);
CLUSTER questionTag USING questionbytag;

-- Triggers --

CREATE FUNCTION delete_user() RETURNS TRIGGER AS
$BODY$
BEGIN
        DELETE FROM assUsertag where (NEW.idUser = idUser);
        DELETE FROM userCommunity where (NEW.idUser = idUser);
        DELETE FROM posts where (NEW.idUser = idUser);
        DELETE FROM followed where (NEW.idUser = idUser);
        RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;

CREATE TRIGGER delete_user
        BEFORE DELETE ON users
        FOR EACH ROW
        EXECUTE PROCEDURE delete_user();


-- Add column to question to store computed ts_vectors.
ALTER TABLE users
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION user_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.name), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.name <> OLD.name) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.name), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER user_search_update
 BEFORE INSERT OR UPDATE ON users
 FOR EACH ROW
 EXECUTE PROCEDURE user_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx_user ON users USING GIN (tsvectors);


-- Add column to question to store computed ts_vectors.
ALTER TABLE community
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION community_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.name), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.name <> OLD.name) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.name), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER community_search_update
 BEFORE INSERT OR UPDATE ON community
 FOR EACH ROW
 EXECUTE PROCEDURE community_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx_community ON community USING GIN (tsvectors);


-- Add column to question to store computed ts_vectors.
ALTER TABLE tag
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION tag_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.name), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.name <> OLD.name) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.name), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER tag_search_update
 BEFORE INSERT OR UPDATE ON tag
 FOR EACH ROW
 EXECUTE PROCEDURE tag_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx_tag ON tag USING GIN (tsvectors);


-- Add column to question to store computed ts_vectors.
ALTER TABLE question
ADD COLUMN tsvectors TSVECTOR;

-- Create a function to automatically update ts_vectors.
CREATE FUNCTION question_search_update() RETURNS TRIGGER AS $$
BEGIN
 IF TG_OP = 'INSERT' THEN
        NEW.tsvectors = (
         setweight(to_tsvector('english', NEW.title), 'A')
        );
 END IF;
 IF TG_OP = 'UPDATE' THEN
         IF (NEW.title <> OLD.title) THEN
           NEW.tsvectors = (
             setweight(to_tsvector('english', NEW.title), 'A')
           );
         END IF;
 END IF;
 RETURN NEW;
END $$
LANGUAGE plpgsql;

-- Create a trigger before insert or update on work.
CREATE TRIGGER question_search_update
 BEFORE INSERT OR UPDATE ON question
 FOR EACH ROW
 EXECUTE PROCEDURE question_search_update();


-- Finally, create a GIN index for ts_vectors.
CREATE INDEX search_idx_vector ON question USING GIN (tsvectors);


--- Transactions ---


BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Insert answer 
INSERT INTO answer (idPost, idQuestion, isVerified )
VALUES (currval('post_id_seq'), new.idQuestion, new.isVerified);
-- Insert post
INSERT INTO post (idPost, idUser, date, score, content) VALUES (currval('post_id_seq'), new.idUser, new.date, new.score, new.content); 
END TRANSACTION;


BEGIN TRANSACTION; 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
SELECT COUNT(*) 
FROM post 
WHERE NEW.idUser = post.idUser AND date < now();
END TRANSACTION;


BEGIN TRANSACTION; 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Insert question 
INSERT INTO question (idPost, title)
VALUES (currval('post_id_seq'), new.title);
-- Insert post 
INSERT INTO post (idPost, idUser, date, score, content) VALUES (currval('post_id_seq'), new.idUser, new.date, new.score, new.content); 
END TRANSACTION;
```


**5. Database Population**

```sql
INSERT INTO user (idUser, email, name, password, bio, isModerator) 
VALUES (1, "a@gmail.com", "Alberto", "a", "Abio", True);
INSERT INTO user (idUser, email, name, password, bio, isModerator) 
VALUES (2, "b@gmail.com", "Berto", "b", "Bbio", False);
INSERT INTO user (idUser, email, name, password, bio, isModerator) 
VALUES (3, "c@gmail.com", "Calberto", "c", "Cbio", False);

INSERT INTO post (idPost, idUser, date, score, content) 
VALUES (1, 1, '2000-01-01' , 0, 'Content #1');
INSERT INTO post (idPost, idUser, date, score, content) 
VALUES (2, 1, '2021-01-01' , 6, 'Content #2');
INSERT INTO post (idPost, idUser, date, score, content) 
VALUES (3, 2, '2015-01-01' , 3, 'Content #3');
INSERT INTO post (idPost, idUser, date, score, content) 
VALUES (4, 2, '2015-01-01' , 3, 'Yes');



INSERT INTO question(idPost, title)
VALUES (1, 'Can dogs bounce?');
INSERT INTO question(idPost, title)
VALUES (2, 'Can pandas bounce?');
INSERT INTO question(idPost, title)
VALUES (3, 'Can chicken bounce?');

INSERT INTO answer(idPost, idQuestion, isVerified)
VALUES (4, 1, True);

INSERT INTO tag(idTag, name)
VALUES (1, 'Politics');

INSERT INTO tag(idTag, name)
VALUES (2, 'Religion');

INSERT INTO tag(idTag, name)
VALUES (3, 'Youtube');

INSERT INTO community (idCommunity, name, date)
VALUES (1, 'Children united', '2001-09-12');
INSERT INTO community (idCommunity, name, date)
VALUES (2, 'Love & Peace', '2011-10-12');

INSERT INTO assUserTag (idUserTag, idUser, idTag, isFavorite, isBlocked)
VALUES (1, 1, 1, True, False);
INSERT INTO assUserTag (idUserTag, idUser, idTag, isFavorite, isBlocked)
VALUES (2, 1, 2, False, True);

INSERT INTO followed (idUser, idPost)
VALUES (1,3);

INSERT INTO userCommunity (idUser, idCommunity)
VALUES (1, 2);
INSERT INTO userCommunity (idUser, idCommunity)
VALUES (2, 1);

INSERT INTO questionTag (idQuestionTag, idQuestion, idTag)
VALUES (1, 1, 3);

INSERT INTO interact (idPost, idUser)
VALUES (1,1)
```  

## Revision History

Changes made to the second submission:

1. 22/10/2022- Added content relative of A4.
 
2. 22/10/2022- Added content relative of A5. 

3. 23/10/2022- Added content relative of A6 and database schema and population. 

-----
GROUP22112, 24/10/2022
* Group member 1: Luís Tiago Trindade Cabral, up202006464@edu.fe.up.pt (Editor)
* Group member 2: Rui Brogueira Andrade, up202007539@edu.fe.up.pt (Editor)
* Group member 3: Bárbara Filipa da Silva Carvalho, up202004695@edu.fe.up.pt (Editor)
* Group member 4: José Miguel Carvalho Rodrigues, up201809590@edu.fc.up.pt (Editor)