Django JSONStore

Blog · Oracle

Django cuts table and index names to 30 characters on Oracle

Oracle 12.2 raised the limit for an identifier from 30 bytes to 128. Django keeps the old number. On Oracle 23ai, a fresh Django 6.1 connection still reports 30, and long names get a truncated form.

>>> connection.vendor
'oracle'
>>> connection.ops.max_name_length()
30

The limit applies to every generated name: the table, the columns, the indexes, and the constraints. Django does not raise an error above the limit. It shortens the name and appends a hash of the full name, so that two long names stay distinct.

# app "tests"
class QuarterlyRevenueReconciliationSnapshot(models.Model):
    account_reference_identifier = models.CharField(
        max_length=20, db_index=True)

>>> QuarterlyRevenueReconciliationSnapshot._meta.db_table
# Oracle:     'tests_quarterlyrevenuerecocd7e'              (30)
# PostgreSQL: 'tests_quarterlyrevenuereconciliationsnapshot' (44)

# the index on that column, read from user_indexes:
#   TESTS_QUAR_ACCOUNT_RE_3E3EA4D4

The hash keeps the name stable. The same model always produces the same table name, so a migration applies more than one time without a problem.

Note that _meta.db_table holds the short form. Django truncates the name when it builds the model class, and it asks the default connection for the limit. Thus the value depends on which database is the default one. A print of it on a PostgreSQL workstation shows the long name.

A migration that names a constraint by hand breaks on Oracle. A RunSQL that reads DROP INDEX tests_quarterlyrevenuereconciliation_... finds no such index, because the real name is the truncated form. Before a hand-written RunSQL reaches Oracle, run manage.py sqlmigrate app 0002 against the Oracle settings and copy the name from that output. The names differ per backend, so the name from a PostgreSQL machine is the wrong name.

Read the real names

sqlmigrate shows the SQL for one migration, with the names that this backend produces. Point the command at the Oracle settings.

manage.py sqlmigrate reports 0002 --settings=config.settings.oracle

To read the names that already exist, query the data dictionary.

SELECT index_name FROM user_indexes
 WHERE table_name = 'TESTS_QUARTERLYREVENUERECOCD7E';
SELECT constraint_name FROM user_constraints
 WHERE table_name = 'TESTS_QUARTERLYREVENUERECOCD7E';

Oracle stores an unquoted identifier in upper case, and Django quotes its identifiers. Thus the name in the data dictionary is upper case, and a query against user_indexes needs the upper-case form or an UPPER() call.

Keep the names short instead

Set db_table for a model with a long name, and set name on an explicit Index. A name that is short enough on every backend gives one schema everywhere, and the hash never appears.

class QuarterlyRevenueReconciliationSnapshot(models.Model):
    class Meta:
        db_table = "reports_qtr_recon"
        indexes = [models.Index(fields=["account_reference_identifier"],
                                name="reports_qtr_recon_acct")]

Measured on Oracle Database 23ai Free and PostgreSQL 17, with Django 6.1 and the oracledb driver. The 38-character model name above produced a 30-character table name on Oracle and the full 44-character name on PostgreSQL, where the limit is 63.