Skip to content

Commit 5f26da1

Browse files
committed
Wrap SQLite expression defaults in parentheses
SQLite requires expression defaults (e.g. datetime('now')) to be enclosed in parentheses. Without this, drizzle-kit push generates invalid SQL like DEFAULT datetime('now') instead of DEFAULT (datetime('now')), causing SQLITE_ERROR syntax errors during table recreation. Fixes #5634 Made-with: Cursor
1 parent 273c780 commit 5f26da1

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

drizzle-kit/src/sqlgenerator.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ const parseType = (schemaPrefix: string, type: string) => {
148148
: `${schemaPrefix}"${withoutArrayDefinition}"${arrayDefinition}`;
149149
};
150150

151+
// SQLite requires expression defaults (function calls like datetime('now'))
152+
// to be wrapped in parentheses: DEFAULT (datetime('now')).
153+
// Simple literals (strings, numbers, NULL, CURRENT_*) do not need wrapping.
154+
const wrapSqliteExprDefault = (val: unknown): string => {
155+
const s = String(val);
156+
if (typeof val !== 'string') return s;
157+
if (s.startsWith('(') || s.startsWith("'") || s.startsWith('"')) return s;
158+
if (/^-?\d+(\.\d+)?$/.test(s)) return s;
159+
if (/^(NULL|TRUE|FALSE|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP)$/i.test(s)) return s;
160+
return `(${s})`;
161+
};
162+
151163
abstract class Convertor {
152164
abstract can(
153165
statement: JsonStatement,
@@ -677,7 +689,9 @@ export class SQLiteCreateTableConvertor extends Convertor {
677689

678690
const primaryKeyStatement = column.primaryKey ? ' PRIMARY KEY' : '';
679691
const notNullStatement = column.notNull ? ' NOT NULL' : '';
680-
const defaultStatement = column.default !== undefined ? ` DEFAULT ${column.default}` : '';
692+
const defaultStatement = column.default !== undefined
693+
? ` DEFAULT ${wrapSqliteExprDefault(column.default)}`
694+
: '';
681695

682696
const autoincrementStatement = column.autoincrement
683697
? ' AUTOINCREMENT'
@@ -1872,7 +1886,7 @@ export class SQLiteAlterTableAddColumnConvertor extends Convertor {
18721886
const { tableName, column, referenceData } = statement;
18731887
const { name, type, notNull, primaryKey, generated } = column;
18741888

1875-
const defaultStatement = `${column.default !== undefined ? ` DEFAULT ${column.default}` : ''}`;
1889+
const defaultStatement = `${column.default !== undefined ? ` DEFAULT ${wrapSqliteExprDefault(column.default)}` : ''}`;
18761890
const notNullStatement = `${notNull ? ' NOT NULL' : ''}`;
18771891
const primaryKeyStatement = `${primaryKey ? ' PRIMARY KEY' : ''}`;
18781892
const referenceAsObject = referenceData

drizzle-kit/tests/sqlite-columns.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { sql } from 'drizzle-orm';
12
import {
23
AnySQLiteColumn,
34
foreignKey,
@@ -1047,3 +1048,53 @@ test('text default values escape single quotes', async (t) => {
10471048
"ALTER TABLE `table` ADD `text` text DEFAULT 'escape''s quotes';",
10481049
);
10491050
});
1051+
1052+
test('expression defaults wrap in parentheses for create table', async (t) => {
1053+
const schema = {
1054+
events: sqliteTable('events', {
1055+
id: integer('id').primaryKey(),
1056+
createdAt: text('created_at').default(sql`datetime('now')`),
1057+
}),
1058+
};
1059+
1060+
const { sqlStatements } = await diffTestSchemasSqlite({}, schema, []);
1061+
1062+
expect(sqlStatements.length).toBe(1);
1063+
expect(sqlStatements[0]).toContain("DEFAULT (datetime('now'))");
1064+
});
1065+
1066+
test('expression defaults wrap in parentheses for add column', async (t) => {
1067+
const schema1 = {
1068+
events: sqliteTable('events', {
1069+
id: integer('id').primaryKey(),
1070+
}),
1071+
};
1072+
1073+
const schema2 = {
1074+
events: sqliteTable('events', {
1075+
id: integer('id').primaryKey(),
1076+
createdAt: text('created_at').default(sql`datetime('now')`),
1077+
}),
1078+
};
1079+
1080+
const { sqlStatements } = await diffTestSchemasSqlite(schema1, schema2, []);
1081+
1082+
expect(sqlStatements.length).toBe(1);
1083+
expect(sqlStatements[0]).toContain("DEFAULT (datetime('now'))");
1084+
});
1085+
1086+
test('literal defaults not wrapped in extra parentheses', async (t) => {
1087+
const schema = {
1088+
users: sqliteTable('users', {
1089+
id: integer('id').primaryKey(),
1090+
name: text('name').default('hello'),
1091+
active: integer('active', { mode: 'boolean' }).default(true),
1092+
}),
1093+
};
1094+
1095+
const { sqlStatements } = await diffTestSchemasSqlite({}, schema, []);
1096+
1097+
expect(sqlStatements.length).toBe(1);
1098+
expect(sqlStatements[0]).toContain("DEFAULT 'hello'");
1099+
expect(sqlStatements[0]).toContain('DEFAULT true');
1100+
});

0 commit comments

Comments
 (0)