diff --git a/Week3/Assignments/SQLInjection.js b/Week3/Assignments/SQLInjection.js new file mode 100644 index 000000000..45bbb2a54 --- /dev/null +++ b/Week3/Assignments/SQLInjection.js @@ -0,0 +1,30 @@ +function getPopulation(Country, name = "' OR '1'='1", code = "' OR '1'='1", cb) { + // assuming that connection to the database is established and stored as conn + conn.query( + `SELECT Population FROM ${Country} WHERE Name = '${name}' and code = '${code}'`, + [Country, name, code], + function (err, result) { + if (err) cb(err); + if (result.length == 0) cb(new Error("Not found")); + cb(null, result[0].name); + } + ); +} +// the query becomes SELECT Population FROM Country WHERE Name = '' OR '1'='1' AND code = '' OR '1'='1'; +// which is always true and returns the population of all countries +// to fix this we can use parameterized queries and whitelist for table names +function getPopulation(Country, name, code, cb) { +const allowedTables=['Country']; +if(!allowedTables.includes(Country)){ + throw new Error('Invalid table'); + } + conn.query( + `SELECT Population FROM ${Country} WHERE Name = $1 and code = $2`, + [name, code], + function (err, result) { + if (err) cb(err); + if (result.length == 0) cb(new Error("Not found")); + cb(null, result[0].Population); + } + ); +} \ No newline at end of file diff --git a/Week3/Assignments/account_changes.js b/Week3/Assignments/account_changes.js new file mode 100644 index 000000000..0b695bcb8 --- /dev/null +++ b/Week3/Assignments/account_changes.js @@ -0,0 +1,24 @@ +const {client}=require('./connection.js'); +async function makeTransaction(){ + try{ + await client.query('BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE'); + const res=await client.query('select balance from account where account_number =$1',[101]); + console.log('Current balance for account 101:', res.rows[0].balance); + if(res.rows[0].balance<=1000){ + throw new Error('Insufficient funds in account 101'); + } + await client.query('UPDATE account SET balance=balance - $1 WHERE account_number=$2',[1000,101]); + await client.query('UPDATE account SET balance=balance + $1 WHERE account_number=$2',[1000,102]); + await client.query('INSERT INTO account_changes (account_number,amount,remark) VALUES ($1,$2,$3)',[101,-1000,'Transfer to account 102']); + await client.query('INSERT INTO account_changes(account_number,amount,remark) VALUES ($1,$2,$3)',[102,1000,'Transfer from account 101']); + await client.query('COMMIT'); + console.log('Transaction completed successfully'); + } + catch(err){ + await client.query('ROLLBACK'); + console.error('Error making transaction', err.message); + } + finally{ + await client.end(); + } +} \ No newline at end of file diff --git a/Week3/Assignments/connection.js b/Week3/Assignments/connection.js new file mode 100644 index 000000000..7ae0d94a8 --- /dev/null +++ b/Week3/Assignments/connection.js @@ -0,0 +1,15 @@ +const { Client } = require('pg'); + +const client = new Client({ + user: 'hyfuser', + host: 'localhost', + database: 'FinanceDB', + password: 'hyfpassword', + port: 5432, +}); + +client.connect() + .then(() => console.log('Connected to PostgreSQL')) + .catch(err => console.error('Connection error', err.stack)); + +module.exports = client; diff --git a/Week3/Assignments/exercise1.md b/Week3/Assignments/exercise1.md new file mode 100644 index 000000000..4c0839b9f --- /dev/null +++ b/Week3/Assignments/exercise1.md @@ -0,0 +1,55 @@ +# Database Normalization Answers + +## 1. Columns that violate 1NF + +The following columns violate 1NF because they contain multiple values in a single cell: +- `food_code` (e.g., `C1, C2`) +- `food_description` (e.g., `Curry, Cake`) + +## 2. Recognized Entities + +Entities that can be extracted: + +1. **Member** + - Columns: `member_id`, `member_name`, `member_address` +2. **Dinner** + - Columns: `dinner_id`, `dinner_date`, `venue_code` +3. **Venue** + - Columns: `venue_code`, `venue_description` +4. **Food** + - Columns: `food_code`, `food_description` + +**Relationships:** +- Many-to-many between Member and Dinner → junction table `Member_Dinner` +- Many-to-many between Dinner and Food → junction table `Dinner_Food` +- One-to-Many between dinner and venue + +## 3. 3NF Compliant Tables and Columns + +### Member +- `member_id` (PK) +- `member_name` +- `member_address` + +### Venue +- `venue_code` (PK) +- `venue_description` + +### Dinner +- `dinner_id` (PK) +- `dinner_date` +- `venue_code` (FK → Venue) + +### Food +- `food_code` (PK) +- `food_description` + +### Member_Dinner (junction table) +- `member_id` (FK → Member) +- `dinner_id` (FK → Dinner) +- PK = (`member_id`, `dinner_id`) + +### Dinner_Food (junction table) +- `dinner_id` (FK → Dinner) +- `food_code` (FK → Food) +- PK = (`dinner_id`, `food_code`) diff --git a/Week3/Assignments/test.js b/Week3/Assignments/test.js new file mode 100644 index 000000000..5fa39d8ce --- /dev/null +++ b/Week3/Assignments/test.js @@ -0,0 +1,50 @@ +import pkg from "pg"; +const { Client } = pkg; +const client1 = new Client({ + user: "postgres", // change if needed + host: "localhost", + database: "testdb", // change if needed + password: "yourpassword", // change if needed + port: 5432, +}); +const client2 = new Client({ + user: "postgres", + host: "localhost", + database: "testdb", + password: "yourpassword", + port: 5432, +}); +async function run() { + await client1.connect(); + await client2.connect(); + try { + // Transaction A (withdraw 100) + const txA = async () => { + await client1.query("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + const res = await client1.query("SELECT balance FROM accounts WHERE account_id = 1"); + console.log("TxA sees balance:", res.rows[0].balance); + await new Promise(r => setTimeout(r, 2000)); // simulate delay + await client1.query("UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"); + await client1.query("COMMIT"); + console.log("TxA committed"); + }; + // Transaction B (withdraw 100 at the same time) + const txB = async () => { + await client2.query("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + const res = await client2.query("SELECT balance FROM accounts WHERE account_id = 1"); + console.log("TxB sees balance:", res.rows[0].balance); + await new Promise(r => setTimeout(r, 1000)); // simulate overlap + await client2.query("UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"); + await client2.query("COMMIT"); + console.log("TxB committed"); + }; + // Run both "at the same time" + await Promise.allSettled([txA(), txB()]); + } catch (err) { + console.error("Error:", err.message); + } finally { + await client1.end(); + await client2.end(); + } +} +run(); \ No newline at end of file diff --git a/Week3/Assignments/transaction-creat-table.js b/Week3/Assignments/transaction-creat-table.js new file mode 100644 index 000000000..4f79b55fa --- /dev/null +++ b/Week3/Assignments/transaction-creat-table.js @@ -0,0 +1,30 @@ +const{client}=require('./connection.js'); + +async function createTables(){ + try{ + await client.query('DROP TABLE IF EXISTS account_changes'); + await client.query('DROP TABLE IF EXISTS account'); + + const createAccountTableQuery=`CREATE TABLE account( + account_number INT PRIMARY KEY, + balance NUMERIC(12,2) NOT NULL);`; + const creatAccount_changesTableQuery=`CREATE TABLE account_changes( + change_number SERIAL PRIMARY KEY, + account_number INT , + AMOUNT NUMERIC(12,2) NOT NULL, + changed_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(255), + FOREIGN KEY (account_number) REFERENCES account(account_number) ON DELETE CASCADE);`; + await client.query(createAccountTableQuery); + await client.query(creatAccount_changesTableQuery); + console.log('Tables created successfully'); + + + } + catch(err){ + console.error('Error creating tables', err.message); + }finally{ + await client.end(); + } +} +createTables(); \ No newline at end of file diff --git a/Week3/Assignments/transaction-insert-values.js b/Week3/Assignments/transaction-insert-values.js new file mode 100644 index 000000000..ee3381a85 --- /dev/null +++ b/Week3/Assignments/transaction-insert-values.js @@ -0,0 +1,33 @@ +const {client}=require('./connection.js'); +async function insertValues(){ + try{ + const insertAccountQuery=`INSERT INTO account (account_number,balance) VALUES ($1,$2),($3,$4),($5,$6),($7,$8) + ON CONFLICT (account_number) DO NOTHING;`; + const values=[ + [101, 1000], + [102, 2000], + [103, 1500], + [104, 3000] + ]; + const res=await client.query(insertAccountQuery, values); + console.log('Inserted account numbers:', res.rows); + const insertAccountChangesQuery=`INSERT INTO account_changes (account_number, amount, remark) VALUES + ($1,$2,$3),($4,$5,$6),($7,$8,$9),($10,$11,$12) + `; + const changesValues=[ + [101, 2000, 'Initial deposit'], + [102, 500, 'Initial deposit'], + [103, 1000, 'Initial deposit'], + [104, 300, 'Initial deposit'] + ]; + + await client.query(insertAccountChangesQuery, changesValues); + console.log('Inserted values into account_changes table'); + } + catch(err){ + console.error('Error inserting values', err.message); + }finally{ + await client.end(); + } +} +insertValues(); \ No newline at end of file diff --git a/Week3/homework/mongodb/index.js b/Week3/homework/mongodb/index.js index 41ee8b618..e998c3e36 100644 --- a/Week3/homework/mongodb/index.js +++ b/Week3/homework/mongodb/index.js @@ -1,8 +1,15 @@ +require('dotenv').config(); const { MongoClient, ServerApiVersion } = require("mongodb"); + const { seedDatabase } = require("./seedDatabase.js"); async function createEpisodeExercise(client) { + const res=await client.db('DatabaseWeek3').collection('bob_ross_episodes').insertOne({ + episode: 'S09E13', + title: 'MOUNTAIN HIDE-AWAY', + elements: ["CIRRUS", "CLOUDS", "CONIFER", "DECIDIOUS", "GRASS", "MOUNTAIN", "MOUNTAINS", "RIVER", "SNOWY_MOUNTAIN", "TREE", "TREES"] + }) /** * We forgot to add the last episode of season 9. It has this information: * @@ -14,11 +21,12 @@ async function createEpisodeExercise(client) { // Write code that will add this to the collection! console.log( - `Created season 9 episode 13 and the document got the id ${"TODO: fill in variable here"}` + `Created season 9 episode 13 and the document got the id ${res.insertedId}` ); } async function findEpisodesExercises(client) { + const res=await client.db('DatabaseWeek3').collection('bob_ross_episodes').findOne({episode:'S02E02'}) /** * Complete the following exercises. * The comments indicate what to do and what the result should be! @@ -27,29 +35,39 @@ async function findEpisodesExercises(client) { // Find the title of episode 2 in season 2 [Should be: WINTER SUN] console.log( - `The title of episode 2 in season 2 is ${"TODO: fill in variable here"}` + `The title of episode 2 in season 2 is ${res.title}` ); + const res2=await client.db('DatabaseWeek3').collection('bob_ross_episodes').findOne({title:'BLACK RIVER'}) // Find the season and episode number of the episode called "BLACK RIVER" [Should be: S02E06] console.log( - `The season and episode number of the "BLACK RIVER" episode is ${"TODO: fill in variable here"}` + `The season and episode number of the "BLACK RIVER" episode is ${res2.episode}` ); + // Find all of the episode titles where Bob Ross painted a CLIFF [Should be: NIGHT LIGHT, EVENING SEASCAPE, SURF'S UP, CLIFFSIDE, BY THE SEA, DEEP WILDERNESS HOME, CRIMSON TIDE, GRACEFUL WATERFALL] + const cliffEpisodes=await client.db('DatabaseWeek3').collection('bob_ross_episodes').find({elements:{$in:['CLIFF']}}).toArray(); + const titles=cliffEpisodes.map(episode=>episode.title); console.log( - `The episodes that Bob Ross painted a CLIFF are ${"TODO: fill in variable here"}` + `The episodes that Bob Ross painted a CLIFF are ${titles.join(", ")}` ); + // Find all of the episode titles where Bob Ross painted a CLIFF and a LIGHTHOUSE [Should be: NIGHT LIGHT] + const cliffAndLighthouseEpisodes=await client.db('DatabaseWeek3').collection('bob_ross_episodes').find({elements:{$all:['CLIFF','LIGHTHOUSE']}}).toArray(); + const cliffAndLighthouseTitles=cliffAndLighthouseEpisodes.map(episode=>episode.title); console.log( - `The episodes that Bob Ross painted a CLIFF and a LIGHTHOUSE are ${"TODO: fill in variable here"}` + `The episodes that Bob Ross painted a CLIFF and a LIGHTHOUSE are ${cliffAndLighthouseTitles.join(", ")}` ); } async function updateEpisodeExercises(client) { + const updateResult=await client.db('DatabaseWeek3').collection('bob_ross_episodes').updateOne({ + episode:'S30E13' + },{$set:{title:'BLUE RIDGE FALLS'}}) /** * There are some problems in the initial data that was filled in. * Let's use update functions to update this information. @@ -60,7 +78,15 @@ async function updateEpisodeExercises(client) { // Episode 13 in season 30 should be called BLUE RIDGE FALLS, yet it is called BLUE RIDGE FALLERS now. Fix that console.log( - `Ran a command to update episode 13 in season 30 and it updated ${"TODO: fill in variable here"} episodes` + `Ran a command to update episode 13 in season 30 and it updated ${updateResult.modifiedCount} episodes` + ); +const updateManyResult = await client + .db('DatabaseWeek3') + .collection('bob_ross_episodes') + .updateMany( + { elements: { $in: ['BUSHES'] } }, + { $set: { "elements.$[elem]": "BUSH" } }, + { arrayFilters: [{ "elem": "BUSHES" }] } ); // Unfortunately we made a mistake in the arrays and the element type called 'BUSHES' should actually be 'BUSH' as sometimes only one bush was painted. @@ -68,7 +94,7 @@ async function updateEpisodeExercises(client) { // It should update 120 episodes! console.log( - `Ran a command to update all the BUSHES to BUSH and it updated ${"TODO: fill in variable here"} episodes` + `Ran a command to update all the BUSHES to BUSH and it updated ${updateManyResult.modifiedCount} episodes` ); } @@ -77,9 +103,10 @@ async function deleteEpisodeExercise(client) { * It seems an errand episode has gotten into our data. * This is episode 14 in season 31. Please remove it and verify that it has been removed! */ + const deleteResult=await client.db('DatabaseWeek3').collection('bob_ross_episodes').deleteOne({episode:'S31E14'}) console.log( - `Ran a command to delete episode and it deleted ${"TODO: fill in variable here"} episodes` + `Ran a command to delete episode and it deleted ${deleteResult.deletedCount} episodes` ); } diff --git a/Week3/homework/mongodb/package-lock.json b/Week3/homework/mongodb/package-lock.json new file mode 100644 index 000000000..101a2daa2 --- /dev/null +++ b/Week3/homework/mongodb/package-lock.json @@ -0,0 +1,176 @@ +{ + "name": "mongodb", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mongodb", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^17.2.2", + "mongodb": "^6.19.0" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/mongodb": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz", + "integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/Week3/homework/mongodb/package.json b/Week3/homework/mongodb/package.json new file mode 100644 index 000000000..3087eb12d --- /dev/null +++ b/Week3/homework/mongodb/package.json @@ -0,0 +1,16 @@ +{ + "name": "mongodb", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "dotenv": "^17.2.2", + "mongodb": "^6.19.0" + } +} diff --git a/Week3/homework/mongodb/seedDatabase.js b/Week3/homework/mongodb/seedDatabase.js index 99be6b3d8..b16ba0e35 100644 --- a/Week3/homework/mongodb/seedDatabase.js +++ b/Week3/homework/mongodb/seedDatabase.js @@ -8,13 +8,13 @@ const data = require("./data.json"); */ const seedDatabase = async (client) => { const hasCollection = await client - .db("databaseWeek3") + .db("DatabaseWeek3") .listCollections({ name: "bob_ross_episodes" }) .hasNext(); if (hasCollection) { const bobRossCollection = await client - .db("databaseWeek3") + .db("DatabaseWeek3") .collection("bob_ross_episodes"); // Remove all the documents