Add DB migrations and run them on startup

Enable sqlx "migrate" feature and add SQL migrations to create the
database schema: pg_trgm extension, sources, genres, authors, sequences,
books, junction tables, annotations, and supporting indexes
This commit is contained in:
2026-01-16 10:28:05 +01:00
parent 9ef8a42fd4
commit c60aa8685b
14 changed files with 151 additions and 3 deletions

View File

@@ -42,4 +42,4 @@ rand = "0.9.0"
chrono = { version = "0.4.40", features = ["serde"] } chrono = { version = "0.4.40", features = ["serde"] }
sqlx = { version = "0.8.3", features = ["runtime-tokio", "postgres", "macros", "chrono", "json"] } sqlx = { version = "0.8.3", features = ["runtime-tokio", "postgres", "macros", "chrono", "json", "migrate"] }

View File

@@ -0,0 +1,2 @@
-- Create pg_trgm extension for trigram-based text search
CREATE EXTENSION IF NOT EXISTS pg_trgm;

View File

@@ -0,0 +1,5 @@
-- Create sources table
CREATE TABLE IF NOT EXISTS sources (
id SMALLSERIAL PRIMARY KEY,
name VARCHAR(32) NOT NULL UNIQUE
);

View File

@@ -0,0 +1,11 @@
-- Create genres table
CREATE TABLE IF NOT EXISTS genres (
id SERIAL PRIMARY KEY,
source SMALLINT NOT NULL,
remote_id INTEGER NOT NULL,
code VARCHAR(45) NOT NULL,
description VARCHAR(99) NOT NULL,
meta VARCHAR(45) NOT NULL,
CONSTRAINT uc_genres_source_remote_id UNIQUE (source, remote_id),
CONSTRAINT fk_genres_sources_id_source FOREIGN KEY (source) REFERENCES sources(id)
);

View File

@@ -0,0 +1,15 @@
-- Create authors table
CREATE TABLE IF NOT EXISTS authors (
id SERIAL PRIMARY KEY,
source SMALLINT NOT NULL,
remote_id INTEGER NOT NULL,
first_name VARCHAR(256) NOT NULL,
last_name VARCHAR(256) NOT NULL,
middle_name VARCHAR(256),
CONSTRAINT uc_authors_source_remote_id UNIQUE (source, remote_id),
CONSTRAINT fk_authors_sources_id_source FOREIGN KEY (source) REFERENCES sources(id)
);
-- Create trigram indexes for author search
CREATE INDEX IF NOT EXISTS tgrm_authors_lf ON authors USING gin ((last_name || ' ' || first_name) gin_trgm_ops);
CREATE INDEX IF NOT EXISTS tgrm_authors_lfm ON authors USING gin ((last_name || ' ' || first_name || ' ' || middle_name) gin_trgm_ops);

View File

@@ -0,0 +1,9 @@
-- Create sequences table
CREATE TABLE IF NOT EXISTS sequences (
id SERIAL PRIMARY KEY,
source SMALLINT NOT NULL,
remote_id INTEGER NOT NULL,
name VARCHAR(256) NOT NULL,
CONSTRAINT uc_sequences_source_remote_id UNIQUE (source, remote_id),
CONSTRAINT fk_sequences_sources_id_source FOREIGN KEY (source) REFERENCES sources(id)
);

View File

@@ -0,0 +1,20 @@
-- Create books table
CREATE TABLE IF NOT EXISTS books (
id SERIAL PRIMARY KEY,
source SMALLINT NOT NULL,
remote_id INTEGER NOT NULL,
title VARCHAR(256) NOT NULL,
lang VARCHAR(3) NOT NULL,
file_type VARCHAR(4) NOT NULL,
uploaded DATE NOT NULL,
is_deleted BOOLEAN NOT NULL DEFAULT false,
pages INTEGER,
year SMALLINT NOT NULL DEFAULT 0,
CONSTRAINT uc_books_source_remote_id UNIQUE (source, remote_id),
CONSTRAINT fk_books_sources_id_source FOREIGN KEY (source) REFERENCES sources(id)
);
-- Create indexes for books
CREATE INDEX IF NOT EXISTS idx_id_asc__not_is_deleted ON books (id) WHERE NOT is_deleted;
CREATE INDEX IF NOT EXISTS idx_id_asc__uploaded__not_is_deleted ON books (id, uploaded) WHERE NOT is_deleted;
CREATE INDEX IF NOT EXISTS idx_uploaded__id_asc__not_is_deleted ON books (uploaded, id) WHERE NOT is_deleted;

View File

