Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Ability to make id column unsigned. #49

Closed
wants to merge 1 commit into from

3 participants

Pavel Zinovkin Anssi Kääriäinen Daniel Swarbrick
Anssi Kääriäinen
Owner

The field type changes for PostgreSQL doesn't seem valid - it should be something like integer check (val > 0). Same for Oracle (add check) and for SQLite if there is support for checks (likely yes).

Otherwise quick cursory glance of the patch doesn't raise any blocker issues.

Pavel Zinovkin

I am familiar only with MySQL, but I did little research.

PostgreSQL: serial and bigserial are equivalent, except last creates bigint column. Also bigserial suggested by russelm in ticket comments.
http://www.postgresql.org/docs/9.1/static/datatype-numeric.html#DATATYPE-SERIAL

SQLite: "Every row of every table has an 64-bit signed integer ROWID. ... If a table contains a column of type INTEGER PRIMARY KEY, then that column becomes an alias for the ROWID.". Looks good for me.
http://sqlite.org/autoinc.html

Oracle: Sequence and trigger used to fill in auto field. Existing type (INTEGER (11)) looks big enough.

Anssi Kääriäinen
Owner

I would take an approach where unsigned means "constrained to positive values" not anything regarding 2 billion vs 4 billion limits... That is, the important part is using an unsigned datatype if that is available, and restricting the values to positive values if possible. I just can't see how unsigned=True should change the datatype to bigserial.

Also, this pull request does not contain docs or tests - so this is not ready to be pulled in.

Pavel Zinovkin

You definitely should read related ticket. Whole point is not unsigned int itself, but "twice the integer space".

Anssi Kääriäinen
Owner

My take is that the issue is that MySQL allows for unsigned int fields - and you get thus 2x the integer space for free. On PostgreSQL unsigned integer fields are not supported, and thus you will not get the space for free. Now, if you want to specify the field as "guaranteed to hold 32bit unsigned integer values" then yes, bigserial is needed. You do get 2**32 space instead of 2x the space however.

I still stand by my point: it does not make sense to make signed=True to change the datatype from 4-byte signed integer to 8-byte signed integer on PostgreSQL. unsigned should mean negative values are not allowed.

Pavel Zinovkin

I see you point. Name of parameter was suggested in ticket comments. And I agree - it may not be clear.
About PostgreSQL bigserial - it's values range restricted to to this range according with docs: 1 to 9223372036854775807
http://www.postgresql.org/docs/8.4/static/datatype-numeric.html
I don't know if it works when you try to set value of field with sql.

How about to name parameter 'bigint=True' and set for mysql bigint as type. So for PostgreSQL and MySQL values range and price of storage will be equal (8 bytes, 1-9223372036854775807).

Anssi Kääriäinen
Owner

Interestingly enough the bigserial does accept negative values (tested on 9.1), despite the documentation.

So, I suggest this:

  • document the field as taking at least 32-bit unsigned integer values (up to 2**32 -1).
  • use bigserial on PostgreSQL, number(11) on Oracle, check SQLite for correct type
  • add check(col > 0) to the DB
Daniel Swarbrick

There's no reason why serial and bigserial wouldn't accept negative values. They're simply syntactical sugar around integer and bigint column types respectively, which create a sequence with an initial nextval() of 1. If you wanted to you, could reset the sequence's values to a negative value, and nextval() would count down (up?) to zero, then into positive numbers. I can't think of an immediate use case for this though ;-)

Pavel Zinovkin pzinovkin closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

May 07, 2012
Pavel Zinovkin Ability to make id column unsigned. c5d1925
This page is out of date. Refresh to see the latest.
3  django/db/backends/__init__.py
@@ -847,7 +847,8 @@ def convert_values(self, value, field):
847 847
         internal_type = field.get_internal_type()
848 848
         if internal_type == 'DecimalField':
849 849
             return value
850  
-        elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
  850
+        elif (internal_type and internal_type.endswith('IntegerField')
  851
+                or internal_type.endswith('AutoField')):
851 852
             return int(value)
852 853
         elif internal_type in ('DateField', 'DateTimeField', 'TimeField'):
853 854
             return value
1  django/db/backends/mysql/creation.py
@@ -6,6 +6,7 @@ class DatabaseCreation(BaseDatabaseCreation):
6 6
     # be interpolated against the values of Field.__dict__ before being output.
7 7
     # If a column type is set to None, it won't be included in the output.
8 8
     data_types = {
  9
+        'UnsignedAutoField': 'integer unsigned AUTO_INCREMENT',
9 10
         'AutoField':         'integer AUTO_INCREMENT',
10 11
         'BooleanField':      'bool',
11 12
         'CharField':         'varchar(%(max_length)s)',
1  django/db/backends/oracle/creation.py
@@ -15,6 +15,7 @@ class DatabaseCreation(BaseDatabaseCreation):
15 15
     # output (the "qn_" prefix is stripped before the lookup is performed.
16 16
 
17 17
     data_types = {
  18
+        'UnsignedAutoField':            'NUMBER(11)',
18 19
         'AutoField':                    'NUMBER(11)',
19 20
         'BooleanField':                 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))',
20 21
         'CharField':                    'NVARCHAR2(%(max_length)s)',
