Commit 0a3688dd authored by Sorrel Harriet's avatar Sorrel Harriet
Browse files

adding example code for lab 8 transactions and locking

parent cacb5d03
<?php
// define a connection 'handle'
$link = mysqli_connect(
'localhost',
'sorrel',
'password123',
'musicstore'
);
// check connection succeeded
if (mysqli_connect_errno()) {
exit(mysqli_connect_error());
}
?>
<?php
/* Helper functions.
Include once from index.php */
/* define a function to sanitise user input
(this would ideally be in includes folder)
helps protect against XSS */
function clean_input($data) {
$data = trim($data); // strips unnecessary characters from beginning/end
$data = stripslashes($data); // remove backslashes
$data = htmlspecialchars($data); // replace special characters with HTML entities
return $data;
}
?>
......@@ -36,6 +36,9 @@ case 'album' :
case 'add-track' :
include 'views/add-track.php';
break;
case 'place-order' :
include 'views/place-order.php';
break;
default :
include 'views/404.php';
}
......
INSERT INTO Genre (name)
VALUES
('Reggae'),
('Funk and Soul'),
('Jazz'),
('Classical'),
('Electronic'),
('Pop');
INSERT INTO Band (name, genre_id)
VALUES
('The Wailers', 1),
('The Aces', 2),
('The Beatles', 6);
INSERT INTO Artist (first_name, last_name, band_id)
VALUES
('Bob', 'Marley', 1),
('Peter', 'Tosh', 1),
('Burning', 'Spear', NULL),
('Alton', 'Ellis', NULL),
('Gregory', 'Issacs', NULL),
('Desmond', 'Dekker', 2),
('John', 'Lennon', 3),
('Madonna', NULL, NULL);
INSERT INTO Album (upc, title, release_year, artist_id, compilation, price, genre_id)
VALUES
('123425364732', 'Soul Rebel', 1970, 1, FALSE, 25.99, 1 ),
('017263547261', 'Catch A Fire', 1973, 1, 0, 25.99, 1 ),
('018263526272', 'Natty Dread', 1974, 1, 1, 20.99, 1 ),
('018273527273', 'Babylon By Bus', 1978, 1, 'FALSE', 24.99, 1 ),
('491827364626', 'Night Nurse', 1982, 5, TRUE, 17.99, 1 ),
('018276272828', 'Mr Issacs', 1982, 5, 0, 9.99, 1 ),
('018273662728', 'Black and Dekker', 1980, 6, 0, 19.99, 1 ),
('726517237627', 'Sunday Coming', 1970, 4, TRUE, 15.99, 1 ),
('018274372722', 'Imagine', 1971, 7, NULL, 11.99, 6 ),
('018273727287', 'Like a Virgin', 1984, 8, 1, 9.99, 6 );
INSERT INTO Inventory (album_upc, stock)
VALUES
('123425364732', 11),
('017263547261', 10),
('018263526272', 8),
('018273527273', 12),
('491827364626', 1),
('018276272828', 3),
('018273662728', 2),
('726517237627', 2),
('018274372722', 14),
('018273727287', 10);
INSERT INTO Track (name, album_upc, track_number)
VALUES
('Sunday Coming', '726517237627', 1),
('Imagine', '018274372722', 1),
('Material Girl', '018273727287', 1),
('Angel', '018273727287', 2),
('Crippled Inside', '018274372722', 2),
('These Eyes', '726517237627', 2),
('Hurting Me', '726517237627', 3);
INSERT INTO Customer (username, email_address, password, first_name, last_name, address_1, address_2, postcode)
VALUES
('sharr003', 's.harriet@gold.ac.uk', 'SOME_HASHED_VALUE', 'Sorrel', 'Harriet', '12 Fake Street', 'London', 'SE140PL'),
('ktack001', 'k.tackie@hotmail.com', 'SOME_HASHED_VALUE', 'Kobi', 'Tackie', '12 Fake Street', 'London', 'SE140PL');
INSERT INTO Transaction (customer_id, delivery)
VALUES
(1, 'next working day'),
(1, 'first class'),
(2, 'second class');
INSERT INTO LineItem (trans_id, album_upc, quantity)
VALUES
(1, '018273727287', 1),
(2, '018273727287', 1),
(3, '491827364626', 1),
(1, '018274372722', 2);
/* Delete existing tables in reverse order of creation
so as not to violate any foreign key constraints */
DROP TABLE IF EXISTS Inventory, Rating, Wish, LineItem, Transaction, Customer, Track, Album, Artist, Band, Genre;
/* Define table for genres */
CREATE TABLE Genre (
id INT AUTO_INCREMENT,
name VARCHAR(25) UNIQUE NOT NULL,
PRIMARY KEY (id)
);
/* Define table for storing act (i.e. a group or solo artist)
ON DELETE CASCADE not used in this case as we don't want bands
being deleted if their genre is deleted. We'd rather an error
was raised. */
CREATE TABLE Band (
id INT AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
genre_id INT,
PRIMARY KEY (id),
FOREIGN KEY (genre_id)
REFERENCES Genre (id)
);
/* Define table for storing artists */
CREATE TABLE Artist (
id INT AUTO_INCREMENT,
first_name VARCHAR(25) NOT NULL,
last_name VARCHAR(25),
band_id INT,
PRIMARY KEY (id),
FOREIGN KEY (band_id)
REFERENCES Band (id)
);
/* Define table for storing albums */
CREATE TABLE Album (
upc CHAR(12),
title VARCHAR(50) NOT NULL,
release_year YEAR(4),
artist_id INT,
compilation BOOLEAN NOT NULL DEFAULT 0,
price DECIMAL(5, 2) unsigned NOT NULL,
genre_id INT,
PRIMARY KEY (upc),
FOREIGN KEY (artist_id)
REFERENCES Artist (id),
FOREIGN KEY (genre_id)
REFERENCES Genre (id)
);
/* Define table for storing single tracks
ON DELETE CASCADE will ensure no track
is left that isn't linked to an album.
ON UPDATE CASCADE will inherit any changes
to the Album UPC (Universal Product Code).
A composite PRIMARY KEY is used here to
ensure the track_number is unique
on any given album */
CREATE TABLE Track (
name VARCHAR(50) NOT NULL UNIQUE,
album_upc CHAR(12) NOT NULL,
track_number INT,
PRIMARY KEY (album_upc, track_number),
FOREIGN KEY (album_upc)
REFERENCES Album (upc)
ON UPDATE CASCADE
ON DELETE CASCADE
);
/* Define table for storing customers */
CREATE TABLE Customer (
id INT AUTO_INCREMENT,
username VARCHAR(25) UNIQUE NOT NULL,
email_address VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(64), /* will store a hashed password */
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
address_1 VARCHAR(50) NOT NULL,
address_2 VARCHAR(50),
postcode VARCHAR(10) NOT NULL,
PRIMARY KEY (id)
);
/* Define table for storing orders */
CREATE TABLE Transaction (
id INT AUTO_INCREMENT,
customer_id INT NOT NULL,
delivery ENUM('first class','second class','next working day'),
PRIMARY KEY (id),
FOREIGN KEY (customer_id)
REFERENCES Customer(id)
ON DELETE CASCADE
);
/* Define table for storing line items */
CREATE TABLE LineItem (
id INT AUTO_INCREMENT,
trans_id INT NOT NULL,
album_upc CHAR(12) NOT NULL,
quantity INT NOT NULL DEFAULT 1,
PRIMARY KEY (id),
FOREIGN KEY (trans_id)
REFERENCES Transaction (id)
ON DELETE CASCADE,
FOREIGN KEY (album_upc)
REFERENCES Album (upc)
ON DELETE CASCADE
);
/* Define table to store customer 'wishes'.
Compile wishlists from this data. */
CREATE TABLE Wish (
customer_id INT,
album_upc CHAR(12) NOT NULL,
PRIMARY KEY (customer_id, album_upc),
FOREIGN KEY (customer_id)
REFERENCES Customer(id)
ON DELETE CASCADE,
FOREIGN KEY (album_upc)
REFERENCES Album (upc)
ON DELETE CASCADE
);
/* Define table for storing customer ratingsfor albums */
CREATE TABLE Rating (
customer_id INT NOT NULL,
album_upc CHAR(12) NOT NULL,
score TINYINT unsigned NOT NULL,
PRIMARY KEY (customer_id, album_upc),
FOREIGN KEY (customer_id)
REFERENCES Customer(id)
ON DELETE CASCADE,
FOREIGN KEY (album_upc)
REFERENCES Album (upc)
ON DELETE CASCADE
);
/* Define table for storing stock info */
CREATE TABLE Inventory (
album_upc CHAR(12) NOT NULL,
stock INT unsigned DEFAULT 0,
PRIMARY KEY (album_upc),
FOREIGN KEY (album_upc)
REFERENCES Album (upc)
ON UPDATE CASCADE
ON DELETE CASCADE
);
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'/>
<title>Music Store: {{ page }}</title>
</head>
<body>
<nav>
<ul>
<li><a href='index.php?page=home'>Home</a></li>
<li><a href='index.php?page=albums'>Albums</a></li>
<li><a href='index.php?page=add-track'>Add Track</a></li>
<li><a href='index.php?page=place-order'>Order Album</a></li>
</ul>
</nav>
<?php
// check if upc parameter set in query string
if (!isset($_GET['upc'])) {
// it is not set, so don't run the script
$content .= "<p>I don't know what album you're looking for...</p>";
}
else {
// continue running the script...
// set a variable to store Album.upc value
$upc = $_GET['upc'];
// define the SQL query to run (from lab 3 queries.sql!)
// use column aliases if necessary to make referencing fields in result-set easier
$sql = "SELECT t.name AS track, t.track_number, a.title AS album
FROM Track t
INNER JOIN Album a
ON t.album_upc=a.upc
WHERE a.upc='$upc'
ORDER BY track_number ASC";
// query the database
$result = mysqli_query($link, $sql);
// get number of rows in result-set
$row_cnt = mysqli_num_rows($result);
// check if there are rows to display...
if ($row_cnt == 0) {
// if not, output a suitable message
$content .= "<p>The album you requested was either not found or has no tracks!</p>";
} else {
// otherwise, update the HTML in $content
// while there are rows, fetch each row as an associative array
// define a row counter
$i = 0;
while ($row = mysqli_fetch_assoc($result)) {
// if first row, output header and start of table
if ($i == 0) {
$content .= "<h1>".$row['album']."</h1>";
$content .= "<table cellpadding='4' border='1'>
<thead align='left'>
<tr>
<th>Track name</th>
<th>Track number</th>
</tr>
</thead>
<tbody>";
$content .= "<p>The album has $row_cnt tracks.</p>";
}
// append the content with more HTML containing row data
$content .= "<tr>
<td>".$row['track']."</td>
<td>".$row['track_number']."</td>
</tr>"; // reference a field value in the array by its key!
// increment row counter
$i++;
}
// free result set
mysqli_free_result($result);
$content .= "</tbody></table>";
}
} // end if/else
?>
<?php
// define the SQL query to run (from lab 3 queries.sql!)
// use column aliases if necessary to make referencing fields in result-set easier
$sql = "SELECT Album.title, Album.price, Album.upc, Artist.first_name, Artist.last_name, Genre.name AS genre, (SELECT COUNT(*) FROM Track WHERE Album.upc=Track.album_upc) AS num_tracks
FROM Album
INNER JOIN Artist
ON Album.artist_id=Artist.id
INNER JOIN Genre
ON Album.genre_id=Genre.id
ORDER BY title ASC;";
// query the database
$result = mysqli_query($link, $sql);
// update HTML content string with page title
$content .= "<h1>Albums</h1>";
// get number of rows in result-set
$row_cnt = mysqli_num_rows($result);
// check if there are rows to display...
if ($row_cnt == 0) {
// if not, output a suitable message
$content .= "<p>There are no albums in the databased!</p>";
} else {
// otherwise, update the HTML in $content
$content .= "<table cellpadding='4' border='1'>
<thead align='left'>
<tr>
<th>Album name</th>
<th>Artist name</th>
<th>Price</th>
<th>Genre</th>
<th>Number of tracks</th>
</tr>
</thead>
<tbody>";
// while there are rows, fetch each row as an associative array
while ($row = mysqli_fetch_assoc($result)) {
// append the content with more HTML containing row data
$content .= "<tr>
<td><a href='?page=album&upc=".$row['upc']."'>".$row['title']."</a></td>
<td>".$row['first_name']." ".$row['last_name']."</td>
<td>&pound;".$row['price']."</td>
<td>".$row['genre']."</td>
<td>".$row['num_tracks']."</td>
</tr>"; // reference a field value in the array by its key!
}
// free result set
mysqli_free_result($result);
$content .= "</tbody></table>";
}
?>
<?php
$content .= "<h1>Welcome to the Music Store</h1>";
$content .= "<p>Some content here...</p>";
?>
<?php
$content = "<h1>Place an Order</h1>";
// define a variable with path to the script which will process form
// -> $_SERVER["PHP_SELF"] is a path to the current script (index.php)
$action = $_SERVER["PHP_SELF"]."?page=place-order";
// turn autocommit off
mysqli_autocommit($link, FALSE);
// start a transaction
mysqli_query($link, 'START TRANSACTION');
// fetch the albums so that we have access to their names and upc's
// lock in shared mode so affected data cannot be altered by other
// sessions BEFORE the transaction is committed or rolled back
$sql = "SELECT title, upc
FROM Album
ORDER BY title LOCK IN SHARE MODE";
$result = mysqli_query($link, $sql);
// check query returned a result
if ($result === false) {
echo mysqli_error($link);
} else {
$options = "";
// create an option for each album
while ($row = mysqli_fetch_assoc($result)) {
$options .= "<option value='".$row['upc']."'>";
$options .= $row['title'];
$options .= "</option>";
}
}
// define the form HTML (would ideally be in a template)
// note that the customer_id is a hidden field
// we'll assume they're a logged in customer and their
// id is saved in the $_SESSION variable (more on that in term 2)
$form_html = "<form action='".$action."' method='POST'>
<input type='hidden' name='cust_id' value='1'>
<fieldset>
<label for='upc'>Album:</label>
<select name='upc' required>
<option value='' disabled selected>Select an album</option>
".$options."
</select>
</fieldset>
<fieldset>
<label for='qty'>Quantity required:</label>
<input type='number' name='qty' min='1' max='25'>
</fieldset>
<fieldset>
<label for='delivery'>Select delivery method:</label>
<select name='delivery' required>
<option value='second class' selected>second class</option>
<option value='first class'>first class</option>
<option value='next working day'>next working day</option>
</select>
</fieldset>
<button type='submit'>Place order</button>
</form>";
// append form HTML to content string
$content .= $form_html;
// ------- START form processing code... -------
// define variables and set to empty values
$upc = $stock = $qty = $cust_id = $delivery = "";
// check if there was a POST request
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// validate the form data
$qty = mysqli_real_escape_string($link, clean_input($_POST["qty"]));
$upc = mysqli_real_escape_string($link, clean_input($_POST["upc"]));
$cust_id = mysqli_real_escape_string($link, clean_input($_POST["cust_id"]));
$delivery = mysqli_real_escape_string($link, clean_input($_POST["delivery"]));
// define query to get stock info for album that has been ordered
// lock the data FOR UPDATE so that it cannot be read by other
// sessions BEFORE the transaction is committed or rolled back
$sql1 = "SELECT stock FROM Inventory WHERE album_upc=".$upc." FOR UPDATE";
// run first query
$result = mysqli_query($link, $sql1);
// check first query returned a result
if ($result === false) {
exit(mysqli_error($link)); // exit script with message
}
// get the stock value from the first result-set
$row = mysqli_fetch_row($result);
$stock = $row[0];
// check if there are enough albums in stock, and calculate new stock
if ($qty <= $stock) {
$new_stock_val = $stock - $qty;
} else {
exit("Sorry, we have insufficient stock."); // exit script
}
// define the insertion query needed to add row to Transaction (order) table
$sql2 = sprintf("INSERT INTO Transaction (customer_id, delivery)
VALUES (%d, '%s')", $cust_id, $delivery);
// run second query
$result2 = mysqli_query($link, $sql2);
// get id of last row inserted (the transaction id)
$trans_id = mysqli_insert_id($link);
// define insertion query needed to add row to LineItem table
$sql3 = sprintf("INSERT INTO LineItem (trans_id, album_upc, quantity)
VALUES (%d, '%s', %d)", $trans_id, $upc, $qty);
// run the third query
$result3 = mysqli_query($link, $sql3);
// define the update query needed to update the Inventory table
$sql4 = sprintf("UPDATE Inventory SET stock=%d
WHERE album_upc='%s'", $new_stock_val, $upc);
// run the final query
$result4 = mysqli_query($link, $sql4);
// check if queries 2-4 all went ok...
if ($result2 === false || $result3 === false || $result4 === false) {
// one of them returned false, so rollback transaction...
mysqli_rollback($link);
echo mysqli_error($link);
} else {
// all completed without error, so commit transaction
mysqli_commit($link);
$content .= "Order successfully placed.";
}
}
// ------- END form processing code... -------
?>
<?php
$content = "<h1>Update Album Stock</h1>";
// define a variable with path to the script which will process form
// -> $_SERVER["PHP_SELF"] is a path to the current script (index.php)
$action = $_SERVER["PHP_SELF"]."?page=update-stock";
// turn autocommit off
mysqli_autocommit($link, FALSE);
// start a transaction
mysqli_query($link, 'START TRANSACTION');
// fetch the albums and associated stock info
// apply FOR UPDATE to prevent reads until
// the transaction is completed with COMMIT or ROLLBACK
$sql = "SELECT a.upc, a.title, i.stock
FROM Album a
INNER JOIN Inventory i
ON a.upc=i.album_upc
ORDER BY title FOR UPDATE";
$result = mysqli_query($link, $sql);
// check query returned a result
if ($result === false) {
echo mysqli_error($link);
} else {