Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion task-1/schema.sql
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
-- Write here your DDL statements to create the tables for the database.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this is a well-structured schema with clear primary keys, foreign keys, and many-to-many relationship tables.

CREATE TABLE cards
(
card_id SERIAL PRIMARY KEY,
card_number TEXT UNIQUE NOT NULL,
card_issued_on DATE NOT NULL,
card_expires_on DATE NOT NULL

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a good opportunity to add a contraint on the issued en expiry dates: CHECK (card_expires_on > card_issued_on). Also consider placing date/timestamp contraints on the borrows table below.

);

CREATE TABLE members
(
member_id SERIAL PRIMARY KEY,
card_id INT UNIQUE,
member_email TEXT UNIQUE NOT NULL,
member_name TEXT NOT NULL,
member_phone TEXT NOT NULL,
member_address TEXT NOT NULL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be nice to further normalize addresses in a separate table. This allows working with e.g. postal codes to uniquely identify addresses. This would make address verification easier.

FOREIGN KEY (card_id) REFERENCES cards (card_id) ON DELETE CASCADE

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this cascading delete really what you intended? Now if you delete a card then the member will also be deleted. Also as card_id can be NULL it looks like ON DELETE SET NULL would be the intended choice.

);

CREATE TABLE publishers
(
publisher_id SERIAL PRIMARY KEY,
publisher_name TEXT UNIQUE NOT NULL
);

CREATE TABLE authors
(
author_id SERIAL PRIMARY KEY,
author_name TEXT UNIQUE NOT NULL

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it is indeed rare, it could be possible that two different authors happen to have the same name. Relaxing the UNIQUE contraint allows for indicating that there are multiple different authors with the same name.

);

CREATE TABLE tags
(
tag_id SERIAL PRIMARY KEY,
tag_name TEXT UNIQUE NOT NULL

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tags are usually short so VARCHAR would be a better choice than arbitrary TEXT.

);

CREATE TABLE books
(
book_id SERIAL PRIMARY KEY,
book_isbn VARCHAR(13) UNIQUE NOT NULL,
book_title TEXT NOT NULL,
publisher_id INT NOT NULL,
FOREIGN KEY (publisher_id) REFERENCES publishers (publisher_id)
);

CREATE TABLE book_authors
(
book_id INT NOT NULL,
author_id INT NOT NULL,
PRIMARY KEY (book_id, author_id),
FOREIGN KEY (book_id) REFERENCES books (book_id) ON DELETE CASCADE,
FOREIGN KEY (author_id) REFERENCES authors (author_id) ON DELETE CASCADE
);