1  django/db/backends/postgresql_psycopg2/creation.py
@@ -10,6 +10,7 @@ class DatabaseCreation(BaseDatabaseCreation):
10 10
     # be interpolated against the values of Field.__dict__ before being output.
11 11
     # If a column type is set to None, it won't be included in the output.
12 12
     data_types = {
  13
+        'UnsignedAutoField': 'bigserial',
13 14
         'AutoField':         'serial',
14 15
         'BooleanField':      'boolean',
15 16
         'CharField':         'varchar(%(max_length)s)',
3  django/db/backends/sqlite3/base.py
@@ -191,7 +191,8 @@ def convert_values(self, value, field):
191 191
         internal_type = field.get_internal_type()
192 192
         if internal_type == 'DecimalField':
193 193
             return util.typecast_decimal(field.format_number(value))
194  
-        elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
  194
+        elif (internal_type and internal_type.endswith('IntegerField')
  195
+                or internal_type.endswith('AutoField')):
195 196
             return int(value)
196 197
         elif internal_type == 'DateField':
197 198
             return parse_date(value)
1  django/db/backends/sqlite3/creation.py
@@ -7,6 +7,7 @@ class DatabaseCreation(BaseDatabaseCreation):
7 7
     # thing" given more verbose field definitions, so leave them as is so that
8 8
     # schema inspection is more useful.
9 9
     data_types = {
  10
+        'UnsignedAutoField':            'integer',
10 11
         'AutoField':                    'integer',
11 12
         'BooleanField':                 'bool',
12 13
         'CharField':                    'varchar(%(max_length)s)',
43  django/db/models/fields/__init__.py
@@ -205,6 +205,13 @@ def clean(self, value, model_instance):
205 205
         self.run_validators(value)
206 206
         return value
207 207
 
  208
+    def _internal_to_db_type(self, internal_type, connection):
  209
+        data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
  210
+        try:
  211
+            return connection.creation.data_types[internal_type] % data
  212
+        except KeyError:
  213
+            return None
  214
+
208 215
     def db_type(self, connection):
209 216
         """
210 217
         Returns the database column data type for this field, for the provided
@@ -225,12 +232,14 @@ def db_type(self, connection):
225 232
         # mapped to one of the built-in Django field types. In this case, you
226 233
         # can implement db_type() instead of get_internal_type() to specify
227 234
         # exactly which wacky database column type you want to use.
228  
-        data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
229  
-        try:
230  
-            return (connection.creation.data_types[self.get_internal_type()]
231  
-                    % data)
232  
-        except KeyError:
233  
-            return None
  235
+        return self._internal_to_db_type(self.get_internal_type(), connection)
  236
+
  237
+    def rel_db_type(self, connection):
  238
+        """
  239
+        Returns the database column data type for related field referencing
  240
+        to this.
  241
+        """
  242
+        return self.db_type(connection)
234 243
 
235 244
     @property
236 245
     def unique(self):
@@ -514,14 +523,20 @@ class AutoField(Field):
514 523
         'invalid': _(u"'%s' value must be an integer."),
515 524
     }
516 525
 
517  
-    def __init__(self, *args, **kwargs):
  526
+    def __init__(self, verbose_name=None, name=None, unsigned=False, **kwargs):
518 527
         assert kwargs.get('primary_key', False) is True, \
519 528
                "%ss must have primary_key=True." % self.__class__.__name__
520 529
         kwargs['blank'] = True
521  
-        Field.__init__(self, *args, **kwargs)
  530
+        self.unsigned = unsigned
  531
+        Field.__init__(self, verbose_name, name, **kwargs)
522 532
 
523 533
     def get_internal_type(self):
524  
-        return "AutoField"
  534
+        return 'AutoField' if not self.unsigned else 'UnsignedAutoField'
  535
+
  536
+    def rel_db_type(self, connection):
  537
+        db_type = 'IntegerField' if not self.unsigned \
  538
+                else 'PositiveIntegerField'
  539
+        return self._internal_to_db_type(db_type, connection)
525 540
 
526 541
     def to_python(self, value):
527 542
         if value is None:
@@ -1139,6 +1154,11 @@ class PositiveIntegerField(IntegerField):
1139 1154
     def get_internal_type(self):
1140 1155
         return "PositiveIntegerField"
1141 1156
 
  1157
+    def rel_db_type(self, connection):
  1158
+        if connection.features.related_fields_match_type:
  1159
+            return self.db_type(connection)
  1160
+        return self._internal_to_db_type('IntegerField', connection)
  1161
+
1142 1162
     def formfield(self, **kwargs):
1143 1163
         defaults = {'min_value': 0}
1144 1164
         defaults.update(kwargs)
@@ -1150,6 +1170,11 @@ class PositiveSmallIntegerField(IntegerField):
1150 1170
     def get_internal_type(self):
1151 1171
         return "PositiveSmallIntegerField"
1152 1172
 
  1173
+    def rel_db_type(self, connection):
  1174
+        if connection.features.related_fields_match_type:
  1175
+            return self.db_type(connection)
  1176
+        return self._internal_to_db_type('IntegerField', connection)
  1177
+
1153 1178
     def formfield(self, **kwargs):
1154 1179
         defaults = {'min_value': 0}
1155 1180
         defaults.update(kwargs)
13  django/db/models/fields/related.py
@@ -1018,19 +1018,8 @@ def formfield(self, **kwargs):
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.