Opened 15 years ago
Last modified 10 years ago
#12529 new Bug
manage.py syncdb doesn't check tables by using mangled names with Oracle backend
Reported by: | Jani Tiainen | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | syncdb oracle inspectdb |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
With Oracle syncdb doesn't use mangled names when comparing tablenames to ones existing in database thus trying to create tables that already exists. This leads to
Creating table ssp_service_category_translation Traceback (most recent call last): File "./manage.py", line 19, in <module> execute_manager(settings) File "../../django-trunk/django/core/management/__init__.py", line 439, in execute_manager utility.execute() File "../../django-trunk/django/core/management/__init__.py", line 380, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "../../django-trunk/django/core/management/base.py", line 195, in run_from_argv self.execute(*args, **options.__dict__) File "../../django-trunk/django/core/management/base.py", line 222, in execute output = self.handle(*args, **options) File "../../django-trunk/django/core/management/base.py", line 351, in handle return self.handle_noargs(**options) File "../../django-trunk/django/core/management/commands/syncdb.py", line 91, in handle_noargs cursor.execute(statement) File "../../django-trunk/django/db/backends/util.py", line 19, in execute return self.cursor.execute(sql, params) File "../../django-trunk/django/db/backends/oracle/base.py", line 487, in execute raise e cx_Oracle.DatabaseError: ORA-00955: name is already used by an existing object
Root cause for this is using connection.introspection.table_name_converter() in syncdb main loop which in case of oracle returns just lowercase name and not properly mangled name that would match existing table in database.
Change History (10)
comment:1 by , 15 years ago
comment:2 by , 15 years ago
It seems that django/db/models/options.py around L105 uses tablename truncate only when table name is not provided.
I think truncate should happen regardless is it provided or not to make models work regardless of DB.
Currently this only is Oracle problem since it's only db backend that has limitation on names.
comment:3 by , 15 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:4 by , 14 years ago
Component: | Uncategorized → Database layer (models, ORM) |
---|
comment:5 by , 14 years ago
Severity: | → Normal |
---|---|
Type: | → Bug |
comment:6 by , 13 years ago
Easy pickings: | unset |
---|---|
Keywords: | inspectdb added |
UI/UX: | unset |
comment:7 by , 11 years ago
Hi, I've fixed this by changing django.db.backends.oracle.introspection.py
from:
def table_name_converter(self, name): "Table name comparison is case insensitive under Oracle" return name.lower()
to:
from django.db.backends import util
def table_name_converter(self, name): "Table name comparison is case insensitive under Oracle" max_length = self.connection.ops.max_name_length() truncated = util.truncate_name(name.upper(), max_length) return truncated.lower()
Is there any problem in this solution?
comment:8 by , 10 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
With the introduction of the new migrations framework in Django 1.7, this issue doesn't exist anymore.
comment:9 by , 10 years ago
Hi, I can still reproduce this using Django 1.7 and 1.7.1.
As an example, let's use django-cas' table "django_cas_session_service_ticket". This also happens with easy_thumbails' table "easy_thumbnails_thumbnaildimensions".
The flow is like this:
- migrate command is called, eventually migrate.py:model_installed() is called:
def model_installed(model): opts = model._meta converter = connection.introspection.table_name_converter # Note that if a model is unmanaged we short-circuit and never try to install it return not ((converter(opts.db_table) in tables) or (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
- here, the table name converter will just return name.lower(), which is clearly NOT the name that was used to create the table.
- at creation time, the Oracle DatabaseOperations (oracle/base.py) class will truncate the name:
def quote_name(self, name): # SQL92 requires delimited (quoted) names to be case-sensitive. When # not quoted, Oracle has case-insensitive behavior for identifiers, but # always defaults to uppercase. # We simplify things by making Oracle identifiers always uppercase. if not name.startswith('"') and not name.endswith('"'): name = '"%s"' % backend_utils.truncate_name(name.upper(), self.max_name_length()) # Oracle puts the query text into a (query % args) construct, so % signs # in names need to be escaped. The '%%' will be collapsed back to '%' at # that stage so we aren't really making the name longer here. name = name.replace('%', '%%') return name.upper()
- the check for table existance "(converter(opts.db_table) in tables)" will fail, as tables array contains "django_cas_session_service1144" and not "django_cas_session_service_ticket"
- with these changes in oracle/introspection.py everything works as expected. imports included in function for easier testing:
def table_name_converter(self, name): "Table name comparison is case insensitive under Oracle" from django.db.backends import utils as backend_utils from django.db.backends.oracle.base import DatabaseOperations self.ops = DatabaseOperations(self) name = backend_utils.truncate_name(name.upper(), self.ops.max_name_length()) return name.lower()
comment:10 by , 10 years ago
Resolution: | fixed |
---|---|
Status: | closed → new |
This happens only when model has db_table defined in Meta:
Note: This also happens in pre-multidb codebase.