Opened 11 months ago
Last modified 5 months ago
#35941 assigned New feature
Add composite GenericForeignKey support — at Version 10
| Reported by: | Csirmaz Bendegúz | Owned by: | Csirmaz Bendegúz |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Csirmaz Bendegúz, Peter Thomassen | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
This is a follow up to #373 (CompositePrimaryKey).
Proposal:
My proposal is to implement GenericForeignKey support with JSON.
object_idis aCharField(orTextField)- CompositePrimaryKey is stored as a JSON array in
object_id - JOINs can be achieved with JSON functions (varies per db backend)
Joins:
After some experimentation with JSON functions, I believe the simplest solution is to construct JOINs like this:
JOIN ON ((object_id::jsonb)->>0)::integer = id
AND ((object_id::jsonb)->>1)::uuid = uuid
Casting is a pain point, especially when joining on DateTimeFields, as we need to make sure the two columns are in the same format.
Risks:
What if someone is using a JSON array as the primary key (but it's not a composite primary key)?
Before deserializing the JSON array, we need to check if the content type has a composite primary key or not.
What if the db backend doesn't support JSON functions?
All supported databases support JSON functions.
JOIN on JSON functions is not efficient
When storing a composite primary key in a CharField / TextField, JSON is the best option we have because it's widely supported by database backends. To achieve better performance, the composite pk shouldn't be stored in a CharField / TextField in the first place.
Notes:
- JOINs must work with Unicode characters
- int, date, datetime, uuid, text fields must be supported
- Django admin's
LogEntryhas its own implementation of "generic foreign keys". The approach we take withGenericForeignKeyshould also apply toLogEntry.
Alternatives:
It's possible to implement other strategies to deal with composite generic foreign keys.
- JSONField - instead of
CharField/TextField, we could make "object_id" aJSONField. This achieves the same thing as my proposal but saves the::jsonbcast. - GenericForeignKey with multiple "object_id"s - less flexible, can't store both regular primary keys and composite primary keys.
There's no reason we couldn't implement other strategies. I believe my proposal provides the most flexibility and backwards-compatibility (at the cost of performance).
Any feedback is appreciated!
Change History (10)
comment:1 by , 11 months ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:3 by , 11 months ago
I think the proposal looks great Ben, thanks for creating this ticket.
One thing that comes to mind as potentially problematic in JSON array comparison for JOIN is the treatment of Unicode characters as we're using json.dumps(ensure_ascii=False) as well as quotes and other characters that need to be escaped in JSON. It'd be great to add test coverage for these.
As for the risks I appreciate you raising them but I think that supporting composite generic foreign key composed of standard members (int, date, datetime, uuid, text) is already a good stretch and I would feel comfortable saying that anything that falls in the exotic primary key category should not necessarily be supported in the first place.
comment:4 by , 11 months ago
| Description: | modified (diff) |
|---|---|
| Owner: | set to |
| Status: | new → assigned |
Yes, thanks for raising these points Simon!
comment:5 by , 11 months ago
| Cc: | added |
|---|
comment:6 by , 11 months ago
| Description: | modified (diff) |
|---|---|
| Has patch: | set |
comment:7 by , 11 months ago
| Description: | modified (diff) |
|---|
comment:8 by , 11 months ago
| Description: | modified (diff) |
|---|
comment:9 by , 11 months ago
| Description: | modified (diff) |
|---|
comment:10 by , 11 months ago
| Description: | modified (diff) |
|---|
Thank you Ben for creating this ticket. Accepting as described in ticket:373#comment:204.