Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/drizzle-kit/0.32.4.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
### PostgreSQL bug fixes

Added columns are now created before primary keys that use them. Unique constraints and indexes are now created before dependent foreign keys.

PostgreSQL introspection now preserves JSON and JSONB expression defaults instead of parsing them as JSON literals.
8 changes: 8 additions & 0 deletions drizzle-kit/src/introspect-pg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,10 @@ const mapDefault = (
}

if (lowered.startsWith('jsonb')) {
if (isExpression) {
return `.default(${mapColumnDefault(defaultValue, true)})`;
}

const def = typeof defaultValue !== 'undefined'
? defaultValue.replace(/::(.*?)(?<![^\w"])(?=$)/, '').slice(1, -1)
: null;
Expand All @@ -783,6 +787,10 @@ const mapDefault = (
}

if (lowered.startsWith('json')) {
if (isExpression) {
return `.default(${mapColumnDefault(defaultValue, true)})`;
}

const def = defaultValue ? defaultValue.replace(/::(.*?)(?<![^\w"])(?=$)/, '').slice(1, -1) : null;

return typeof defaultValue !== 'undefined' ? `.default(${def})` : '';
Expand Down
21 changes: 21 additions & 0 deletions drizzle-kit/src/serializer/pgSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,7 @@ export const defaultForColumn = (column: any, internals: PgKitInternals, tableNa
) {
return undefined;
}
const originalColumnDefault = column.column_default.toString();

if (column.column_default.endsWith('[]')) {
column.column_default = column.column_default.slice(0, -2);
Expand Down Expand Up @@ -2082,6 +2083,26 @@ export const defaultForColumn = (column: any, internals: PgKitInternals, tableNa
// if numeric(1,1) and used '99' -> psql stores like '99'::numeric
return columnDefaultAsString.includes("'") ? columnDefaultAsString : `'${columnDefaultAsString}'`;
} else if (column.data_type === 'json' || column.data_type === 'jsonb') {
if (!columnDefaultAsString.startsWith("'") || !columnDefaultAsString.endsWith("'")) {
if (typeof internals!.tables![tableName] === 'undefined') {
internals!.tables![tableName] = {
columns: {
[columnName]: {
isDefaultAnExpression: true,
},
},
};
} else if (typeof internals!.tables![tableName]!.columns[columnName] === 'undefined') {
internals!.tables![tableName]!.columns[columnName] = {
isDefaultAnExpression: true,
};
} else {
internals!.tables![tableName]!.columns[columnName]!.isDefaultAnExpression = true;
}

return originalColumnDefault;
}

const jsonWithoutSpaces = JSON.stringify(JSON.parse(columnDefaultAsString.slice(1, -1)));
return `'${jsonWithoutSpaces}'::${column.data_type}`;
} else if (column.data_type === 'boolean') {
Expand Down
61 changes: 61 additions & 0 deletions drizzle-kit/tests/pg-json-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from 'vitest';
import { introspectPgDB } from '../src/api';
import type { DrizzlePgDB } from '../src/api';
import { schemaToTypeScript } from '../src/introspect-pg';
import { fromDatabase } from '../src/serializer/pgSerializer';

const columnRow = {
table_name: 'items',
is_nullable: 'YES',
array_dimensions: 0,
data_type: 'jsonb',
seq_name: null,
additional_dt: 'jsonb',
enum_name: 'jsonb',
is_generated: 'NEVER',
generation_expression: null,
is_identity: 'NO',
identity_generation: null,
identity_start: null,
identity_increment: null,
identity_maximum: null,
identity_minimum: null,
identity_cycle: 'NO',
type_schema: 'pg_catalog',
};

function databaseWithDefault(columnDefault: string): DrizzlePgDB {
const query = async (sql: string): Promise<Record<string, unknown>[]> => {
if (sql.includes("c.relkind IN ('r', 'v', 'm')")) {
return [{ table_schema: 'public', table_name: 'items', type: 'table', rls_enabled: false }];
}
if (sql.includes('a.attndims AS array_dimensions')) {
return [{ ...columnRow, column_name: 'payload', column_default: columnDefault }];
}

return [];
};

return { query } as DrizzlePgDB;
}

test('introspects a jsonb expression default without parsing it as JSON', async () => {
const expression = "jsonb_build_object('kind'::text, 'record'::text)";
const schema = await introspectPgDB(databaseWithDefault(expression), [], ['public']);

expect(schema.tables['public.items']!.columns.payload!.default).toBe(expression);
});

test('normalizes a quoted jsonb literal default', async () => {
const schema = await introspectPgDB(databaseWithDefault(`'{"kind": "record"}'::jsonb`), [], ['public']);

expect(schema.tables['public.items']!.columns.payload!.default).toBe(`'{"kind":"record"}'::jsonb`);
});

test('preserves casts and generates SQL for jsonb expression defaults', async () => {
const expression = "current_setting('app.payload')::jsonb";
const schema = await fromDatabase(databaseWithDefault(expression), () => true, ['public']);

expect(schema.tables['public.items']!.columns.payload!.default).toBe(expression);
expect(schemaToTypeScript(schema, 'preserve').file).toContain(`.default(sql\`${expression}\`)`);
});
Loading