Support Contract and Compatibility Matricesο
This is the single source of truth for what django-ydb-backend supports.
Issues, pull requests, release notes, and the other docs in this directory
should link here rather than restating support claims. It exists to make the
backendβs guarantees explicit before the first non-beta release (issue #30).
Status: ratified. The support levels below were reviewed against the evidence (see How this was determined) and ratified by the maintainer. Remaining work is tracked in the linked issues and split into release-blocking vs non-blocking.
Support levelsο
Level |
Meaning |
|---|---|
β Supported |
Works as documented and is exercised by tests. Safe to rely on in production within the stated caveats. |
π‘ Best-effort |
Works, but with documented functional caveats (partial coverage, edge cases, reduced precision). Use only after reading the caveat. |
β Unsupported |
Does not work, or the database does not enforce it. The backend may raise a clear error, silently skip the operation (documented), or accept an ORM declaration that YDB never guarantees. |
βͺ Not yet evaluated |
No conformance evidence yet. Treat as unsupported until verified. |
Database-enforced guarantees are credited only when YDB itself enforces them.
Foreign keys, uniqueness, and check constraints are not enforced by YDB, so
they are marked β β even though the ORM still accepts the declaration. You can
reimplement them in application code (validate_unique(), clean(), validators),
but that is the applicationβs responsibility, not backend support.
Version supportο
The first non-beta target (issue #30):
Component |
Supported range |
Recommended |
Notes |
|---|---|---|---|
Python |
|
3.12 |
Declared in |
Django |
|
5.2 LTS |
Matches the |
YDB |
|
latest stable / |
|
ydb-dbapi |
|
0.1.8+ |
Dependency pin in |
How this was determinedο
Three sources back every classification:
Conformance harness (issue #72).
conformance/run.sh <module>checks out the matching Django source tree and runs Djangoβs own database test suite (tests/runtests.py) against a local YDB. This is the standard third-party backend approach. Per-module results are summarised in the matrices below; reproduce any of them with, e.g.,conformance/run.sh lookup.DatabaseFeaturesflags (ydb_backend/backend/features.py). The backendβs declared capabilities, anddjango_test_skips, which records each skipped Django test with the reason.The shipped behavior documented in FIELDS, MIGRATIONS, TRANSACTIONS, OPERATIONS, and CONTRIB.
The conformance harness reports
supports_transactions = Falsefor its own process only, to degrade DjangoβsTestCasetoTransactionTestCase(YDB has no savepoints). The shipped backend keepssupports_transactions = True; the real limitation is savepoints, not transactions (see Transactions).
Fieldsο
Full type mapping is in FIELDS.md. Status of each Django field:
Field |
Status |
Notes |
|---|---|---|
|
β |
|
|
β |
Stored as |
|
β |
Stored as |
|
β |
|
|
β |
Signed/unsigned int types per range; see FIELDS.md. |
|
β |
|
|
β |
|
|
β |
Arbitrary |
|
β |
Native |
|
β |
|
|
β |
|
|
β |
|
|
π‘ |
Naive datetimes shift by the server timezone on round-trip (issue #78). Microsecond precision is fine. Production default |
|
π‘ |
No native YDB time type; stored as |
|
π‘ |
Native |
Relationsο
ORM-level relations work; YDB enforces none of the relational guarantees β see FIELDS.md and CONTRIB.md.
Feature |
Status |
Notes |
|---|---|---|
|
β |
Stored as a scalar |
|
β |
Through tables are ordinary YDB tables; add/list/remove works. |
|
β |
Conformance: 20/20. |
|
π‘ |
Conformance: ~108/113; a few edge cases fail. |
DB-level FK constraints / |
β |
|
DB-level cascade delete |
β |
Django ORM |
O2O / M2M-pair uniqueness |
β |
Not enforced by YDB. The ORM declaration is accepted but duplicates are not rejected; enforce in app code if needed. |
Constraintsο
See MIGRATIONS.md. βSkippedβ operations are logged with a warning and the migration proceeds; the guarantee is not enforced.
Constraint |
Status |
Notes |
|---|---|---|
Primary key |
β |
Must be set at table creation; immutable afterwards (changing it raises |
NOT NULL (at create) |
β |
Enforced. |
|
β |
Not enforced by YDB (unique secondary indexes are unreleased). Migration skips the constraint with a warning and the DB will accept duplicates; enforce in app code ( |
Nullable / partially-nullable unique |
β |
|
CHECK (column & table) |
β |
Not supported by YDB ( |
Foreign-key constraints |
β |
Not created or introspected. |
Multiple constraints/indexes on the same fields |
β |
|
Indexesο
Index type |
Status |
Notes |
|---|---|---|
Secondary, non-unique ( |
β |
|
Unique index |
β |
Secondary indexes are non-unique; unique indexes are unreleased in YDB. |
Partial index ( |
β |
|
Expression index |
β |
|
Covering index ( |
β |
Emits |
Rename index |
β |
|
Introspect column ordering (ASC/DESC) |
β |
|
Transactionsο
Full contract in TRANSACTIONS.md.
Feature |
Status |
Notes |
|---|---|---|
|
β |
Body runs in a YDB interactive transaction; commit on clean exit, rollback on exception. |
Autocommit (outside |
β |
Each statement is its own transaction; driver auto-retries transient errors. |
|
β |
Optimistic concurrency; conflicts surface as |
Savepoints |
β |
|
Nested |
β |
No savepoints: catching an exception inside a nested block poisons the whole transaction. |
Django |
β |
Relies on savepoints. Use |
DDL inside |
β |
|
Automatic retry of |
β |
Application responsibility β catch |
Migrations & schema operationsο
See MIGRATIONS.md. The schema editor never silently ignores an
operation: it either raises NotSupportedError or skips with a warning.
Operation |
Status |
Notes |
|---|---|---|
|
β |
|
Add nullable column |
β |
|
Add NOT NULL column with default |
β |
Default materialised into DDL for backfill. |
Add NOT NULL column without default |
β |
Raises |
Drop column |
β |
|
Rename table |
β |
|
Relax NOT NULL β nullable |
β |
|
Make column NOT NULL after creation |
β |
Skipped with a warning (YDB can only drop NOT NULL). |
Change column type |
β |
Raises |
Rename column |
β |
Raises |
Change primary key |
β |
Raises |
Add / drop secondary index |
β |
|
|
β |
Runs to completion (unenforceable constraints skipped). |
Default-value change |
β |
No-op at the DB level β YDB does not store column defaults. Harmless for the ORM: Django applies field defaults in Python, so new rows still get the new default. |
Table/column comments, stored procedures |
β |
Not supported. |
ORM query featuresο
Feature |
Status |
Notes |
|---|---|---|
CRUD ( |
β |
|
Most field lookups |
β |
|
Backslash / |
β |
Escaped correctly via |
Coercing lookups ( |
β |
Raise during parameter handling. |
Correlated subqueries ( |
β |
YDB cannot resolve the outer reference (issue #77). Non-correlated subqueries work. |
Aggregation / annotation |
π‘ |
GROUP BY validation fixed (#76); tail remains: |
|
β |
|
|
β |
|
UNION with |
π‘ |
Several orderings not yet handled. |
UNION as a subquery / wrapped for |
β |
Generates invalid SQL. |
|
β |
Reads back generated PKs ( |
|
β |
Works; with DB functions / JSONField / MTI it is π‘ (partial). |
|
β |
|
Window functions ( |
β |
|
|
β |
|
|
β |
|
Insert into a primary-key-only / MTI-parent table |
β |
Raises |
|
β |
|
Introspection & inspectdbο
See MIGRATIONS.md.
Aspect |
Status |
Notes |
|---|---|---|
Column nullability |
β |
|
Indexes (columns, ASC order) |
β |
Secondary indexes non-unique. |
Sequences |
π‘ |
Reported only for an integer primary key. |
Field-type mapping ( |
π‘ |
Lossy where several Django fields share a YDB type (e.g. |
Foreign keys |
β |
Never reported (not enforced). |
Check constraints |
β |
Never reported. |
Column default |
β |
|
Admin, Auth & contrib appsο
Covered by tests/django_contrib/test_smoke.py; see CONTRIB.md.
Workflow |
Status |
Notes |
|---|---|---|
|
β |
|
Create users / superusers, password checks |
β |
|
Groups, permissions, |
β |
|
Session create / load / delete |
β |
|
Admin login & model changelists |
β |
|
Unique usernames / M2M-pair uniqueness |
β |
Not DB-enforced (YDB has no unique constraints). Djangoβs |
UPSERTο
The supported public UPSERT is the native YDB UPSERT INTO β a single atomic,
race-free statement keyed on the primary key. The user-facing API is
YDBManager: set objects = YDBManager() on the model, then call
Model.objects.upsert(obj) / bulk_upsert(objs) (each accepts a model instance
or a dict and returns the persisted instances).
Aspect |
Status |
Notes |
|---|---|---|
|
β |
Native |
Conflict target |
β (PK only) |
UPSERT is keyed on the primary key. |
|
π‘ |
Restricts the written columns; columns left out are preserved on existing rows. YDB requires every NOT NULL column to be present, so |
The docs/examples in OPERATIONS.md are rewritten separately in #47.
Fundamental limitationsο
These follow from YDBβs architecture and are not expected to change (issue #79). Design around them:
No savepoints. Nested
atomic()blocks cannot roll back independently, and DjangoβsTestCasedoes not work β useTransactionTestCase.No database-level referential / unique / check enforcement. Foreign keys, uniqueness, and checks are application responsibilities.
No insert into a primary-key-only table. YDB has no
INSERT ... DEFAULT VALUESand rejectsNULLfor aSerialcolumn, and the database generates the key β so a row whose only column is an auto PK cannot be inserted. This rules out multi-table inheritance (concrete parents) and primary-key-only models; both raiseNotSupportedError. Give every model at least one non-PK field, and useabstract = Truebase classes instead of concrete parents.No correlated subqueries. A subquery that references the enclosing queryβs table fails with
Member not found: <table>(the outer row is not in scope). This coversOuterRefinsideExists/Subquery, anExists/subquery used as a lookupβs left-hand side, and Djangoβsexclude()across a multivalued relationship (which Django compiles to a correlated subquery). Non-correlated subqueries (e.g.field__in=<queryset>) work. This is a YDB platform limitation (#77); it is distinct from theUnknown name: $element_Nparameter-wiring error (related-field DELETE), which is fixed.
Release-blocking vs non-blocking gapsο
Proposed split for the first non-beta (issue #51 tracks readiness):
Release-blocking (must be resolved or accurately documented before non-beta):
This support contract exists and is referenced by README/docs β (this issue, #30).
README/docs support claims match tested behavior (#50).
Version target named and packaging metadata aligned (Python
>=3.10, Django>=4.2,<7.0). βTransaction contract documented (#36, done).
Native
UPSERT INTOwired up as the supported upsert path (#46). βPattern/exact lookup escaping returns the correct rows (#75). β Fixed β backslash,
%and_are escaped withESCAPE '~'.
Non-blocking (documented limitations, can ship as known gaps):
Naive datetime shift under
USE_TZ=False(#78) β production default unaffected.Function/aggregate tail:
PI()/Random()/CURRENT_TIMESTAMP, ORDER BY aggregated (#80).Correlated-subquery LHS (#77).
UNION ordering / subquery edge cases.
UPSERT docs/examples rewrite (#47) β follows the #46 implementation.
Ratification statusο
All matrix support levels and the blocking/non-blocking split above have been reviewed and ratified by the maintainer. Decisions recorded:
Python floor β
>=3.10.Django range β
>=4.2,<7.0(matches the packaging pin).Unenforced guarantees (foreign keys, uniqueness, checks) β β, not best-effort; application-level workarounds do not count as backend support.
UPSERT contract β native
UPSERT INTOis the supported path (implementation tracked in #46).Pattern/exact lookup escaping (#75) β release-blocking.
This contract is the baseline; future changes to a support level should update this document in the same PR.