From f61fc0016c08e81ad80227e9aa2b954bea14254b Mon Sep 17 00:00:00 2001 From: Astakhov Peter Date: Thu, 8 Oct 2026 22:38:41 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/init_db.sh.sh | 33 +++++++++++++++++ sql/01_create_tables.sql | 74 +++++++++++++++++++++++++++++++++++++++ sql/02_create_indexes.sql | 36 +++++++++++++++++++ sql/README.md | 19 ++++++++++ 4 files changed, 162 insertions(+) create mode 100644 scripts/init_db.sh.sh create mode 100644 sql/01_create_tables.sql create mode 100644 sql/02_create_indexes.sql create mode 100644 sql/README.md diff --git a/scripts/init_db.sh.sh b/scripts/init_db.sh.sh new file mode 100644 index 0000000..85500d1 --- /dev/null +++ b/scripts/init_db.sh.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# ============================================================ +# Скрипт инициализации базы данных +# Использование: ./init_db.sh [DB_CONN] +# ============================================================ + +set -e + +DB_CONN="${1:-dbname=books user=postgres password=Karabas@Barabas host=172.20.0.4}" + +echo "=== Инициализация базы данных ===" +echo "Подключение: $DB_CONN" +echo "" + +# Проверяем соединение +if ! psql "$DB_CONN" -c "SELECT 1" > /dev/null 2>&1; then + echo "ОШИБКА: Не удалось подключиться к базе данных" + exit 1 +fi + +echo "1. Создание таблиц..." +psql "$DB_CONN" -f /app/sql/01_create_tables.sql + +echo "" +echo "2. Создание индексов..." +psql "$DB_CONN" -f /app/sql/02_create_indexes.sql + +echo "" +echo "3. Загрузка жанров..." +python3 /app/scripts/parse_genres.py /app/data/genres.xml "$DB_CONN" + +echo "" +echo "=== Готово! ===" \ No newline at end of file diff --git a/sql/01_create_tables.sql b/sql/01_create_tables.sql new file mode 100644 index 0000000..fdebef3 --- /dev/null +++ b/sql/01_create_tables.sql @@ -0,0 +1,74 @@ +-- ============================================================ +-- Схема базы данных для Домашней библиотеки +-- PostgreSQL 14+ +-- ============================================================ + +-- Авторы +CREATE TABLE IF NOT EXISTS authors ( + id SERIAL PRIMARY KEY, + full_name TEXT NOT NULL UNIQUE, -- "Пелевин,Виктор,Олегович" + last_name TEXT NOT NULL, -- "Пелевин" + first_name TEXT, -- "Виктор" + middle_name TEXT, -- "Олегович" + name_hash TEXT UNIQUE, -- MD5 от full_name (lowercase) + first_letter TEXT NOT NULL, -- первая буква фамилии (lowercase) + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Серии +CREATE TABLE IF NOT EXISTS series ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, -- "Плоский мир" + name_hash TEXT UNIQUE, -- MD5 от name (lowercase) + first_letter TEXT, -- первая буква названия + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Жанры (справочник из genres.xml + кастомные из inpx) +CREATE TABLE IF NOT EXISTS genres ( + id SERIAL PRIMARY KEY, + code TEXT NOT NULL UNIQUE, -- "sf_history", "popadanec" + title_ru TEXT, -- "Альтернативная история" + title_en TEXT, -- "Alternative history" + parent_code TEXT, -- "sf" — родительский жанр + root_code TEXT, -- "sf" — корневой жанр + is_root BOOLEAN DEFAULT FALSE, -- TRUE для корневых + is_custom BOOLEAN DEFAULT FALSE, -- TRUE если добавлен из inpx + genre_alt TEXT[], -- альтернативные коды + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Книги +CREATE TABLE IF NOT EXISTS books ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + title_hash TEXT, -- MD5 от title (lowercase) + internal_id INTEGER, -- ID книги в источнике (LIBID) + internal_id2 INTEGER, -- Дублирующий ID + file_size INTEGER, + format TEXT DEFAULT 'fb2', + added_date DATE, -- Дата добавления в источник + language TEXT DEFAULT 'ru', + source TEXT NOT NULL, -- 'lib.rus.ec', 'flibusta' и т.д. + series_id INTEGER REFERENCES series(id) ON DELETE SET NULL, + series_number NUMERIC(10,2), -- Номер в серии + fts tsvector GENERATED ALWAYS AS ( + to_tsvector('russian', coalesce(title,'')) + ) STORED, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Связь книг и авторов (многие-ко-многим) +CREATE TABLE IF NOT EXISTS book_authors ( + book_id INTEGER REFERENCES books(id) ON DELETE CASCADE, + author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE, + position INTEGER, -- Порядок автора (1 = главный) + PRIMARY KEY (book_id, author_id) +); + +-- Связь книг и жанров (многие-ко-многим) +CREATE TABLE IF NOT EXISTS book_genres ( + book_id INTEGER REFERENCES books(id) ON DELETE CASCADE, + genre_id INTEGER REFERENCES genres(id) ON DELETE CASCADE, + PRIMARY KEY (book_id, genre_id) +); \ No newline at end of file diff --git a/sql/02_create_indexes.sql b/sql/02_create_indexes.sql new file mode 100644 index 0000000..952621b --- /dev/null +++ b/sql/02_create_indexes.sql @@ -0,0 +1,36 @@ +-- ============================================================ +-- Индексы для быстрого поиска +-- ============================================================ + +-- Авторы +CREATE INDEX IF NOT EXISTS idx_authors_last_name ON authors(last_name); +CREATE INDEX IF NOT EXISTS idx_authors_first_letter ON authors(first_letter); +CREATE INDEX IF NOT EXISTS idx_authors_name_hash ON authors(name_hash); +CREATE INDEX IF NOT EXISTS idx_authors_full_name ON authors(full_name); + +-- Серии +CREATE INDEX IF NOT EXISTS idx_series_name_hash ON series(name_hash); +CREATE INDEX IF NOT EXISTS idx_series_first_letter ON series(first_letter); + +-- Жанры +CREATE INDEX IF NOT EXISTS idx_genres_code ON genres(code); +CREATE INDEX IF NOT EXISTS idx_genres_parent ON genres(parent_code); +CREATE INDEX IF NOT EXISTS idx_genres_root ON genres(root_code); +CREATE INDEX IF NOT EXISTS idx_genres_is_root ON genres(is_root); +CREATE INDEX IF NOT EXISTS idx_genres_is_custom ON genres(is_custom); + +-- Книги +CREATE INDEX IF NOT EXISTS idx_books_title_hash ON books(title_hash); +CREATE INDEX IF NOT EXISTS idx_books_internal_id ON books(internal_id); +CREATE INDEX IF NOT EXISTS idx_books_internal_id2 ON books(internal_id2); +CREATE INDEX IF NOT EXISTS idx_books_source ON books(source); +CREATE INDEX IF NOT EXISTS idx_books_series ON books(series_id, series_number); +CREATE INDEX IF NOT EXISTS idx_books_language ON books(language); +CREATE INDEX IF NOT EXISTS idx_books_added_date ON books(added_date DESC NULLS LAST); +CREATE INDEX IF NOT EXISTS idx_books_fts ON books USING GIN (fts); + +-- Связи +CREATE INDEX IF NOT EXISTS idx_book_authors_book ON book_authors(book_id); +CREATE INDEX IF NOT EXISTS idx_book_authors_author ON book_authors(author_id); +CREATE INDEX IF NOT EXISTS idx_book_genres_book ON book_genres(book_id); +CREATE INDEX IF NOT EXISTS idx_book_genres_genre ON book_genres(genre_id); \ No newline at end of file diff --git a/sql/README.md b/sql/README.md new file mode 100644 index 0000000..c00e5c6 --- /dev/null +++ b/sql/README.md @@ -0,0 +1,19 @@ +# SQL-схема Домашней библиотеки + +## Порядок применения + +1. `01_create_tables.sql` — создаёт все таблицы +2. `02_create_indexes.sql` — создаёт индексы +3. `parse_genres.py` — загружает справочник жанров из `genres.xml` + +## Быстрый запуск + +```bash +# Через Docker +docker compose exec opds bash /app/scripts/init_db.sh + +# Или вручную +psql "dbname=books user=postgres password=Karabas@Barabas host=172.20.0.4" \ + -f sql/01_create_tables.sql \ + -f sql/02_create_indexes.sql + \ No newline at end of file