#30183 closed Bug (fixed)
SQLite instrospection.get_constraints() for unique constraints return wrong count of constraints and doesn't have columns for all items
Reported by: | Pavel Tyslacki | Owned by: | Pavel Tyslacki |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Simon Charette | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Examples below has next issues:
None
name of constraint'columns': []
for some constraints (get bySELECT sql FROM sqlite_master
parsing)- duplicates of constraints from different places (get by
SELECT sql FROM sqlite_master
parsing and from idexes) - ignoring of some constraints when it created for same columns
This issue blocking fixing #30172 for sqlite, because migrations can use introspection for constraint detection and changing.
cursor.execute(""" CREATE TABLE "test1" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL, "email" varchar(255) NOT NULL ) """) print(connection.introspection.get_constraints(cursor, 'test1')) # '__primary__': {'columns': ['id'], 'primary_key': True, 'unique': False, 'foreign_key': False, 'check': False, 'index': False}
cursor.execute(""" CREATE TABLE "test2" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL UNIQUE, "email" varchar(255) NOT NULL ) """) print(connection.introspection.get_constraints(cursor, 'test2')) # None: {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False} # 'sqlite_autoindex_test2_1': {'columns': ['name'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True} # '__primary__': {'columns': ['id'], 'primary_key': True, 'unique': False, 'foreign_key': False, 'check': False, 'index': False}
cursor.execute(""" CREATE TABLE "test3" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL, "email" varchar(255) NOT NULL, CONSTRAINT "name_uniq" UNIQUE ("name") ) """) print(connection.introspection.get_constraints(cursor, 'test3')) # 'name_uniq': {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'sqlite_autoindex_test3_1': {'columns': ['name'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # '__primary__': {'columns': ['id'], 'primary_key': True, 'unique': False, 'foreign_key': False, 'check': False, 'index': False}
cursor.execute(""" CREATE TABLE "test4" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL UNIQUE, "email" varchar(255) NOT NULL, CONSTRAINT "name_uniq" UNIQUE ("name") ) """) print(connection.introspection.get_constraints(cursor, 'test4')) # None: {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'name_uniq': {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'sqlite_autoindex_test4_1': {'columns': ['name'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # '__primary__': {'columns': ['id'], 'primary_key': True, 'unique': False, 'foreign_key': False, 'check': False, 'index': False}
cursor.execute(""" CREATE TABLE "test5" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL UNIQUE, "email" varchar(255) NOT NULL, CONSTRAINT "name_uniq" UNIQUE ("name") ) """) cursor.execute(""" CREATE UNIQUE INDEX "test5_uniq" ON "test5" ("name") """) print(connection.introspection.get_constraints(cursor, 'test5')) # None: {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'name_uniq': {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'test5_uniq': {'columns': ['name'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # 'sqlite_autoindex_test5_1': {'columns': ['name'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # '__primary__': {'columns': ['id'], 'primary_key': True, 'unique': False, 'foreign_key': False, 'check': False, 'index': False}
cursor.execute(""" CREATE TABLE "test6" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL UNIQUE, "email" varchar(255) NOT NULL, CONSTRAINT "name_uniq" UNIQUE ("email") ) """) cursor.execute(""" CREATE UNIQUE INDEX "test6_uniq" ON "test6" ("name", "email") """) print(connection.introspection.get_constraints(cursor, 'test6')) # None: {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'name_uniq': {'unique': True, 'columns': [], 'primary_key': False, 'foreign_key': False, 'check': False, 'index': False}, # 'test6_uniq': {'columns': ['name', 'email'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # 'sqlite_autoindex_test6_2': {'columns': ['email'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # 'sqlite_autoindex_test6_1': {'columns': ['name'], 'primary_key': False, 'unique': True, 'foreign_key': False, 'check': False, 'index': True}, # '__primary__': {'columns': ['id'], 'primary_key': True, 'unique': False, 'foreign_key': False, 'check': False, 'index': False}
Change History (12)
comment:1 by , 6 years ago
Cc: | added |
---|
comment:2 by , 6 years ago
Hello Pavel, thanks for tackling #30172.
I'm pretty sure it's possible to adjust the SQLite constraint introspection logic to appropriately detect inline UNIQUE
constraint which seems to be the issue here. The current logic assumes CONSTRAINT
to appear before which is not always the case as you've come to discover.
comment:3 by , 6 years ago
Yep, I'm a bit investigating it. For now look like for inline constraints (CHECK and UNIQUE) detection you should use table definition parsing, indexes created via CREATE INDEX
can use current logic.
Just describe why only table definition parsing should be used for inline UNIQUE constraint:
- both named and unnamed (UNIQUE in field definition) has different name within
index_list
and you can match this indexes only (as I see) with fields comparison - two inline UNIQUE constraints with same fields will be represented as one index in
index_list
- there are lack for ASC/DESC ordering detecting
- you cannot delete indexes created as inline constraints via
DROP INDEX
I have prototype of table definition parsing so hope I'll can finish fix soon.
comment:4 by , 6 years ago
Component: | Migrations → Database layer (models, ORM) |
---|---|
Summary: | Sqlite instrospection.get_constraints() for unique constraints return wrong count of constraints and doesn't have columns for all items → SQLite instrospection.get_constraints() for unique constraints return wrong count of constraints and doesn't have columns for all items |
Triage Stage: | Unreviewed → Accepted |
comment:5 by , 6 years ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
SQLite introspection was improved in dba4a634ba999bf376caee193b3378bc0b730bd4 but there are still limitations. Simon might be able to advise if any of these issues are fixable or if another approach (similar to how it's often required to rebuild the entire table for schema changes) is required to solve #30172 for SQLite.