drizzle-team / drizzle-team/drizzle-orm
[BUG]: drizzle-kit v1 drops NOT NULL from SQLite TEXT primary keys — but SQLite allows NULL in non-INTEGER PRIMARY KEY columns (regression vs 0.31)
- Dominant language
- TypeScript
- Stars
- 35.8k
- Forks
- 1.6k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 4
Description
### Report hasn't been filed before.
- [x] I have verified that the bug I'm about to report hasn't been filed before.
### What version of `drizzle-orm` are you using?
1.0.0-rc.4
### What version of `drizzle-kit` are you using?
1.0.0-rc.4
### Other packages
_No response_
### Describe the Bug
**What is the undesired behavior?**
For SQLite, drizzle-kit v1 no longer emits `NOT NULL` on primary-key columns — even when `.notNull()` is explicit in the schema:
```ts
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: text("id").primaryKey(), // e.g. a UUID PK
// id: text("id").primaryKey().notNull() — same result
});
```
`drizzle-kit generate` (1.0.0-rc.4) produces:
```sql
CREATE TABLE `users` (
`id` text PRIMARY KEY
);
```
drizzle-kit 0.31.x produced `` `id` text PRIMARY KEY NOT NULL `` for the same schema.
This looks like an intentional "PRIMARY KEY implies NOT NULL" normalization — the v1 SQLite serializer drops the flag at snapshot level:
```js
notNull: column.notNull && !primaryKey,
```
That assumption is correct for standard SQL, but **not for SQLite**. Due to a long-standing documented quirk, SQLite allows NULLs in PRIMARY KEY columns unless the column is `INTEGER PRIMARY KEY`, or the table is `WITHOUT ROWID` or `STRICT` ([SQLite docs, "The PRIMARY KEY"](https://www.sqlite.org/lang_createtable.html#the_primary_key)):
> According to the SQL standard, PRIMARY KEY should always imply NOT NULL. Unfortunately, due to a bug in some early versions, this is not the case in SQLite. […] NULL values are considered distinct from all other values, including other NULLs.
So with the generated DDL above, this succeeds silently:
```sql
INSERT INTO users (id) VALUES (NULL); -- accepted!
INSERT INTO users (id) VALUES (NULL); -- accepted again — NULLs are pairwise distinct
```
A table with a TEXT (e.g. UUID) primary key can now accumulate multiple NULL "primary keys". That is a silent data-integrity regression for every SQLite/Turso/D1 project using non-integer PKs.
**What are the steps to reproduce it?**
1. `pnpm add drizzle-orm@1.0.0-rc.4 drizzle-kit@1.0.0-rc.4`
2. Schema as above (`text().primaryKey()`), `dialect: "sqlite"` (also reproduces with `"turso"`)
3. `npx drizzle-kit generate` → migration SQL contains `` `id` text PRIMARY KEY `` with no `NOT NULL`
4. Apply it, then `INSERT INTO users (id) VALUES (NULL);` → row is inserted
**What is the desired result?**
- Keep emitting `NOT NULL` for SQLite primary-key columns that are not `INTEGER PRIMARY KEY` (rowid alias) — i.e. restore the 0.31 behavior for TEXT/BLOB/REAL PKs
- At the very least, an explicit `.notNull()` in the schema should never be discarded
Related: #2611 asked to drop the *redundant* `NOT NULL` for `integer().primaryKey()` (where SQLite really does enforce non-NULL via the rowid alias). The v1 behavior looks like that request applied to *all* SQLite PK columns, which overshoots — for non-INTEGER PKs the `NOT NULL` was load-bearing.
**Workaround**: hand-edit each generated migration to add `NOT NULL` back to text PK columns (the snapshot normalizes both sides the same way, so the manual patch doesn't cause spurious diffs on subsequent generates).
**Environment**: Turso (SQLite) with TEXT UUID primary keys; Node.js 22; pnpm monorepo.
Contributor guide
Research direction
Reproduce the issue with the provided text primary-key schema and run drizzle-kit generate for SQLite. Inspect the v1 SQLite serializer at the shown notNull expression, then verify that generated SQL retains NOT NULL for non-INTEGER primary keys and explicit .notNull() declarations without changing INTEGER PRIMARY KEY behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, sqlite, typescript
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100