From c60aa8685be72797a6e51c900afde3f8755e9831 Mon Sep 17 00:00:00 2001 From: Bulat Kurbanov Date: Fri, 16 Jan 2026 10:28:05 +0100 Subject: [PATCH] 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 --- Cargo.toml | 2 +- .../20260116092202_create_extensions.sql | 2 ++ .../20260116092203_create_sources_table.sql | 5 +++++ .../20260116092204_create_genres_table.sql | 11 ++++++++++ .../20260116092205_create_authors_table.sql | 15 ++++++++++++++ .../20260116092206_create_sequences_table.sql | 9 +++++++++ .../20260116092207_create_books_table.sql | 20 +++++++++++++++++++ ...260116092208_create_book_authors_table.sql | 13 ++++++++++++ ...0260116092209_create_book_genres_table.sql | 13 ++++++++++++ ...0116092210_create_book_sequences_table.sql | 14 +++++++++++++ ...260116092211_create_translations_table.sql | 14 +++++++++++++ ...092212_create_author_annotations_table.sql | 12 +++++++++++ ...16092213_create_book_annotations_table.sql | 12 +++++++++++ src/db.rs | 12 +++++++++-- 14 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 migrations/20260116092202_create_extensions.sql create mode 100644 migrations/20260116092203_create_sources_table.sql create mode 100644 migrations/20260116092204_create_genres_table.sql create mode 100644 migrations/20260116092205_create_authors_table.sql create mode 100644 migrations/20260116092206_create_sequences_table.sql create mode 100644 migrations/20260116092207_create_books_table.sql create mode 100644 migrations/20260116092208_create_book_authors_table.sql create mode 100644 migrations/20260116092209_create_book_genres_table.sql create mode 100644 migrations/20260116092210_create_book_sequences_table.sql create mode 100644 migrations/20260116092211_create_translations_table.sql create mode 100644 migrations/20260116092212_create_author_annotations_table.sql create mode 100644 migrations/20260116092213_create_book_annotations_table.sql diff --git a/Cargo.toml b/Cargo.toml index bcacf3e..1b56911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,4 +42,4 @@ rand = "0.9.0" 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"] } diff --git a/migrations/20260116092202_create_extensions.sql b/migrations/20260116092202_create_extensions.sql new file mode 100644 index 0000000..d78f9a4 --- /dev/null +++ b/migrations/20260116092202_create_extensions.sql @@ -0,0 +1,2 @@ +-- Create pg_trgm extension for trigram-based text search +CREATE EXTENSION IF NOT EXISTS pg_trgm; diff --git a/migrations/20260116092203_create_sources_table.sql b/migrations/20260116092203_create_sources_table.sql new file mode 100644 index 0000000..bd0d36e --- /dev/null +++ b/migrations/20260116092203_create_sources_table.sql @@ -0,0 +1,5 @@ +-- Create sources table +CREATE TABLE IF NOT EXISTS sources ( + id SMALLSERIAL PRIMARY KEY, + name VARCHAR(32) NOT NULL UNIQUE +); diff --git a/migrations/20260116092204_create_genres_table.sql b/migrations/20260116092204_create_genres_table.sql new file mode 100644 index 0000000..2c91eee --- /dev/null +++ b/migrations/20260116092204_create_genres_table.sql @@ -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) +); diff --git a/migrations/20260116092205_create_authors_table.sql b/migrations/20260116092205_create_authors_table.sql new file mode 100644 index 0000000..38d3171 --- /dev/null +++ b/migrations/20260116092205_create_authors_table.sql @@ -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); diff --git a/migrations/20260116092206_create_sequences_table.sql b/migrations/20260116092206_create_sequences_table.sql new file mode 100644 index 0000000..02e377b --- /dev/null +++ b/migrations/20260116092206_create_sequences_table.sql @@ -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) +); diff --git a/migrations/20260116092207_create_books_table.sql b/migrations/20260116092207_create_books_table.sql new file mode 100644 index 0000000..a19220d --- /dev/null +++ b/migrations/20260116092207_create_books_table.sql @@ -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; diff --git a/migrations/20260116092208_create_book_authors_table.sql b/migrations/20260116092208_create_book_authors_table.sql new file mode 100644 index 0000000..7af3d2c --- /dev/null +++ b/migrations/20260116092208_create_book_authors_table.sql @@ -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); diff --git a/migrations/20260116092209_create_book_genres_table.sql b/migrations/20260116092209_create_book_genres_table.sql new file mode 100644 index 0000000..8e08c6a --- /dev/null +++ b/migrations/20260116092209_create_book_genres_table.sql @@ -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); diff --git a/migrations/20260116092210_create_book_sequences_table.sql b/migrations/20260116092210_create_book_sequences_table.sql new file mode 100644 index 0000000..f274498 --- /dev/null +++ b/migrations/20260116092210_create_book_sequences_table.sql @@ -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); diff --git a/migrations/20260116092211_create_translations_table.sql b/migrations/20260116092211_create_translations_table.sql new file mode 100644 index 0000000..46309e5 --- /dev/null +++ b/migrations/20260116092211_create_translations_table.sql @@ -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); diff --git a/migrations/20260116092212_create_author_annotations_table.sql b/migrations/20260116092212_create_author_annotations_table.sql new file mode 100644 index 0000000..0044bcc --- /dev/null +++ b/migrations/20260116092212_create_author_annotations_table.sql @@ -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); diff --git a/migrations/20260116092213_create_book_annotations_table.sql b/migrations/20260116092213_create_book_annotations_table.sql new file mode 100644 index 0000000..bbfe158 --- /dev/null +++ b/migrations/20260116092213_create_book_annotations_table.sql @@ -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); diff --git a/src/db.rs b/src/db.rs index d66431c..ae6d488 100644 --- a/src/db.rs +++ b/src/db.rs @@ -12,10 +12,18 @@ pub async fn get_postgres_pool() -> PgPool { CONFIG.postgres_db ); - PgPoolOptions::new() + let pool = PgPoolOptions::new() .max_connections(10) .acquire_timeout(std::time::Duration::from_secs(300)) .connect(&database_url) .await - .unwrap() + .unwrap(); + + // Run migrations + sqlx::migrate!("./migrations") + .run(&pool) + .await + .expect("Failed to run migrations"); + + pool }