From c7e41619bd0411b88df53f98427088c19e7f56ed Mon Sep 17 00:00:00 2001 From: VLADISLAV NACHIKOV Date: Wed, 27 Aug 2025 21:13:32 +0200 Subject: [PATCH 1/2] Done 1st week --- Week1/databases/ex1_meetup.js | 110 ++++++++++++++++++++++++++++++++++ Week1/databases/ex2_world.js | 107 +++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 Week1/databases/ex1_meetup.js create mode 100644 Week1/databases/ex2_world.js diff --git a/Week1/databases/ex1_meetup.js b/Week1/databases/ex1_meetup.js new file mode 100644 index 000000000..23ce641cf --- /dev/null +++ b/Week1/databases/ex1_meetup.js @@ -0,0 +1,110 @@ +// ex1_meetup.js +require("dotenv").config(); +const { Client } = require("pg"); + +const adminConfig = { + host: process.env.PGHOST, + port: process.env.PGPORT, + user: process.env.PGUSER, + password: process.env.PGPASSWORD, + database: "postgres", // connect to the default "postgres" DB to create/drop "meetup" +}; + +const meetupConfig = { + ...adminConfig, + database: "meetup", +}; + +async function run() { + const admin = new Client(adminConfig); + await admin.connect(); + + // 1) Reset and recreate the DB (so the script can be run repeatedly) + // Close active connections (just in case), then drop and create again + await admin.query(` + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = 'meetup' AND pid <> pg_backend_pid(); + `); + await admin.query(`DROP DATABASE IF EXISTS meetup;`); + await admin.query(`CREATE DATABASE meetup;`); + await admin.end(); + + // 2) Now work inside the "meetup" database + const db = new Client(meetupConfig); + await db.connect(); + + // 3) Create tables (drop first just in case) + await db.query(`DROP TABLE IF EXISTS Meeting;`); + await db.query(`DROP TABLE IF EXISTS Room;`); + await db.query(`DROP TABLE IF EXISTS Invitee;`); + + await db.query(` + CREATE TABLE Invitee ( + invitee_no SERIAL PRIMARY KEY, + invitee_name VARCHAR(100) NOT NULL, + invited_by VARCHAR(100) NOT NULL + ); + `); + + await db.query(` + CREATE TABLE Room ( + room_no SERIAL PRIMARY KEY, + room_name VARCHAR(100) NOT NULL, + floor_number INTEGER NOT NULL + ); + `); + + await db.query(` + CREATE TABLE Meeting ( + meeting_no SERIAL PRIMARY KEY, + meeting_title VARCHAR(200) NOT NULL, + starting_time TIMESTAMP NOT NULL, + ending_time TIMESTAMP NOT NULL, + room_no INTEGER NOT NULL REFERENCES Room(room_no) + ); + `); + + // 4) Seed data (5 rows per table) + await db.query(` + INSERT INTO Invitee (invitee_name, invited_by) VALUES + ('Alice', 'Vlad'), + ('Bob', 'Vlad'), + ('Carol', 'Dima'), + ('David', 'Dima'), + ('Eve', 'Lena'); + `); + + const rooms = await db.query(` + INSERT INTO Room (room_name, floor_number) VALUES + ('Amsterdam', 1), + ('Rotterdam', 2), + ('Utrecht', 3), + ('Eindhoven', 2), + ('Haarlem', 1) + RETURNING room_no; + `); + + // Use actual room_no values returned by INSERT ... RETURNING + const r = rooms.rows.map((x) => x.room_no); + + await db.query( + ` + INSERT INTO Meeting (meeting_title, starting_time, ending_time, room_no) VALUES + ('Intro to Databases', '2025-08-27 10:00', '2025-08-27 11:00', $1), + ('PostgreSQL Basics', '2025-08-27 12:00', '2025-08-27 13:00', $2), + ('Schema Design', '2025-08-28 10:00', '2025-08-28 11:30', $3), + ('SQL CRUD Practice', '2025-08-28 12:00', '2025-08-28 13:00', $4), + ('Indexes & Performance', '2025-08-29 09:30', '2025-08-29 10:30', $5); + `, + r.slice(0, 5) + ); // bind the five room_no placeholders + + console.log("Exercise 1: DONE (database, tables, and seed inserted)"); + await db.end(); +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/Week1/databases/ex2_world.js b/Week1/databases/ex2_world.js new file mode 100644 index 000000000..9c857a796 --- /dev/null +++ b/Week1/databases/ex2_world.js @@ -0,0 +1,107 @@ +// ex2_world.js +require("dotenv").config(); +const { Client } = require("pg"); + +const worldConfig = { + host: process.env.PGHOST, + port: process.env.PGPORT, + user: process.env.PGUSER, + password: process.env.PGPASSWORD, + database: "world", +}; + +async function q(db, label, sql, params = []) { + const res = await db.query(sql, params); + console.log(`\n=== ${label} ===`); + // print only the first 10 rows for readability + console.table(res.rows.slice(0, 10)); + return res; +} + +async function run() { + const db = new Client(worldConfig); + await db.connect(); + + // 1) Countries with population > 8M + await q( + db, + "Countries with population > 8M", + `SELECT name FROM country WHERE population > 8000000 ORDER BY population DESC;` + ); + + // 2) Countries that have "land" in their names + await q( + db, + 'Countries with "land" in name', + `SELECT name FROM country WHERE name ILIKE '%land%' ORDER BY name;` + ); + + // 3) Cities with population between 500k and 1M + await q( + db, + "Cities 500k..1M", + `SELECT name, population FROM city WHERE population BETWEEN 500000 AND 1000000 ORDER BY population DESC;` + ); + + // 4) Countries on the continent 'Europe' + await q( + db, + "Countries in Europe", + `SELECT name FROM country WHERE continent = 'Europe' ORDER BY name;` + ); + + // 5) Countries by surface area (desc) + await q( + db, + "Countries by surface area (DESC)", + `SELECT name, surfacearea FROM country ORDER BY surfacearea DESC;` + ); + + // 6) Names of all cities in the Netherlands + await q( + db, + "Cities in the Netherlands", + `SELECT c.name FROM city c + JOIN country co ON c.countrycode = co.code + WHERE co.name = 'Netherlands' + ORDER BY c.name;` + ); + + // 7) Population of Rotterdam + await q( + db, + "Population of Rotterdam", + `SELECT c.population FROM city c + JOIN country co ON c.countrycode = co.code + WHERE co.name = 'Netherlands' AND c.name = 'Rotterdam';` + ); + + // 8) Top 10 countries by surface area + await q( + db, + "Top 10 countries by surface area", + `SELECT name, surfacearea FROM country ORDER BY surfacearea DESC LIMIT 10;` + ); + + // 9) Top 10 most populated cities + await q( + db, + "Top 10 most populated cities", + `SELECT name, population FROM city ORDER BY population DESC LIMIT 10;` + ); + + // 10) Estimated world population (sum of country populations) + await q( + db, + "World population (sum of countries.population)", + `SELECT SUM(population)::BIGINT AS world_population FROM country;` + ); + + await db.end(); + console.log("\nExercise 2: DONE"); +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); From d59d1149b4b32067d24cbe00d4ce1058ae5a70b5 Mon Sep 17 00:00:00 2001 From: VLADISLAV NACHIKOV Date: Wed, 3 Sep 2025 18:18:19 +0200 Subject: [PATCH 2/2] Fixes for pull request --- Week1/databases/ex1_meetup.js | 227 ++++++++++++++++++++-------------- Week1/databases/ex2_world.js | 205 +++++++++++++++++------------- 2 files changed, 252 insertions(+), 180 deletions(-) diff --git a/Week1/databases/ex1_meetup.js b/Week1/databases/ex1_meetup.js index 23ce641cf..410e75598 100644 --- a/Week1/databases/ex1_meetup.js +++ b/Week1/databases/ex1_meetup.js @@ -1,110 +1,149 @@ -// ex1_meetup.js require("dotenv").config(); const { Client } = require("pg"); +// Admin connection (connects to default "postgres" DB) +// Used only for creating/dropping the "meetup" database const adminConfig = { host: process.env.PGHOST, port: process.env.PGPORT, user: process.env.PGUSER, password: process.env.PGPASSWORD, - database: "postgres", // connect to the default "postgres" DB to create/drop "meetup" + database: "postgres", }; -const meetupConfig = { - ...adminConfig, - database: "meetup", -}; +// Config to connect to the actual "meetup" DB +const meetupConfig = { ...adminConfig, database: "meetup" }; async function run() { - const admin = new Client(adminConfig); - await admin.connect(); - - // 1) Reset and recreate the DB (so the script can be run repeatedly) - // Close active connections (just in case), then drop and create again - await admin.query(` - SELECT pg_terminate_backend(pid) - FROM pg_stat_activity - WHERE datname = 'meetup' AND pid <> pg_backend_pid(); - `); - await admin.query(`DROP DATABASE IF EXISTS meetup;`); - await admin.query(`CREATE DATABASE meetup;`); - await admin.end(); - - // 2) Now work inside the "meetup" database - const db = new Client(meetupConfig); - await db.connect(); - - // 3) Create tables (drop first just in case) - await db.query(`DROP TABLE IF EXISTS Meeting;`); - await db.query(`DROP TABLE IF EXISTS Room;`); - await db.query(`DROP TABLE IF EXISTS Invitee;`); - - await db.query(` - CREATE TABLE Invitee ( - invitee_no SERIAL PRIMARY KEY, - invitee_name VARCHAR(100) NOT NULL, - invited_by VARCHAR(100) NOT NULL - ); - `); + let admin; // reference to admin client + let db; // reference to meetup client - await db.query(` - CREATE TABLE Room ( - room_no SERIAL PRIMARY KEY, - room_name VARCHAR(100) NOT NULL, - floor_number INTEGER NOT NULL - ); - `); - - await db.query(` - CREATE TABLE Meeting ( - meeting_no SERIAL PRIMARY KEY, - meeting_title VARCHAR(200) NOT NULL, - starting_time TIMESTAMP NOT NULL, - ending_time TIMESTAMP NOT NULL, - room_no INTEGER NOT NULL REFERENCES Room(room_no) + try { + // ================================ + // 1. Reset the "meetup" database + // ================================ + admin = new Client(adminConfig); + await admin.connect(); + + // Terminate active connections (safety measure) + await admin.query(` + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = 'meetup' AND pid <> pg_backend_pid(); + `); + + // Drop and recreate the database + await admin.query(`DROP DATABASE IF EXISTS meetup;`); + await admin.query(`CREATE DATABASE meetup;`); + + // Close admin connection (not needed anymore) + await admin.end(); + admin = null; + + // ================================ + // 2. Connect to "meetup" database + // ================================ + db = new Client(meetupConfig); + await db.connect(); + + // Drop old tables (if they exist, order matters because of FK dependencies) + await db.query(`DROP TABLE IF EXISTS Meeting;`); + await db.query(`DROP TABLE IF EXISTS Room;`); + await db.query(`DROP TABLE IF EXISTS Invitee;`); + + // ================================ + // 3. Create tables + // ================================ + + // Invitees table: list of people and who invited them + await db.query(` + CREATE TABLE Invitee ( + invitee_no INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + invitee_name VARCHAR(100) NOT NULL, + invited_by VARCHAR(100) NOT NULL + ); + `); + + // Rooms table: meeting rooms with floor number + await db.query(` + CREATE TABLE Room ( + room_no INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + room_name VARCHAR(100) NOT NULL, + floor_number SMALLINT NOT NULL, + CONSTRAINT room_floor_number_range CHECK (floor_number BETWEEN -5 AND 200) + ); + `); + + // Meetings table: references Room + await db.query(` + CREATE TABLE Meeting ( + meeting_no INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + meeting_title VARCHAR(200) NOT NULL, + starting_time TIMESTAMPTZ NOT NULL, + ending_time TIMESTAMPTZ NOT NULL, + room_no INTEGER NOT NULL REFERENCES Room(room_no), + CONSTRAINT meeting_time_valid CHECK (ending_time > starting_time) + ); + `); + + // ================================ + // 4. Seed data + // ================================ + + // Insert 5 invitees + await db.query(` + INSERT INTO Invitee (invitee_name, invited_by) VALUES + ('Alice','Vlad'), + ('Bob','Vlad'), + ('Carol','Dima'), + ('David','Dima'), + ('Eve','Lena'); + `); + + // Insert 5 rooms and return generated IDs + const rooms = await db.query(` + INSERT INTO Room (room_name, floor_number) VALUES + ('Amsterdam', 1), + ('Rotterdam', 2), + ('Utrecht', 3), + ('Eindhoven', 2), + ('Haarlem', 1) + RETURNING room_no; + `); + + // Extract room_no values to bind in Meeting inserts + const r = rooms.rows.map((x) => x.room_no); + + // Insert 5 meetings linked to the inserted rooms + await db.query( + ` + INSERT INTO Meeting (meeting_title, starting_time, ending_time, room_no) VALUES + ('Intro to Databases', '2025-08-27 10:00+00', '2025-08-27 11:00+00', $1), + ('PostgreSQL Basics', '2025-08-27 12:00+00', '2025-08-27 13:00+00', $2), + ('Schema Design', '2025-08-28 10:00+00', '2025-08-28 11:30+00', $3), + ('SQL CRUD Practice', '2025-08-28 12:00+00', '2025-08-28 13:00+00', $4), + ('Indexes & Performance', '2025-08-29 09:30+00', '2025-08-29 10:30+00', $5); + `, + r.slice(0, 5) // bind five room IDs ); - `); - - // 4) Seed data (5 rows per table) - await db.query(` - INSERT INTO Invitee (invitee_name, invited_by) VALUES - ('Alice', 'Vlad'), - ('Bob', 'Vlad'), - ('Carol', 'Dima'), - ('David', 'Dima'), - ('Eve', 'Lena'); - `); - - const rooms = await db.query(` - INSERT INTO Room (room_name, floor_number) VALUES - ('Amsterdam', 1), - ('Rotterdam', 2), - ('Utrecht', 3), - ('Eindhoven', 2), - ('Haarlem', 1) - RETURNING room_no; - `); - - // Use actual room_no values returned by INSERT ... RETURNING - const r = rooms.rows.map((x) => x.room_no); - - await db.query( - ` - INSERT INTO Meeting (meeting_title, starting_time, ending_time, room_no) VALUES - ('Intro to Databases', '2025-08-27 10:00', '2025-08-27 11:00', $1), - ('PostgreSQL Basics', '2025-08-27 12:00', '2025-08-27 13:00', $2), - ('Schema Design', '2025-08-28 10:00', '2025-08-28 11:30', $3), - ('SQL CRUD Practice', '2025-08-28 12:00', '2025-08-28 13:00', $4), - ('Indexes & Performance', '2025-08-29 09:30', '2025-08-29 10:30', $5); - `, - r.slice(0, 5) - ); // bind the five room_no placeholders - - console.log("Exercise 1: DONE (database, tables, and seed inserted)"); - await db.end(); + + console.log(" Exercise 1: DONE (database, tables, and seed data inserted)"); + } catch (err) { + console.error("Script failed:", err); + process.exitCode = 1; // do not force quit immediately, allow finally block + } finally { + // Ensure connections are closed even if error occurred + if (db) { + try { + await db.end(); + } catch {} + } + if (admin) { + try { + await admin.end(); + } catch {} + } + } } -run().catch((err) => { - console.error(err); - process.exit(1); -}); +run(); diff --git a/Week1/databases/ex2_world.js b/Week1/databases/ex2_world.js index 9c857a796..a84898d76 100644 --- a/Week1/databases/ex2_world.js +++ b/Week1/databases/ex2_world.js @@ -1,4 +1,3 @@ -// ex2_world.js require("dotenv").config(); const { Client } = require("pg"); @@ -10,98 +9,132 @@ const worldConfig = { database: "world", }; +// Small helper to run a query and pretty-print up to 10 rows async function q(db, label, sql, params = []) { const res = await db.query(sql, params); console.log(`\n=== ${label} ===`); - // print only the first 10 rows for readability console.table(res.rows.slice(0, 10)); return res; } async function run() { - const db = new Client(worldConfig); - await db.connect(); - - // 1) Countries with population > 8M - await q( - db, - "Countries with population > 8M", - `SELECT name FROM country WHERE population > 8000000 ORDER BY population DESC;` - ); - - // 2) Countries that have "land" in their names - await q( - db, - 'Countries with "land" in name', - `SELECT name FROM country WHERE name ILIKE '%land%' ORDER BY name;` - ); - - // 3) Cities with population between 500k and 1M - await q( - db, - "Cities 500k..1M", - `SELECT name, population FROM city WHERE population BETWEEN 500000 AND 1000000 ORDER BY population DESC;` - ); - - // 4) Countries on the continent 'Europe' - await q( - db, - "Countries in Europe", - `SELECT name FROM country WHERE continent = 'Europe' ORDER BY name;` - ); - - // 5) Countries by surface area (desc) - await q( - db, - "Countries by surface area (DESC)", - `SELECT name, surfacearea FROM country ORDER BY surfacearea DESC;` - ); - - // 6) Names of all cities in the Netherlands - await q( - db, - "Cities in the Netherlands", - `SELECT c.name FROM city c - JOIN country co ON c.countrycode = co.code - WHERE co.name = 'Netherlands' - ORDER BY c.name;` - ); - - // 7) Population of Rotterdam - await q( - db, - "Population of Rotterdam", - `SELECT c.population FROM city c - JOIN country co ON c.countrycode = co.code - WHERE co.name = 'Netherlands' AND c.name = 'Rotterdam';` - ); - - // 8) Top 10 countries by surface area - await q( - db, - "Top 10 countries by surface area", - `SELECT name, surfacearea FROM country ORDER BY surfacearea DESC LIMIT 10;` - ); - - // 9) Top 10 most populated cities - await q( - db, - "Top 10 most populated cities", - `SELECT name, population FROM city ORDER BY population DESC LIMIT 10;` - ); - - // 10) Estimated world population (sum of country populations) - await q( - db, - "World population (sum of countries.population)", - `SELECT SUM(population)::BIGINT AS world_population FROM country;` - ); - - await db.end(); - console.log("\nExercise 2: DONE"); + let db; // keep a reference so we can close it in finally + + try { + db = new Client(worldConfig); + await db.connect(); + + // 1) Countries with population > 8M + await q( + db, + "Countries with population > 8M", + `SELECT name + FROM country + WHERE population > 8000000 + ORDER BY population DESC;` + ); + + // 2) Countries that have "land" in their names + await q( + db, + 'Countries with "land" in name', + `SELECT name + FROM country + WHERE name ILIKE '%land%' + ORDER BY name;` + ); + + // 3) Cities with population between 500k and 1M + await q( + db, + "Cities 500k..1M", + `SELECT name, population + FROM city + WHERE population BETWEEN 500000 AND 1000000 + ORDER BY population DESC;` + ); + + // 4) Countries on the continent 'Europe' + await q( + db, + "Countries in Europe", + `SELECT name + FROM country + WHERE continent = 'Europe' + ORDER BY name;` + ); + + // 5) Countries by surface area (desc) + await q( + db, + "Countries by surface area (DESC)", + `SELECT name, surfacearea + FROM country + ORDER BY surfacearea DESC;` + ); + + // 6) Names of all cities in the Netherlands + await q( + db, + "Cities in the Netherlands", + `SELECT c.name + FROM city c + JOIN country co ON c.countrycode = co.code + WHERE co.name = 'Netherlands' + ORDER BY c.name;` + ); + + // 7) Population of Rotterdam + await q( + db, + "Population of Rotterdam", + `SELECT c.population + FROM city c + JOIN country co ON c.countrycode = co.code + WHERE co.name = 'Netherlands' AND c.name = 'Rotterdam';` + ); + + // 8) Top 10 countries by surface area + await q( + db, + "Top 10 countries by surface area", + `SELECT name, surfacearea + FROM country + ORDER BY surfacearea DESC + LIMIT 10;` + ); + + // 9) Top 10 most populated cities + await q( + db, + "Top 10 most populated cities", + `SELECT name, population + FROM city + ORDER BY population DESC + LIMIT 10;` + ); + + // 10) Estimated world population (sum of country populations) + await q( + db, + "World population (sum of countries.population)", + `SELECT SUM(population)::BIGINT AS world_population + FROM country;` + ); + + console.log("\n Exercise 2: DONE"); + } catch (err) { + console.error(" Exercise 2 failed:", err); + // do not exit immediately; allow finally to close the connection + process.exitCode = 1; + } finally { + // always close the connection + if (db) { + try { + await db.end(); + } catch {} + } + } } -run().catch((err) => { - console.error(err); - process.exit(1); -}); +run();