Migrations
YDB is a distributed database, so some schema operations that traditional databases (PostgreSQL, MySQL) allow require a different approach or are not available at all. The schema editor never silently ignores an unsupported operation — depending on how dangerous it is, it either:
raises
NotSupportedErrorfor changes that would leave the table and the Django model out of sync and break queries (renaming a column, changing a column type, changing the primary key) — the migration fails fast instead of corrupting the schema; orskips the operation with a logged warning for things YDB cannot honour but that leave the table queryable (unique / check constraints,
unique_together, making a column NOT NULL after creation) — the migration proceeds.
Because unenforceable operations are skipped rather than rejected,
python manage.py migrate for the standard Django apps (contenttypes, auth,
admin, sessions) runs to completion — but uniqueness and check guarantees
are not enforced by the database and must live in application logic.
Schema changes (ALTER TABLE)
Supported:
Add a nullable column (
ADD COLUMN).Add a NOT NULL column that has a default — the default is written into the DDL (
ADD COLUMN ... NOT NULL DEFAULT <value>) so YDB can backfill existing rows.Remove a column (
DROP COLUMN).Rename the table.
Add / drop a secondary index for a field (
db_index).Relax a column from NOT NULL to nullable (
ALTER COLUMN ... DROP NOT NULL).
Raises NotSupportedError:
Add a NOT NULL column without a default — YDB cannot backfill existing rows. Add it as nullable, or give the field a default.
Change a column type.
Rename a column.
Change the primary key.
The workaround for these is to create a new table with the required schema and copy the data across.
Skipped with a warning:
Making a column NOT NULL after creation (YDB can only drop NOT NULL, not add it; the column keeps its current nullability).
Default-value changes (defaults are applied by Django, not stored in YDB), so new rows still get the new default.
Constraints
The primary key (set at table creation, immutable afterwards) and NOT NULL at creation time are the constraints YDB enforces. The rest are accepted by the ORM and the migration runs, but the database does not enforce them:
unique/unique_together— not enforced (unique secondary indexes are unreleased in YDB). The migration skips the constraint with a warning and the database accepts duplicates; enforce withvalidate_unique()if you need it.CHECKconstraints (column and table) — not supported; skipped with a warning. Enforce inclean()/ validators.Foreign-key constraints — never created or introspected.
Multiple constraints / indexes on the same fields are not supported.
Indexes
Secondary, non-unique indexes (
db_index,Index) — supported, created at table creation or viaADD INDEX ... GLOBAL. Renaming an index works.Covering indexes (
Index(include=...)) — supported; emit aCOVER (...)clause so the index can satisfy a query without reading the table.Unique indexes are not available (secondary indexes are non-unique; unique indexes are unreleased in YDB), and partial and expression indexes are unsupported.
Relations and many-to-many
Relations are stored as plain scalar columns (<name>_id) typed from the
target’s primary key; no FOREIGN KEY, REFERENCES or ON DELETE SQL is
emitted, and referential integrity is left to the application.
Auto-created many-to-many through tables are created and dropped together with
their model, so ManyToManyField
add / list / remove works at the ORM level. Custom (through=) models are
created as ordinary tables.
Primary keys
The primary key must be specified when the table is created, and it cannot be
changed afterwards (changing it raises NotSupportedError).
Multi-table inheritance and primary-key-only models
Inserting a row whose only column is an auto-increment (Serial) primary key is
not supported and raises NotSupportedError: YDB has no
INSERT ... DEFAULT VALUES, rejects NULL for a Serial column, and the
database — not the client — generates the key, so there is no value to insert.
This affects two model shapes:
Multi-table inheritance (
class Child(Parent)with a concrete parent): saving a child first inserts the parent row, which is primary-key-only.Primary-key-only models (
class M(models.Model): pass).
Use a single concrete model (or abstract = True base classes) and give every
model at least one non-primary-key field.
Introspection and inspectdb
Introspection reflects what YDB actually exposes through DESCRIBE:
Column nullability is accurate — a column reported as
Optional<T>is nullable, otherwise NOT NULL.Indexes are reported with their columns in ascending order; secondary indexes are non-unique.
Sequences are reported only for an integer primary key.
inspectdbfield-type mapping is lossy where several Django fields share one YDB type (for exampleUtf8is reported asTextField,DoubleasFloatField).Foreign keys, check constraints, and column defaults are never reported, because YDB does not enforce or store them.