-
-
Notifications
You must be signed in to change notification settings - Fork 93
Deserialization does not respect optional fields for special types for raw queries #998
Description
Bug description
If a field can be null/None and is specially handled with the DESERIALIZERS variable found in prisma/_raw_query (ie BigInt, Decimal, Json), then any raw queries which has said field and is null will error out.
This only happens on 0.14.0 and above, as raw queries were reworked for that version.
How to reproduce
- Define a schema with an optional BigInt/Decimal/Json value:
model PrismaBugExample {
id Int @id @default(autoincrement())
bad_var BigInt?
}- Set up the tables and your Python program, and create an entry with a null/
Nonevalue forbad_var:
await PrismaBugExample.prisma().create(data={"bad_var": None})- Make a raw query getting that entry:
data = await PrismaBugExample.prisma().query_first("SELECT * FROM PrismaBugExample")The bug should occur at this step.
Expected behavior
For raw queries including these nullable types to work.
Prisma information
Schema: https://github.com/AstreaTSS/PYTHIA/blob/8873fd94c770c9faa503c70201c01af3a0390943/schema.prisma
Models (there is some post-generation adjustments that I do to them - it has no effect on this bug but I might as well include everything): https://github.com/AstreaTSS/PYTHIA/blob/8873fd94c770c9faa503c70201c01af3a0390943/common/models.py
Query:
- Actual Query: https://github.com/AstreaTSS/PYTHIA/blob/8873fd94c770c9faa503c70201c01af3a0390943/common/models.py#L434-L452
- Execution: https://github.com/AstreaTSS/PYTHIA/blob/8873fd94c770c9faa503c70201c01af3a0390943/common/models.py#L122-L124
Environment & setup
- OS: Windows - WSL, Ubuntu
- Database: PostgreSQL
- Python version: 3.12.3
- Prisma version:
prisma : 5.17.0
prisma client python : 0.14.0
platform : debian-openssl-1.1.x
expected engine version : 393aa359c9ad4a4bb28630fb5613f9c281cde053
installed extras : []
install path : /home/astrea/Documents/PYTHIA/env/lib/python3.12/site-packages/prisma
binary cache dir : /home/astrea/.cache/prisma-python/binaries/5.17.0/393aa359c9ad4a4bb28630fb5613f9c281cde053
Internal cause
Doing some investigation on my own, I found the culprit of the bug - _deserialize_prisma_object in src/prisma/_raw_query.py:
prisma-client-py/src/prisma/_raw_query.py
Lines 122 to 166 in f1ab3b1
| def _deserialize_prisma_object( | |
| fields: list[object], | |
| *, | |
| result: RawQueryResult, | |
| for_model: bool, | |
| model: type[BaseModelT] | None = None, | |
| ) -> BaseModelT | dict[str, Any]: | |
| # create a local reference to avoid performance penalty of global | |
| # lookups on some python versions | |
| _deserializers = DESERIALIZERS | |
| new_obj: dict[str, Any] = {} | |
| for i, field in enumerate(fields): | |
| key = result.columns[i] | |
| prisma_type = result.types[i] | |
| if prisma_type.endswith('-array'): | |
| if field is None: | |
| # array fields can stil be `None` | |
| new_obj[key] = None | |
| continue | |
| if not isinstance(field, list): | |
| raise TypeError( | |
| f'Expected array data for {key} column with internal type {prisma_type}', | |
| ) | |
| item_type, _ = prisma_type.split('-') | |
| new_obj[key] = [ | |
| _deserializers[item_type](value, for_model) | |
| # | |
| if item_type in _deserializers | |
| else value | |
| for value in field | |
| ] | |
| else: | |
| value = field | |
| new_obj[key] = _deserializers[prisma_type](value, for_model) if prisma_type in _deserializers else value | |
| if model is not None: | |
| return model_parse(model, new_obj) | |
| return new_obj |
More specifically, focusing in on the non-array handling (essentially a single line), we get this:
new_obj[key] = _deserializers[prisma_type](value, for_model) if prisma_type in _deserializers else valueFrom my understanding, what this does is:
- Sees if the
prisma_type(which I think is gathered from the model or from Prisma proper - point is, it's gotten independently of the actual query result) is in theDESERIALIZERSmentioned earlier (though aliased under_deserializers). - If it is, then it throws the gotten value directly into the deserializer.
Note that this makes no check to see if the value is, say, None. Interestingly enough, arrays do check for None, meaning it won't occur there.
Anyways, a potential fix is just to hoist that None check for arrays to above the conditional statement, making it run for all values regardless of type. I was unsure if that fixes the issue in every case, and I'm unsure if that's necessarily the way it should be fixed, hence this issue.