Django MSSQL:覆盖外键约束名称生成
Django MSSQL: Override Foreign Key Constraint Name Generation
我正在尝试使用 django-mssql-backend 在 Django 3.0 中创建一个数据库模型,作为 SQL Server 2019 的数据库后端。该数据库对 table 中包含的 table 使用多个模式其中一些 table 是非托管的(已经存在),而其他的是通过迁移从头开始创建的。为了使多个模式正常工作,我使用了我在此处的另一个答案中发现的技巧,建议按如下方式格式化 tablename:
class MyModel(models.Model):
...
class Meta:
db_table = 'schema].[table'
这是为了使 SQL 编译时在外部自动形成的包裹方括号完成 schema/table 定义。这个问题是 ForeignKey 对象使用这个 table 名称生成它们的约束名称,这会导致出现无效的约束名称,导致一旦 tables 创建完成并且需要要创建的约束。\
它们是这样生成的:
[schema1].[table1_colname_id_bc165567_fk2_schema2].[table_colname]
有没有办法覆盖这种行为?如果这可以通过分叉后端并添加手动编译代码来覆盖,我将如何去做呢?否则,我如何在使用多个模式并充分利用 Django 附带的 ORM/migrations 的同时在我的模型中使用外键?
我终于搞清楚了。在分叉 django-mssql-backend 之后,我覆盖了 sql_server.pyodbc.schema
模块中 DatabaseSchemaEditor
class 中的 _create_index_name
和 _fk_constraint_name
方法,改变了 table 命名为 split('[')[-1]
,它省略了字符串的模式 hack 部分和额外的左括号,但不应干扰未应用该 hack 的 tables。
这是重写的方法
(大部分代码与 django.db.backends.base.schema 中的原始实现相同):
def _create_index_name(self, table_name, column_names, suffix=""):
"""
Generate a unique name for an index/unique constraint.
The name is divided into 3 parts: the table name, the column names,
and a unique digest and suffix.
"""
# CHANGE HERE (table_name to table_name.split('[')[-1]
_, table_name = split_identifier(table_name.split('[')[-1])
hash_suffix_part = '%s%s' % (names_digest(table_name, *column_names, length=8), suffix)
max_length = self.connection.ops.max_name_length() or 200
# If everything fits into max_length, use that name.
index_name = '%s_%s_%s' % (table_name, '_'.join(column_names), hash_suffix_part)
if len(index_name) <= max_length:
return index_name
# Shorten a long suffix.
if len(hash_suffix_part) > max_length / 3:
hash_suffix_part = hash_suffix_part[:max_length // 3]
other_length = (max_length - len(hash_suffix_part)) // 2 - 1
index_name = '%s_%s_%s' % (
table_name[:other_length],
'_'.join(column_names)[:other_length],
hash_suffix_part,
)
# Prepend D if needed to prevent the name from starting with an
# underscore or a number (not permitted on Oracle).
if index_name[0] == "_" or index_name[0].isdigit():
index_name = "D%s" % index_name[:-1]
return index_name
def _fk_constraint_name(self, model, field, suffix):
def create_fk_name(*args, **kwargs):
return self.quote_name(self._create_index_name(*args, **kwargs))
return ForeignKeyName(
model._meta.db_table.split('[')[-1],
[field.column],
# CHANGE HERE (db_table to db_table.split('[')[-1]
split_identifier(field.target_field.model._meta.db_table.split('[')[-1])[1],
[field.target_field.column],
suffix,
create_fk_name,
)
如果 Django 的未来版本继续不支持多个模式,希望这对将来遇到类似问题的其他人有用。
编辑:我已经在 django-mssql-backend repo 上创建了一个 pull request 来合并这些更改,所以如果这个问题的上下文得到批准,将来对人们来说可能不是问题。
我正在尝试使用 django-mssql-backend 在 Django 3.0 中创建一个数据库模型,作为 SQL Server 2019 的数据库后端。该数据库对 table 中包含的 table 使用多个模式其中一些 table 是非托管的(已经存在),而其他的是通过迁移从头开始创建的。为了使多个模式正常工作,我使用了我在此处的另一个答案中发现的技巧,建议按如下方式格式化 tablename:
class MyModel(models.Model):
...
class Meta:
db_table = 'schema].[table'
这是为了使 SQL 编译时在外部自动形成的包裹方括号完成 schema/table 定义。这个问题是 ForeignKey 对象使用这个 table 名称生成它们的约束名称,这会导致出现无效的约束名称,导致一旦 tables 创建完成并且需要要创建的约束。\
它们是这样生成的:
[schema1].[table1_colname_id_bc165567_fk2_schema2].[table_colname]
有没有办法覆盖这种行为?如果这可以通过分叉后端并添加手动编译代码来覆盖,我将如何去做呢?否则,我如何在使用多个模式并充分利用 Django 附带的 ORM/migrations 的同时在我的模型中使用外键?
我终于搞清楚了。在分叉 django-mssql-backend 之后,我覆盖了 sql_server.pyodbc.schema
模块中 DatabaseSchemaEditor
class 中的 _create_index_name
和 _fk_constraint_name
方法,改变了 table 命名为 split('[')[-1]
,它省略了字符串的模式 hack 部分和额外的左括号,但不应干扰未应用该 hack 的 tables。
这是重写的方法
(大部分代码与 django.db.backends.base.schema 中的原始实现相同):
def _create_index_name(self, table_name, column_names, suffix=""):
"""
Generate a unique name for an index/unique constraint.
The name is divided into 3 parts: the table name, the column names,
and a unique digest and suffix.
"""
# CHANGE HERE (table_name to table_name.split('[')[-1]
_, table_name = split_identifier(table_name.split('[')[-1])
hash_suffix_part = '%s%s' % (names_digest(table_name, *column_names, length=8), suffix)
max_length = self.connection.ops.max_name_length() or 200
# If everything fits into max_length, use that name.
index_name = '%s_%s_%s' % (table_name, '_'.join(column_names), hash_suffix_part)
if len(index_name) <= max_length:
return index_name
# Shorten a long suffix.
if len(hash_suffix_part) > max_length / 3:
hash_suffix_part = hash_suffix_part[:max_length // 3]
other_length = (max_length - len(hash_suffix_part)) // 2 - 1
index_name = '%s_%s_%s' % (
table_name[:other_length],
'_'.join(column_names)[:other_length],
hash_suffix_part,
)
# Prepend D if needed to prevent the name from starting with an
# underscore or a number (not permitted on Oracle).
if index_name[0] == "_" or index_name[0].isdigit():
index_name = "D%s" % index_name[:-1]
return index_name
def _fk_constraint_name(self, model, field, suffix):
def create_fk_name(*args, **kwargs):
return self.quote_name(self._create_index_name(*args, **kwargs))
return ForeignKeyName(
model._meta.db_table.split('[')[-1],
[field.column],
# CHANGE HERE (db_table to db_table.split('[')[-1]
split_identifier(field.target_field.model._meta.db_table.split('[')[-1])[1],
[field.target_field.column],
suffix,
create_fk_name,
)
如果 Django 的未来版本继续不支持多个模式,希望这对将来遇到类似问题的其他人有用。
编辑:我已经在 django-mssql-backend repo 上创建了一个 pull request 来合并这些更改,所以如果这个问题的上下文得到批准,将来对人们来说可能不是问题。