@@ -0,0 +1,13 @@
-- Create book_authors junction table
CREATE TABLE IF NOT EXISTS book_authors (
id SERIAL PRIMARY KEY,
author INTEGER NOT NULL,
book INTEGER NOT NULL,
CONSTRAINT uc_book_authors_book_author UNIQUE (book, author),
CONSTRAINT fk_book_authors_authors_author_id FOREIGN KEY (author) REFERENCES authors(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_book_authors_books_book_id FOREIGN KEY (book) REFERENCES books(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- Create indexes for book_authors
CREATE INDEX IF NOT EXISTS book_authors_author ON book_authors (author);
CREATE INDEX IF NOT EXISTS book_authors_book ON book_authors (book);

View File

@@ -0,0 +1,13 @@
-- Create book_genres junction table
CREATE TABLE IF NOT EXISTS book_genres (
id SERIAL PRIMARY KEY,
genre INTEGER NOT NULL,
book INTEGER NOT NULL,
CONSTRAINT uc_book_genres_book_genre UNIQUE (book, genre),
CONSTRAINT fk_book_genres_genres_genre_id FOREIGN KEY (genre) REFERENCES genres(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_book_genres_books_book_id FOREIGN KEY (book) REFERENCES books(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- Create indexes for book_genres
CREATE INDEX IF NOT EXISTS book_genres_genre ON book_genres (genre);
CREATE INDEX IF NOT EXISTS book_genres_book ON book_genres (book);

View File

@@ -0,0 +1,14 @@
-- Create book_sequences junction table
CREATE TABLE IF NOT EXISTS book_sequences (
id SERIAL PRIMARY KEY,
position SMALLINT NOT NULL,
sequence INTEGER NOT NULL,
book INTEGER NOT NULL,
CONSTRAINT uc_book_sequences_book_sequence UNIQUE (book, sequence),
CONSTRAINT fk_book_sequences_sequences_sequence_id FOREIGN KEY (sequence) REFERENCES sequences(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_book_sequences_books_book_id FOREIGN KEY (book) REFERENCES books(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- Create indexes for book_sequences
CREATE INDEX IF NOT EXISTS book_sequences_sequence ON book_sequences (sequence);
CREATE INDEX IF NOT EXISTS book_sequences_book ON book_sequences (book);

View File

@@ -0,0 +1,14 @@
-- Create translations junction table
CREATE TABLE IF NOT EXISTS translations (
id SERIAL PRIMARY KEY,
position SMALLINT NOT NULL,
author INTEGER NOT NULL,
book INTEGER NOT NULL,
CONSTRAINT uc_translations_book_author UNIQUE (book, author),
CONSTRAINT fk_translations_authors_author_id FOREIGN KEY (author) REFERENCES authors(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_translations_books_book_id FOREIGN KEY (book) REFERENCES books(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- Create indexes for translations
CREATE INDEX IF NOT EXISTS translations_author ON translations (author);
CREATE INDEX IF NOT EXISTS translations_book ON translations (book);

View File

@@ -0,0 +1,12 @@
-- Create author_annotations table
CREATE TABLE IF NOT EXISTS author_annotations (
id SERIAL PRIMARY KEY,
author INTEGER NOT NULL UNIQUE,
title VARCHAR(256) NOT NULL,
text TEXT NOT NULL,
file VARCHAR(256),
CONSTRAINT fk_author_annotations_authors_id_author FOREIGN KEY (author) REFERENCES authors(id)
);
-- Create index for author_annotations
CREATE INDEX IF NOT EXISTS author_annotation_author_id ON author_annotations (author);

View File

@@ -0,0 +1,12 @@
-- Create book_annotations table
CREATE TABLE IF NOT EXISTS book_annotations (
id SERIAL PRIMARY KEY,
book INTEGER NOT NULL UNIQUE,
title VARCHAR(256) NOT NULL,
text TEXT NOT NULL,
file VARCHAR(256),
CONSTRAINT fk_book_annotations_books_id_book FOREIGN KEY (book) REFERENCES books(id)
);
-- Create index for book_annotations
CREATE INDEX IF NOT EXISTS book_annotation_book_id ON book_annotations (book);

View File

@@ -12,10 +12,18 @@ pub async fn get_postgres_pool() -> PgPool {
CONFIG.postgres_db CONFIG.postgres_db
); );
PgPoolOptions::new() let pool = PgPoolOptions::new()
.max_connections(10) .max_connections(10)
.acquire_timeout(std::time::Duration::from_secs(300)) .acquire_timeout(std::time::Duration::from_secs(300))
.connect(&database_url) .connect(&database_url)
.await .await
.unwrap() .unwrap();
// Run migrations
sqlx::migrate!("./migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
pool
} }