lenec ru

← все посты

Drizzle: relation does not exist — типичные причины

17K

Запускаешь приложение, делаешь первый запрос, и Postgres отвечает:

error: relation "users" does not exist

На стороне Drizzle никаких компиляционных ошибок нет: схема валидная, типы есть, IDE подсвечивает поля. А база об этой таблице ничего не знает.

В моей практике эта ошибка ловится на одних и тех же повторяющихся причинах. Покажу их в порядке убывания «обычно это вот это».

1. Миграции не применены

Самый частый случай. Ты обновил схему в schema.ts, сгенерировал миграцию, но не накатил её на БД. Drizzle Kit сгенерил drizzle/0001_xxx.sql — файл лежит в репозитории, но на бэкенде ничего не выполнено.

Лечится одной командой:

pnpm drizzle-kit migrate

Или через программный API (если ты гоняешь миграции из кода):

import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);

await migrate(db, { migrationsFolder: './drizzle' });

На моём типичном проекте миграции вызываются на старте приложения. Если на сервере БД новая, а миграции на ней не запускались — конечно, relation does not exist.

2. Подключилась не та база

Бывает, ты в development-режиме сидишь на одной БД, а в проде или staging — на другой, где схемы ещё нет. DATABASE_URL переключился незаметно — например, через .env.production.

Быстрая проверка:

psql "$DATABASE_URL" -c "\dt"

Эта команда покажет таблицы в текущей подключённой БД. Если там пусто или нет нужной — ты подключился не туда либо миграции не накатывались.

3. Search path и схемы

Postgres умеет несколько схем (public, app, что угодно). По умолчанию таблицы создаются в public. Но если ты в schema.ts сделал так:

import { pgSchema, pgTable } from 'drizzle-orm/pg-core';

export const appSchema = pgSchema('app');

export const users = appSchema.table('users', {
  id: serial('id').primaryKey(),
  email: text('email').notNull(),
});

Drizzle сгенерит миграцию, которая создаст app.users. Если ты потом подключаешься с search_path = public и пишешь запрос вручную «from users» — Postgres не найдёт. И Drizzle тоже сообщит про не существующую relation, если там что-то перепутано.

Проверка:

SHOW search_path;
SELECT schemaname, tablename FROM pg_tables WHERE tablename = 'users';

Решение — либо явно указывать схему в Drizzle (appSchema.table), либо менять search_path в коннекте.

4. Опечатка в имени таблицы

Postgres различает регистр идентификаторов в зависимости от того, заквочены они или нет. users и "Users" — две разные таблицы. Если ты случайно создал таблицу как "Users" (большие буквы в кавычках) и потом запрашиваешь users — будет «does not exist».

Drizzle сам не делает таких сюрпризов: он использует имена ровно так, как ты прописал в схеме. Но если миграция уже была применена руками или другой ORM создавала таблицы — может прилететь.

Проверка:

SELECT tablename FROM pg_tables WHERE schemaname = 'public';

Если видишь Users вместо users — нужно либо переименовать, либо в Drizzle прописать имя точно так же.

5. Migration journal в Drizzle разъехался

Drizzle Kit ведёт «журнал» применённых миграций в таблице __drizzle_migrations. Если эта таблица говорит, что миграция уже применена, а сама таблица в БД отсутствует — может быть рассинхрон. Например, ты руками дропнул таблицу или восстановил БД из старого бэкапа.

Симптом тонкий: drizzle-kit migrate ничего не делает (потому что считает, что всё накатано), а в БД таблиц нет. Лечится либо ручным удалением соответствующих записей из __drizzle_migrations и повторным migrate, либо переподходом — снеси БД и накати с нуля, если это dev.

6. Drizzle подключён к БД, в которой нет миграций (multi-tenant)

На проектах с несколькими БД на одного пользователя я видел такое: код работает с БД tenant_42, а миграции накатывались только на tenant_main. Каждый новый tenant требует своей пробежки миграций, и если этого этапа нет — у нового арендатора пустая БД, и при первом запросе вылетает «relation does not exist».

Решение — на каждый новый tenant запускать migrate прежде, чем приложение начнёт ходить в эту БД.

7. Транзакции и checkpoint

Совсем редкий, но выползающий случай: ты руками в одной транзакции делаешь CREATE TABLE, потом из другой пытаешься сделать SELECT, а транзакция ещё не закоммичена. Другие коннекты не увидят таблицу, пока не commit. На обычной разработке это не страшно (миграции у Drizzle коммитят), но если интегрируешь Drizzle с какими-то внешними миграционными скриптами — стоит держать в голове.

Алгоритм диагностики, которым я пользуюсь

  • Подключиться к нужной БД psql и сделать \dt. Видишь нужную таблицу — продолжай. Нет — мигрировал не туда.
  • Проверить DATABASE_URL, к которой реально подключается приложение. Не поленись напечатать в логах при старте (без пароля!).
  • Если таблица существует под другим именем (регистр, схема) — поправить либо схему Drizzle, либо БД.
  • Если миграция в журнале есть, а таблицы нет — синхронизировать.

Профилактика

На всех своих проектах я делаю миграции обязательной частью старта приложения. Это лишние 50 миллисекунд на загрузку, но ноль шансов забыть мигрировать на проде:

// src/server/init.ts
import { migrate } from 'drizzle-orm/node-postgres/migrator';

export async function initDb() {
  await migrate(db, { migrationsFolder: './drizzle' });
}

В CI у меня шаг «прогнать миграции на тестовой БД» отдельной job-ой — если что-то сломалось в schema.ts, упадёт ещё до деплоя.

Сообщение «relation does not exist» в 95% случаев — это «ты не накатил миграцию» либо «подключился не туда». Остальные 5% — про регистр, схемы и multi-tenant. Главное — не паниковать и идти по списку.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.