F2 — PostgreSQL-бэкенд (read-only, изнутри) #36

Closed
opened 2026-09-16 17:21:05 +01:00 by eSlider · 1 comment
Owner

Epic: #34

F2 — PostgreSQL-бэкенд (read-only, «изнутри»)

Цель

Читать файлы/папки напрямую из БД Community Server (PostgreSQL) — быстро, без
HTTP-слоя — и представить как FileStore (read-only).

Research (сначала)

  • Определить фактическую БД OnlyOffice в VM onlyoffice-v2 (PG или MySQL?) и
    доступность: внутри VM (localhost:5432) → снаружи нужен туннель/expose
    (SSH-порт VM открыт: 127.0.0.1:32). Зафиксировать в docs/.
  • Схема: files_file, files_folder, files_folder_tree, files_security,
    tenants (tenant_id), поля (id, parent, title, content_length, modified_on,
    version, folder_id, create_by, ...).

Реализация

  • file_pg.go: pgStore (read-only) на database/sql + github.com/jackc/pgx/v5/stdlib
    (или pgxpool) — реализует List/Stat/Download (по object key MinIO/storage)
    и, опционально, Searcher по имени.
  • Guard: только SELECT; явный запрет write-методов (ErrReadOnly).
  • Конфиг: ONLYOFFICE_DSN (или ONLYOFFICE_PG_*), в .env.example только имена.
  • Download через тот же MinIO-путь, что storage_fallback.go (переиспользовать).

