The SQLite dialect recovers constraint names by regexp-parsing the stored
CREATE TABLE text, since the PRAGMA calls don't report them. The patterns
for PRIMARY KEY, UNIQUE and FOREIGN KEY in dialects/sqlite/base.py require
CONSTRAINT, the name, and the following keyword to be separated by literal
spaces only:
PK_PATTERN = r'CONSTRAINT +(?:"(.+?)"|(\w+)) +PRIMARY KEY'
r'(?:CONSTRAINT +(?:"(.+?)"|(\w+)) +)?UNIQUE *\((.+?)\)'
FK_PATTERN = (
r'(?:CONSTRAINT +(?:"(.+?)"|(\w+)) +)?'
r"FOREIGN KEY *\( *(.+?) *\) +"
...
)
+ following a literal space matches more spaces but not a newline. SQLite
stores DDL exactly as it was typed, so a table created with the constraint
name on its own line reflects with name: None, silently.
The CHECK constraint pattern in the same module already uses \s+ and is not
affected.
Repro
import sqlalchemy as sa
e = sa.create_engine("sqlite://")
with e.begin() as c:
c.exec_driver_sql("CREATE TABLE a (id INTEGER PRIMARY KEY, x INTEGER)")
c.exec_driver_sql("""CREATE TABLE t (
id INTEGER NOT NULL,
x INTEGER,
CONSTRAINT my_pk
PRIMARY KEY (id),
CONSTRAINT my_uq
UNIQUE (x),
CONSTRAINT my_ck
CHECK (x > 0),
CONSTRAINT my_fk
FOREIGN KEY (x) REFERENCES a (x)
)""")
insp = sa.inspect(c)
print("pk :", insp.get_pk_constraint("t")["name"])
print("unique:", [u["name"] for u in insp.get_unique_constraints("t")])
print("check :", [u["name"] for u in insp.get_check_constraints("t")])
print("fk :", [u["name"] for u in insp.get_foreign_keys("t")])
Output:
pk : None
unique: [None]
check : ['my_ck']
fk : [None]
Expected my_pk, my_uq, my_ck, my_fk throughout.
Putting each CONSTRAINT <name> on the same line as its keyword — which is
what SQLAlchemy's own DDL compiler emits — reflects correctly, so this only
affects tables created outside of SQLAlchemy or via exec_driver_sql() with
hand-written DDL.
The fix looks like replacing the literal-space + runs in these three
patterns with \s+, matching what the CHECK pattern already does.
The SQLite dialect recovers constraint names by regexp-parsing the stored
CREATE TABLEtext, since thePRAGMAcalls don't report them. The patternsfor PRIMARY KEY, UNIQUE and FOREIGN KEY in
dialects/sqlite/base.pyrequireCONSTRAINT, the name, and the following keyword to be separated by literalspaces only:
+following a literal space matches more spaces but not a newline. SQLitestores DDL exactly as it was typed, so a table created with the constraint
name on its own line reflects with
name: None, silently.The CHECK constraint pattern in the same module already uses
\s+and is notaffected.
Repro
Output:
Expected
my_pk,my_uq,my_ck,my_fkthroughout.Putting each
CONSTRAINT <name>on the same line as its keyword — which iswhat SQLAlchemy's own DDL compiler emits — reflects correctly, so this only
affects tables created outside of SQLAlchemy or via
exec_driver_sql()withhand-written DDL.
The fix looks like replacing the literal-space
+runs in these threepatterns with
\s+, matching what the CHECK pattern already does.