Operations
See the support contract for the UPSERT support level and the ORM query features matrix.
The operations implement the Django ORM query compilation system in a YDB-specific syntax, taking into account the features of this distributed database.
Features:
All string types (CharField, TextField) are mapped to Utf 8.
Datetime is used for the DateTimeField, and timestamps are processed via .timestamp().
Query parameters
YDB requires typed query parameters. The backend types each parameter from the Django expression that produced it — a lookup’s value is typed from the left-hand side’s field, nested expressions and subqueries from their own compilation — rather than by inspecting the generated SQL. A parameter whose type cannot be resolved is typed from its Python value.
This covers joins, foreign-key filters, __in, F(), Case/When,
annotations, aggregate (HAVING) filters and non-correlated subqueries.
Limitations
Correlated subqueries are not supported.
Exists()/Subquery()withOuterRefreference the outer table from inside the subquery, which YDB cannot resolve. Non-correlated subqueries work.
UPSERT
UPSERT (UPDATE or INSERT) writes rows keyed on the primary key: a missing
row is inserted, and an existing row has the written columns overwritten while
its other columns are preserved. The backend uses YDB’s native UPSERT INTO,
which runs as a single atomic statement — there is no read-modify-write
step, so concurrent upserts of the same key cannot create duplicates.
Manager setup
UPSERT is provided by YDBManager. Set it as the model’s manager:
from django.db import models
from ydb_backend.models.manager import YDBManager
class NFTToken(models.Model):
contract_address = models.CharField(max_length=42)
token_id = models.CharField(max_length=78, primary_key=True)
owner = models.CharField(max_length=42)
metadata_url = models.CharField(max_length=256)
last_price = models.FloatField()
objects = YDBManager()
upsert() and bulk_upsert()
Both accept a model instance or a dict (bulk_upsert accepts a list, and may
mix the two) and return the persisted instances:
# Insert: the row does not exist yet.
NFTToken.objects.upsert({
"contract_address": "0x1a2b3c4d5e",
"token_id": "12345",
"owner": "0xAlice123",
"metadata_url": "ipfs://QmXyZ123",
"last_price": 1.5,
})
# Update: same primary key — the listed columns are overwritten.
NFTToken.objects.upsert({
"contract_address": "0x1a2b3c4d5e",
"token_id": "12345",
"owner": "0xBob456",
"metadata_url": "ipfs://QmXyZ456",
"last_price": 2.5,
})
# Bulk: one statement upserts every row.
tokens = NFTToken.objects.bulk_upsert([
{"contract_address": "0x11", "token_id": "100", "owner": "0xA",
"metadata_url": "ipfs://a", "last_price": 10.0},
NFTToken(contract_address="0x22", token_id="200", owner="0xB",
metadata_url="ipfs://b", last_price=20.0),
])
Conflict target
UPSERT is always keyed on the primary key. conflict_target may be omitted (it
defaults to the primary key) or set to the primary key explicitly; any other
target raises NotSupportedError, because YDB has no unique constraints to
match on:
NFTToken.objects.upsert(data, conflict_target="token_id") # ok — the PK
NFTToken.objects.upsert(data, conflict_target="owner") # NotSupportedError
Writing a subset of columns
update_fields restricts which columns are written; columns left out are
preserved on existing rows. YDB’s UPSERT INTO requires every NOT NULL
column to be present, so update_fields may only drop nullable columns —
omitting a NOT NULL column raises NotSupportedError.
class InventoryItem(models.Model):
sku = models.CharField(max_length=20, primary_key=True)
name = models.CharField(max_length=100) # NOT NULL
reorder_level = models.IntegerField(null=True) # nullable
quantity = models.IntegerField() # NOT NULL
objects = YDBManager()
# Writes name + quantity; the nullable reorder_level is left untouched.
InventoryItem.objects.upsert(
{"sku": "A1", "name": "Widget", "quantity": 9},
update_fields=["name", "quantity"],
)
# Raises NotSupportedError: omits the NOT NULL column `quantity`.
InventoryItem.objects.upsert(
{"sku": "A1", "name": "Widget"},
update_fields=["name"],
)
The behavior above is covered by tests/compiler/test_upsert.py.