#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:
Nonename of constraint'columns': []for some constraints (get bySELECT sql FROM sqlite_masterparsing)- duplicates of constraints from different places (get by
SELECT sql FROM sqlite_masterparsing 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 , 7 years ago
| Cc: | added |
|---|
comment:2 by , 7 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 , 7 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_listand 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 , 7 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 , 7 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.