Тесты/критерии

  • Unit: SQL-билдеры, маппинг строк → Entry (без БД).
  • Integration (//go:build integration, skip без DSN): list/stat/download
    реального файла из дерева проекта, сверка с REST-результатом.
  • Read-only: write-методы возвращают ErrReadOnly.
  • go build/vet/test зелёные; секретов нет.
Epic: #34 ## F2 — PostgreSQL-бэкенд (read-only, «изнутри») ### Цель Читать файлы/папки напрямую из БД Community Server (PostgreSQL) — быстро, без HTTP-слоя — и представить как `FileStore` (read-only). ### Research (сначала) - Определить фактическую БД OnlyOffice в VM `onlyoffice-v2` (PG или MySQL?) и доступность: внутри VM (`localhost:5432`) → снаружи нужен туннель/expose (SSH-порт VM открыт: `127.0.0.1:32`). Зафиксировать в `docs/`. - Схема: `files_file`, `files_folder`, `files_folder_tree`, `files_security`, `tenants` (`tenant_id`), поля (id, parent, title, content_length, modified_on, version, folder_id, create_by, ...). ### Реализация - `file_pg.go`: `pgStore` (read-only) на `database/sql` + `github.com/jackc/pgx/v5/stdlib` (или `pgxpool`) — реализует `List`/`Stat`/`Download` (по object key MinIO/`storage`) и, опционально, `Searcher` по имени. - Guard: только SELECT; явный запрет write-методов (`ErrReadOnly`). - Конфиг: `ONLYOFFICE_DSN` (или `ONLYOFFICE_PG_*`), в `.env.example` только имена. - `Download` через тот же MinIO-путь, что `storage_fallback.go` (переиспользовать). ### Тесты/критерии - [ ] Unit: SQL-билдеры, маппинг строк → `Entry` (без БД). - [ ] Integration (`//go:build integration`, skip без DSN): list/stat/download реального файла из дерева проекта, сверка с REST-результатом. - [ ] Read-only: write-методы возвращают `ErrReadOnly`. - [ ] `go build/vet/test` зелёные; секретов нет.
eSlider added the feature label 2026-09-16 17:21:13 +01:00
Author
Owner

F2 — отчёт: read-only SQL-бэкенд файлов (Community Server DB)

PR: #45
Ветка feat/pg-store#36 · коммит bf3ef02 (base main).
Не мержу и не закрываю.

Research — БД оказалась MySQL, не PostgreSQL (блокера нет)

Проверено live на VM onlyoffice-v2 (SSH 127.0.0.1:32):

  • Community Server работает на MySQL 8.0, хост 127.0.0.1:3306 внутри VM,
    база onlyoffice; конфиг /etc/onlyoffice/communityserver/appsettings.production.json
    (providerName: MySql.Data.MySqlClient). Активные коннекты mono → :3306.
  • Таблицы: files_file (8987 строк, PK tenant_id,id,version), files_folder
    (553), files_folder_tree (2455), files_security; тенанты — tenants_tenants
    (таблицы tenants нет). Поля: id, version, version_group,
    current_version (1 = текущая версия), folder_id, title, content_length,
    create_on, modified_on, tenant_id.
  • PostgreSQL 16 в той же VM — наш контур office-assistant: база onlyoffice,
    схема edw_docs (роли edw/edw_ro), files_file там нет. К OnlyOffice
    отношения не имеет.
  • Портал хранит файлы в S3/MinIO (бакет office, хост 192.168.188.10:9000);
    DiscStorage — только мелочь.

Вывод: цель F2 (прямой SQL-доступ к БД Community Server) достижима, но движок —
MySQL. Issue/эпик предполагали PG. Реализовал database/sql с выбором драйвера
по DSN: mysql live, pgx для PostgreSQL (если портал перенесут). Оба пути
проверены живьём (pgx — подключением к edw_ro).

Туннель (MySQL)

ssh -f -N -o ControlMaster=no -o ControlPath=none -p 32 -i ~/.ssh/id_ed25519 \
    -L 13306:127.0.0.1:3306 root@127.0.0.1
# DSN: root:<pw>@tcp(127.0.0.1:13306)/onlyoffice?parseTime=true

ControlMaster=no / ControlPath=none обязательны (иначе forward уходит в
persistent master из ~/.ssh/config).

Реализация

  • file_pg.go: pgStore (read-only) на database/sql; драйверы
    github.com/go-sql-driver/mysql и github.com/jackc/pgx/v5/stdlib.
    List/Stat/Download, Search по имени (LIKE), Name() = mysql|postgres.
    Write-методы → ErrReadOnly. current_version = 1 — только текущая версия.
  • Download — REST не используется: ключ объекта
    00/00/<tenant>/files/folder_<shard>/file_<id>/v<version>/content.<ext>,
    shard = (id/1000+1)*1000 (файл 3727 → folder_4000, НЕ folder_id; проверено
    live по MinIO). Стриминг — через новый общий downloadMinioObject в
    storage_fallback.go (переиспользование, без дублей).
  • Конфиг: ONLYOFFICE_DSN, ONLYOFFICE_PG_DRIVER, ONLYOFFICE_PG_TENANT
    (fallback ONLYOFFICE_TENANT), ONLYOFFICE_PG_{HOST,PORT,USER,PASSWORD,DBNAME,SSLMODE}.
    .env.example — только имена.
  • Док: docs/community-server-db.md (схема, туннель, env, ключ MinIO, грабли).

Тесты (имена + PASS)

  • Unit file_pg_test.go: TestRebind, TestCSPObjectKey,
    TestPGDriverDetection, TestNormalizeSQLDSNMySQL, TestFileRowToEntry,
    TestFolderRowToEntry, TestPGStoreWriteMethodsReadOnly, TestPGStoreName,
    TestPGStoreStatRejectsNonNumeric — PASS.
  • Integration file_pg_integration_test.go (//go:build integration, skip без
    ONLYOFFICE_DSN): TestIntegrationPGStore — PASS live (backend mysql):
    Stat/List сверены с REST (c.Files()), Download совпал побайтово с REST,
    write-методы дают ErrReadOnly.
  • pgx-путь: NewPGStore c EDW_RO_DSN → Name()=postgres, ping OK (live).
  • go build ./... && go vet ./... && go test -race ./... — зелёные.
    gitleaks — no leaks found. Секретов в коде нет.

Блокеры

Нет. Замечание для PO: issue/эпик называют бэкенд «PostgreSQL», фактически
источник — MySQL. Если нужен строго PG-only — скажите, уберу mysql-ветку; но
тогда live-данных для integration нет (в PG files_file отсутствует).

## F2 — отчёт: read-only SQL-бэкенд файлов (Community Server DB) PR: https://git.produktor.io/eSlider/go-onlyoffice/pulls/45 Ветка `feat/pg-store#36` · коммит `bf3ef02` (base `main`). Не мержу и не закрываю. ### Research — БД оказалась MySQL, не PostgreSQL (блокера нет) Проверено live на VM `onlyoffice-v2` (SSH `127.0.0.1:32`): - Community Server работает на **MySQL 8.0**, хост `127.0.0.1:3306` внутри VM, база `onlyoffice`; конфиг `/etc/onlyoffice/communityserver/appsettings.production.json` (`providerName: MySql.Data.MySqlClient`). Активные коннекты `mono → :3306`. - Таблицы: `files_file` (8987 строк, PK `tenant_id,id,version`), `files_folder` (553), `files_folder_tree` (2455), `files_security`; тенанты — `tenants_tenants` (таблицы `tenants` нет). Поля: `id`, `version`, `version_group`, `current_version` (1 = текущая версия), `folder_id`, `title`, `content_length`, `create_on`, `modified_on`, `tenant_id`. - PostgreSQL 16 в той же VM — **наш** контур office-assistant: база `onlyoffice`, схема `edw_docs` (роли `edw`/`edw_ro`), `files_file` там нет. К OnlyOffice отношения не имеет. - Портал хранит файлы в **S3/MinIO** (бакет `office`, хост `192.168.188.10:9000`); DiscStorage — только мелочь. Вывод: цель F2 (прямой SQL-доступ к БД Community Server) достижима, но движок — MySQL. Issue/эпик предполагали PG. Реализовал `database/sql` с выбором драйвера по DSN: **mysql** live, **pgx** для PostgreSQL (если портал перенесут). Оба пути проверены живьём (pgx — подключением к `edw_ro`). ### Туннель (MySQL) ```bash ssh -f -N -o ControlMaster=no -o ControlPath=none -p 32 -i ~/.ssh/id_ed25519 \ -L 13306:127.0.0.1:3306 root@127.0.0.1 # DSN: root:<pw>@tcp(127.0.0.1:13306)/onlyoffice?parseTime=true ``` `ControlMaster=no / ControlPath=none` обязательны (иначе forward уходит в persistent master из `~/.ssh/config`). ### Реализация - `file_pg.go`: `pgStore` (read-only) на `database/sql`; драйверы `github.com/go-sql-driver/mysql` и `github.com/jackc/pgx/v5/stdlib`. `List`/`Stat`/`Download`, `Search` по имени (`LIKE`), `Name()` = `mysql`|`postgres`. Write-методы → `ErrReadOnly`. `current_version = 1` — только текущая версия. - `Download` — REST не используется: ключ объекта `00/00/<tenant>/files/folder_<shard>/file_<id>/v<version>/content.<ext>`, `shard = (id/1000+1)*1000` (файл 3727 → `folder_4000`, НЕ `folder_id`; проверено live по MinIO). Стриминг — через новый общий `downloadMinioObject` в `storage_fallback.go` (переиспользование, без дублей). - Конфиг: `ONLYOFFICE_DSN`, `ONLYOFFICE_PG_DRIVER`, `ONLYOFFICE_PG_TENANT` (fallback `ONLYOFFICE_TENANT`), `ONLYOFFICE_PG_{HOST,PORT,USER,PASSWORD,DBNAME,SSLMODE}`. `.env.example` — только имена. - Док: `docs/community-server-db.md` (схема, туннель, env, ключ MinIO, грабли). ### Тесты (имена + PASS) - Unit `file_pg_test.go`: `TestRebind`, `TestCSPObjectKey`, `TestPGDriverDetection`, `TestNormalizeSQLDSNMySQL`, `TestFileRowToEntry`, `TestFolderRowToEntry`, `TestPGStoreWriteMethodsReadOnly`, `TestPGStoreName`, `TestPGStoreStatRejectsNonNumeric` — PASS. - Integration `file_pg_integration_test.go` (`//go:build integration`, skip без `ONLYOFFICE_DSN`): `TestIntegrationPGStore` — **PASS live** (backend `mysql`): `Stat`/`List` сверены с REST (`c.Files()`), `Download` совпал побайтово с REST, write-методы дают `ErrReadOnly`. - pgx-путь: `NewPGStore` c `EDW_RO_DSN` → `Name()=postgres`, ping OK (live). - `go build ./... && go vet ./... && go test -race ./...` — зелёные. gitleaks — `no leaks found`. Секретов в коде нет. ### Блокеры Нет. Замечание для PO: issue/эпик называют бэкенд «PostgreSQL», фактически источник — MySQL. Если нужен строго PG-only — скажите, уберу mysql-ветку; но тогда live-данных для integration нет (в PG `files_file` отсутствует).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: eSlider/go-onlyoffice#36