CREATE TABLE book_tags
(
book_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (book_id, tag_id),
FOREIGN KEY (book_id) REFERENCES books (book_id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags (tag_id) ON DELETE CASCADE
);

CREATE TABLE shelves
(
shelf_id SERIAL PRIMARY KEY,
shelf_code TEXT UNIQUE NOT NULL
);

CREATE TABLE copies
(
copy_id SERIAL PRIMARY KEY,
copy_barcode TEXT UNIQUE NOT NULL,
book_id INT NOT NULL,
shelf_id INT,
FOREIGN KEY (book_id) REFERENCES books (book_id) ON DELETE CASCADE,
FOREIGN KEY (shelf_id) REFERENCES shelves (shelf_id)
);

CREATE TABLE borrows
(
borrow_id SERIAL PRIMARY KEY,
member_id INT NOT NULL,
copy_id INT NOT NULL,
borrowed_at TIMESTAMP NOT NULL,
due_date TIMESTAMP NOT NULL,
returned_at TIMESTAMP,
fine_eur NUMERIC(8,2) NOT NULL DEFAULT 0.00,
FOREIGN KEY (member_id) REFERENCES members (member_id),
FOREIGN KEY (copy_id) REFERENCES copies (copy_id)
);
171 changes: 169 additions & 2 deletions task-2/src/main/java/org/hyf/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,174 @@
package org.hyf;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.PreparedStatement;

public class Main {

private static Connection getConnection() throws SQLException {
String url = "jdbc:postgresql://localhost:5432/test";
String user = "hyfuser";
String pass = "hyfpassword";
return DriverManager.getConnection(url, user, pass);
}

public static void insertData() {
try (Connection conn = getConnection()) {

// 1. Publishers
PreparedStatement pub = conn.prepareStatement(
"INSERT INTO publishers (publisher_name) VALUES (?)"
);
pub.setString(1, "O'Reilly");
pub.executeUpdate();
pub.setString(1, "Addison-Wesley");
pub.executeUpdate();

// 2. Authors
PreparedStatement auth = conn.prepareStatement(
"INSERT INTO authors (author_name) VALUES (?)"
);
auth.setString(1, "Douglas Crockford");
auth.executeUpdate();
auth.setString(1, "Eric Freeman");
auth.executeUpdate();

// 3. Books
PreparedStatement book = conn.prepareStatement(
"INSERT INTO books (book_isbn, book_title, publisher_id) VALUES (?, ?, ?)"
);
book.setString(1, "9780596517748");
book.setString(2, "JavaScript: The Good Parts");
book.setInt(3, 1);
book.executeUpdate();

book.setString(1, "9780596007126");
book.setString(2, "Head First Design Patterns");
book.setInt(3, 1);
book.executeUpdate();

// 4. Cards
PreparedStatement card = conn.prepareStatement(
"INSERT INTO cards (card_number, card_issued_on, card_expires_on) VALUES (?, ?, ?)"
);
card.setString(1, "C-1008");
card.setDate(2, java.sql.Date.valueOf("2024-01-30"));
card.setDate(3, java.sql.Date.valueOf("2027-01-30"));
card.executeUpdate();

card.setString(1, "C-1004");
card.setDate(2, java.sql.Date.valueOf("2023-11-20"));
card.setDate(3, java.sql.Date.valueOf("2026-11-20"));
card.executeUpdate();

// 5. Members
PreparedStatement mem = conn.prepareStatement(
"INSERT INTO members (card_id, member_email, member_name, member_phone, member_address) VALUES (?, ?, ?, ?, ?)"
);
mem.setInt(1, 1);
mem.setString(2, "omar@example.com");
mem.setString(3, "Omar");
mem.setString(4, "+31689012345");
mem.setString(5, "Rijnkade 19, Utrecht");
mem.executeUpdate();

mem.setInt(1, 2);
mem.setString(2, "tom@example.com");
mem.setString(3, "Tom");
mem.setString(4, "+31645678901");
mem.setString(5, "Biltstraat 88, Utrecht");
mem.executeUpdate();

// 6. Shelves
PreparedStatement shelf = conn.prepareStatement(
"INSERT INTO shelves (shelf_code) VALUES (?)"
);
shelf.setString(1, "B-03");
shelf.executeUpdate();

// 7. Copies
PreparedStatement copy = conn.prepareStatement(
"INSERT INTO copies (copy_barcode, book_id, shelf_id) VALUES (?, ?, ?)"
);
copy.setString(1, "BC-0011");
copy.setInt(2, 1);
copy.setInt(3, 1);
copy.executeUpdate();

copy.setString(1, "BC-0013");
copy.setInt(2, 1);
copy.setInt(3, 1);
copy.executeUpdate();

// 8. Borrows
PreparedStatement bor = conn.prepareStatement(
"INSERT INTO borrows (member_id, copy_id, borrowed_at, due_date, returned_at, fine_eur) VALUES (?, ?, ?, ?, ?, ?)"
);
bor.setInt(1, 1);
bor.setInt(2, 1);
bor.setTimestamp(3, java.sql.Timestamp.valueOf("2025-12-02 08:08:00"));
bor.setTimestamp(4, java.sql.Timestamp.valueOf("2025-12-23 08:08:00"));
bor.setTimestamp(5, java.sql.Timestamp.valueOf("2025-12-13 08:08:00"));
bor.setBigDecimal(6, new java.math.BigDecimal("0.00"));
bor.executeUpdate();

bor.setInt(1, 2);
bor.setInt(2, 2);
bor.setTimestamp(3, java.sql.Timestamp.valueOf("2026-01-10 08:51:00"));
bor.setTimestamp(4, java.sql.Timestamp.valueOf("2026-01-31 08:51:00"));
bor.setTimestamp(5, java.sql.Timestamp.valueOf("2026-01-25 08:51:00"));
bor.setBigDecimal(6, new java.math.BigDecimal("0.00"));
bor.executeUpdate();

System.out.println("Inserted data successfully.");

} catch (SQLException e) {
e.printStackTrace();
}
}

public static void fetchData() {
String sql = """
SELECT
b.book_title,
p.publisher_name,
c.copy_barcode,
m.member_name,
br.borrowed_at,
br.returned_at
FROM borrows br
JOIN copies c ON br.copy_id = c.copy_id
JOIN books b ON c.book_id = b.book_id
JOIN publishers p ON b.publisher_id = p.publisher_id
JOIN members m ON br.member_id = m.member_id
ORDER BY br.borrowed_at
LIMIT 10
""";

try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {

System.out.println("- Borrows -");

while (rs.next()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetchData query is very nice and returns a relevant non-technical view without primary keys. The program could present richer output based on the query. For example, like so:

String row = rs.getString("member_name") + " -> " +
rs.getString("book_title") + " (" +
rs.getString("copy_barcode") + "), borrowed at " +
rs.getTimestamp("borrowed_at") + ", returned at " +
rs.getTimestamp("returned_at");

String row = rs.getString("member_name") + " -> " +
rs.getString("book_title") + " (" +
rs.getString("copy_barcode") + ")";

System.out.println(row);
}

} catch (SQLException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
System.out.println("You code goes here :) You can overwrite this entire method.");
insertData();
fetchData();
}
}
}
Binary file added task-2/target/classes/org/hyf/Main.class

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiled .class files do not belong in Git, but sometimes get accidentally commited. To make this easier a .gitignore file with the line: target/ would prevent build output to be accidentally committed.

Binary file not shown.