#32577: Add support for `UUIDAutoField` `DEFAULT_AUTO_FIELD`
-------------------------------------+-------------------------------------
     Reporter:  Tomasz Wójcik        |                    Owner:  nobody
         Type:  New feature          |                   Status:  new
    Component:  Database layer       |                  Version:  3.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:  Accepted
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Comment (by Nick Pope):

 I've been involved in some of the changes and discussions related to
 `AutoField` improvements and `DEFAULT_AUTO_FIELD` and wanted to clarify a
 few things, as well as what needs to be done to achieve the creation of
 `UUIDAutoField`.

 ----

 Some points about `DEFAULT_AUTO_FIELD` which was added in
 b5e12d490af3debca8c55ab3c1698189fdedbbdb:

 - The current implementation restricts to values that are a subclass of
 `AutoField`. (See
 
[https://github.com/django/django/commit/b5e12d490af3debca8c55ab3c1698189fdedbbdb
 #diff-11ee95e0230bcff70a542128891516b8022177e0af58f00257aee9e8ef83a933R245
 here].)
 - It was named `DEFAULT_AUTO_FIELD` to indicate that it referred to the
 ''automatically generated'' `id` field.
   - This was instead of `DEFAULT_AUTOFIELD` which would imply that it
 could only ever be related to `AutoField`.
   - Another suggestion had been `DEFAULT_MODEL_PK`, but we weren't ready
 to allow other fields without `AutoField`-like semantics.
 - We could lift the restriction to allow other non-`AutoField` types, but
 the concept of an ''automatically generated'' `id` field needs to be
 decoupled from `AutoField` a bit.

 ----

 Some history and thoughts about `AutoField`:

 - Much of the logic related to the ''automatically generated'' `id` field
 is hardwired to look for `AutoField` subclasses. This needs to be fixed.
 - `AutoField` has essentially always been like an `IntegerField`, but
 historically copy-pasted some of that rather than inherit from
 `IntegerField` directly.
 - `BigAutoField` inherited from `AutoField`, but overrode with parts of
 `BigIntegerField`, but it didn't originally inherit from
 `BigIntegerField`.
 - As a result, these `*AutoFields`s didn't support the system checks or
 validators of their `*IntegerField` equivalents.
 - I addressed some of this in 21e559495b8255bba1e8a4429cd083246ab90457 for
 #29979:
   - Moved the logic about automatically generated fields into
 `AutoFieldMixin`.
   - Fixed the `*AutoField` classes to inherit from `AutoFieldMixin` and
 their appropriate `*IntegerField` type.
   - Added `AutoFieldMeta` to maintain backward compatibility for
 `isinstance(..., AutoField)` checks and provide a hook point for
 deprecation of it in the future.

 '''One more thing to mention about an ''automatically generated'' `id`
 field is that the value is generated by the database and returned - it
 must not be generated in Python.'''

 ----

 What needs to be done to get to `UUIDAutoField`? Well, here is what I
 think the path is...

 - Add a `db_generated`/`is_auto` (or another better name) flag to
 `AutoFieldMixin`. (See
 [https://code.djangoproject.com/ticket/24042#comment:5 my comment]. I'm
 not convinced #24042 should have been closed.)
 - Replace `isinstance(..., AutoField)` checks with the new flag. (I seem
 to recall that `isinstance(..., AutoFieldMixin)` wasn't really favoured.)
   - This will also open up `DEFAULT_AUTO_FIELD` to a wider range of non-
 integer automatically generated field types.
 - Deprecate `isinstance` (and `issubclass`?) checks on `AutoField` within
 `AutoFieldMeta`. (The metaclass can be scrapped when the deprecation
 period ends.)
 - Implement support for `.db_default` so that we can specify a function
 for generating a UUID on the database rather than in Python, e.g.
 
[https://docs.djangoproject.com/en/3.2/ref/contrib/postgres/functions/#randomuuid
 RandomUUID].
   - Work is ongoing in this [https://github.com/django/django/pull/13709
 PR] to address #470 which will implement this.
   - It is also possible that this
 [https://github.com/django/django/pull/11783 PR] to address #30032 is an
 acceptable alternative path.
 - Make `AutoFieldMixin` part of the public API and document it so that
 others can use it for their own non-integer, non-UUID implementations.
 - Finally, implement `UUIDAutoField` itself. This is a start, there will
 be more pieces to the puzzle:

 {{{#!python
 class UUIDAutoField(AutoFieldMixin, UUIDField):

     def __init__(self, *args, **kwargs):
         kwargs['db_default'] = RandomUUID()
         super().__init__(*args, **kwargs)

     def deconstruct(self):
         name, path, args, kwargs = super().deconstruct()
         del kwargs['db_default']
         return name, path, args, kwargs

     def get_internal_type(self):
         return 'UUIDAutoField'

     def rel_db_type(self, connection):
         return UUIDField().db_type(connection=connection)
 }}}

 It is likely that `AutoFieldMixin.get_db_prep_value()` would need
 attention because of `connection.ops.validate_autopk_value()`.

 Note that this field would also be limited to databases that have a
 function to generate a UUID.

 As Mariusz said, implementing a non-integer auto field such as
 `UUIDAutoField` would close #17337.

 ----

 Here are some responses to some of the other things mentioned in the
 comments above.

 > First, the default auto field BigAutoField is not subclassing AutoField
 so it's not consistent with the error message.

 This is a little confusing as it is done a bit magically for now - see the
 bit about `AutoFieldMeta` above.
 There was, however, a bug for subclasses of `SmallAutoField` and
 `BigAutoField` which was fixed by
 45a58c31e64dbfdecab1178b1d00a3803a90ea2d.

 > While I'm not sure it would make sense in an AutoField, string primary
 keys are possible. For example, you could use a language code (en, fr, de,
 etc) as a primary key.

 Yes, you can create a textual primary key, but you have to override `id`
 for every model, e.g.

 {{{#!python
 id = models.CharField(max_length=64, primary_key=True)
 }}}

 It is unlikely that it'd make sense as an "`AutoField`" unless you can
 generate the value on the database-side.

 > Could I argue that IDs based on human-readable strings, like ​coolname,
 are a valid use case?
 >
 > It's easier to reference '"soft-cuddly-shrew-of-expertise" than
 "a1e1b1c5-5d5e-4eef-9ee9-e74e87ce71d6" or "2467584".
 >
 > Would this be seen as bad practice and we should use a slugfield that's
 not the primary key for something like this instead?

 Again, unless this were generated by the database, you'd probably be
 better off doing something like:

 {{{#!python
 def generate_coolname():
     ...  # Implementation here.

 class MyModel(models.Model):
     id = models.CharField(default=generate_coolname, max_length=64,
 primary_key=True)
 }}}

 Guaranteeing uniqueness is always a fun challenge...

 > To my understanding there's no significant performance hit from using
 UUID as a primary key. It is 4x larger than int32 (128bit). So also index
 is bigger, cache will store less data etc.

 I dredged up some articles on this topic I recalled seeing a while back:

 - https://www.2ndquadrant.com/en/blog/sequential-uuid-generators/
 - https://www.2ndquadrant.com/en/blog/sequential-uuid-generators-ssd/

 > And as I said, I'm happy to open a PR as I want to use it with UUIDs in
 my next project.

 You can use UUID primary keys today. You just have to define a custom
 primary key so that the auto field isn't generated:

 {{{#!python
 class MyModel(models.Model):
     id = models.UUIDField(primary_key=True, default=uuid.uuid4,
 editable=False)
 }}}

 Granted, this is less convenient, but it is possible and
 [https://docs.djangoproject.com/en/3.2/ref/models/fields/#uuidfield
 documented].

-- 
Ticket URL: <https://code.djangoproject.com/ticket/32577#comment:11>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/067.2985f4416870e07d62debdbce24b19bb%40djangoproject.com.

Reply via email to