diff --git a/drizzle-kit/src/dialects/sqlite/grammar.ts b/drizzle-kit/src/dialects/sqlite/grammar.ts index 560a07fcf3..fd85244855 100644 --- a/drizzle-kit/src/dialects/sqlite/grammar.ts +++ b/drizzle-kit/src/dialects/sqlite/grammar.ts @@ -97,6 +97,8 @@ const intAffinities = [ 'unsigned big int', 'int2', 'int8', + 'boolean', + 'bool', ]; export const Int: SqlType<'timestamp' | 'timestamp_ms'> = { @@ -122,16 +124,35 @@ export const Int: SqlType<'timestamp' | 'timestamp_ms'> = { }, defaultFromIntrospect: (value) => { const it = trimChar(value, "'"); + const lower = it.toLowerCase(); + if (lower === 'true' || lower === 'false') return lower; const check = Number(it); if (Number.isNaN(check)) return value; // unknown if (check >= Number.MIN_SAFE_INTEGER && check <= Number.MAX_SAFE_INTEGER) return it; return it; // bigint }, - toTs: (value) => { + toTs: (value, type) => { + const isBool = type?.toLowerCase() === 'boolean' || type?.toLowerCase() === 'bool'; + if (isBool) { + let def = ''; + if (value !== undefined && value !== null && value !== '') { + const lower = String(value).toLowerCase().trim(); + if (lower === 'true' || lower === '1') { + def = 'true'; + } else if (lower === 'false' || lower === '0') { + def = 'false'; + } else { + def = `sql\`${value}\``; + } + } + return { def, options: { mode: 'boolean' } }; + } + if (!value) return ''; - if (value === 'true' || value === 'false') { - return { def: value, options: { mode: 'boolean' } }; + const lower = String(value).toLowerCase().trim(); + if (lower === 'true' || lower === 'false') { + return { def: lower, options: { mode: 'boolean' } }; } const check = Number(value); @@ -169,7 +190,6 @@ export const Real: SqlType = { const numericAffinities = [ 'numeric', 'decimal', - 'boolean', 'date', 'datetime', ]; @@ -399,9 +419,15 @@ export function sqlTypeFrom(sqlType: string): string { return 'real'; } + if ( + ['boolean', 'bool'].some((it) => lowered.startsWith(it)) + ) { + return 'boolean'; + } + // https://www.sqlite.org/datatype3.html -> 3.1.1. Affinity Name Examples if ( - ['numeric', 'decimal', 'boolean', 'date', 'datetime'].some((it) => lowered.startsWith(it)) + ['numeric', 'decimal', 'date', 'datetime'].some((it) => lowered.startsWith(it)) ) { return 'numeric'; } diff --git a/drizzle-kit/tests/sqlite/grammar.test.ts b/drizzle-kit/tests/sqlite/grammar.test.ts index c33eed283d..f6e2fa4d7a 100644 --- a/drizzle-kit/tests/sqlite/grammar.test.ts +++ b/drizzle-kit/tests/sqlite/grammar.test.ts @@ -1,4 +1,4 @@ -import { parseSqliteDdl, parseTableSQL, parseViewSQL, stripSqlComments } from 'src/dialects/sqlite/grammar'; +import { parseDefault, parseSqliteDdl, parseTableSQL, parseViewSQL, sqlTypeFrom, stripSqlComments, typeFor } from 'src/dialects/sqlite/grammar'; import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest'; import { prepareTestDatabase, TestDatabase } from './mocks'; @@ -262,3 +262,53 @@ describe('parse ddl', (t) => { }); }); }); + +describe('sqlite boolean column scaffolding and defaults (issue #6182)', () => { + test('sqlTypeFrom maps BOOLEAN and bool to boolean', () => { + expect(sqlTypeFrom('BOOLEAN')).toBe('boolean'); + expect(sqlTypeFrom('boolean')).toBe('boolean'); + expect(sqlTypeFrom('BOOL')).toBe('boolean'); + expect(sqlTypeFrom('bool')).toBe('boolean'); + }); + + test('typeFor boolean returns integer import with boolean mode options', () => { + const grammarType = typeFor('boolean'); + expect(grammarType.drizzleImport()).toBe('integer'); + + expect(grammarType.toTs('true', 'boolean')).toStrictEqual({ + def: 'true', + options: { mode: 'boolean' }, + }); + expect(grammarType.toTs('false', 'boolean')).toStrictEqual({ + def: 'false', + options: { mode: 'boolean' }, + }); + expect(grammarType.toTs('TRUE', 'boolean')).toStrictEqual({ + def: 'true', + options: { mode: 'boolean' }, + }); + expect(grammarType.toTs('FALSE', 'boolean')).toStrictEqual({ + def: 'false', + options: { mode: 'boolean' }, + }); + expect(grammarType.toTs('1', 'boolean')).toStrictEqual({ + def: 'true', + options: { mode: 'boolean' }, + }); + expect(grammarType.toTs('0', 'boolean')).toStrictEqual({ + def: 'false', + options: { mode: 'boolean' }, + }); + expect(grammarType.toTs(null, 'boolean')).toStrictEqual({ + def: '', + options: { mode: 'boolean' }, + }); + }); + + test('parseDefault normalizes boolean literals from introspection', () => { + expect(parseDefault('BOOLEAN', 'true')).toBe('true'); + expect(parseDefault('BOOLEAN', 'TRUE')).toBe('true'); + expect(parseDefault('BOOLEAN', 'false')).toBe('false'); + expect(parseDefault('BOOLEAN', 'FALSE')).toBe('false'); + }); +});