Skip to content

Commit 1245e09

Browse files
authored
Merge pull request #11 from replit/bb-array-defaults
Fix parsing of array defaults
2 parents b0f1278 + 9b513d4 commit 1245e09

File tree

4 files changed

+458
-24
lines changed

4 files changed

+458
-24
lines changed

drizzle-kit/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@drizzle-team/drizzle-kit",
3-
"version": "0.31.10",
3+
"version": "0.31.11",
44
"homepage": "https://orm.drizzle.team",
55
"keywords": [
66
"drizzle",
@@ -47,7 +47,8 @@
4747
"@drizzle-team/brocli": "^0.10.2",
4848
"@esbuild-kit/esm-loader": "^2.5.5",
4949
"esbuild": "^0.25.4",
50-
"esbuild-register": "^3.5.0"
50+
"esbuild-register": "^3.5.0",
51+
"postgres-array": "^3.0.4"
5152
},
5253
"devDependencies": {
5354
"@arethetypeswrong/cli": "^0.15.3",

drizzle-kit/src/serializer/pgSerializer.ts

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import type {
4040
UniqueConstraint,
4141
View,
4242
} from '../serializer/pgSchema';
43+
import { parse as parsePostgresArray } from 'postgres-array';
4344
import { type DB, escapeSingleQuotes, isPgArrayType } from '../utils';
4445
import { getColumnCasing, sqlToStr } from './utils';
4546

@@ -1934,7 +1935,91 @@ WHERE
19341935
};
19351936
};
19361937

1937-
const defaultForColumn = (column: any, internals: PgKitInternals, tableName: string) => {
1938+
/**
1939+
* Formats a single array element based on the PostgreSQL column data type.
1940+
* Handles cleaning up quotes and spaces from postgres-array parsed elements,
1941+
* and applies appropriate formatting for different data types.
1942+
*/
1943+
const formatArrayElement = (element: any, dataType: string): string | null => {
1944+
if (element === null) return element;
1945+
1946+
// Remove outer quotes from postgres-array parsed elements if present
1947+
// First trim spaces since postgres-array includes leading spaces in array elements
1948+
let cleanElement = typeof element === 'string' ? element.trim() : element;
1949+
if (typeof cleanElement === 'string' && ((cleanElement.startsWith("'") && cleanElement.endsWith("'")) || (cleanElement.startsWith('"') && cleanElement.endsWith('"')))) {
1950+
cleanElement = cleanElement.slice(1, -1);
1951+
}
1952+
1953+
// remove [] from dataType if it exists
1954+
if (!dataType.endsWith("[]")) {
1955+
throw new Error(`array dataType ${dataType} does not end with '[]'`);
1956+
}
1957+
1958+
const baseDataType = dataType.slice(0, -"[]".length);
1959+
1960+
if (['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(baseDataType)) {
1961+
return cleanElement;
1962+
} else if (dataType.startsWith('timestamp')) {
1963+
return `"${cleanElement}"`;
1964+
} else if (baseDataType === 'interval') {
1965+
return `"${cleanElement.replaceAll('"', `\"`)}"`;
1966+
} else if (baseDataType === 'boolean') {
1967+
return cleanElement === 't' || cleanElement === 'true' ? 'true' : 'false';
1968+
} else if (['json', 'jsonb'].includes(baseDataType)) {
1969+
// For JSON/JSONB arrays, cleanElement is already a JSON string
1970+
// We just need to ensure it's properly quoted
1971+
1972+
// First, try to parse it to validate it's valid JSON
1973+
const parsed = JSON.parse(cleanElement);
1974+
// Then stringify it back to ensure consistent formatting
1975+
return `"${JSON.stringify(parsed).replaceAll('"', '\\"')}"`;
1976+
} else {
1977+
return `"${cleanElement}"`;
1978+
}
1979+
};
1980+
1981+
/**
1982+
* Handles the default value formatting for array columns.
1983+
* Converts various array formats to PostgreSQL array notation and formats elements.
1984+
*/
1985+
const handleArrayDefault = (columnDefaultAsString: string, dataType: string): string => {
1986+
// Handle common simple cases
1987+
if (columnDefaultAsString === '{}' || columnDefaultAsString === "'{}'") {
1988+
return "'{}'";
1989+
} else if (columnDefaultAsString === '{""}' || columnDefaultAsString === "'{\"\"}'") {
1990+
return "'{\"\"}'";
1991+
}
1992+
1993+
// Convert ARRAY constructor syntax to PostgreSQL bracket notation that postgres-array can parse
1994+
let normalizedArrayString = columnDefaultAsString;
1995+
1996+
if (columnDefaultAsString.startsWith('ARRAY[') && columnDefaultAsString.endsWith(']')) {
1997+
// Convert ARRAY['a'::text, 'b', 'c'::varchar] -> {'a', 'b', 'c'}
1998+
const content = columnDefaultAsString.slice(6, -1); // Remove 'ARRAY[' and ']'
1999+
2000+
// Remove type casting from individual elements (::text, ::varchar, etc.)
2001+
const cleanContent = content.replace(/::\w+/g, '');
2002+
normalizedArrayString = `{${cleanContent}}`;
2003+
}
2004+
2005+
// Handle various bracket notation formats to ensure compatibility with postgres-array
2006+
if (normalizedArrayString.startsWith("'{") && normalizedArrayString.endsWith("}'")) {
2007+
normalizedArrayString = normalizedArrayString.slice(1, -1); // Remove outer quotes
2008+
} else if (!normalizedArrayString.startsWith("{") && !normalizedArrayString.startsWith("'") && normalizedArrayString !== '{}') {
2009+
// Handle cases where array string doesn't have proper brackets
2010+
normalizedArrayString = `{${normalizedArrayString}}`;
2011+
}
2012+
2013+
// Use postgres-array library to parse the normalized string
2014+
const parsedArray = [...parsePostgresArray(normalizedArrayString)];
2015+
2016+
// Format elements according to data type
2017+
const formattedElements = parsedArray.map((element) => formatArrayElement(element, dataType));
2018+
2019+
return `'{${formattedElements.join(',')}}'`;
2020+
};
2021+
2022+
export const defaultForColumn = (column: any, internals: PgKitInternals, tableName: string) => {
19382023
const columnName = column.column_name;
19392024
const isArray = internals?.tables[tableName]?.columns[columnName]?.isArray ?? false;
19402025

@@ -1961,27 +2046,7 @@ const defaultForColumn = (column: any, internals: PgKitInternals, tableName: str
19612046
const columnDefaultAsString: string = column.column_default.toString();
19622047

19632048
if (isArray) {
1964-
return `'{${
1965-
columnDefaultAsString
1966-
.slice(2, -2)
1967-
.split(/\s*,\s*/g)
1968-
.map((value) => {
1969-
if (['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(column.data_type.slice(0, -2))) {
1970-
return value;
1971-
} else if (column.data_type.startsWith('timestamp')) {
1972-
return `${value}`;
1973-
} else if (column.data_type.slice(0, -2) === 'interval') {
1974-
return value.replaceAll('"', `\"`);
1975-
} else if (column.data_type.slice(0, -2) === 'boolean') {
1976-
return value === 't' ? 'true' : 'false';
1977-
} else if (['json', 'jsonb'].includes(column.data_type.slice(0, -2))) {
1978-
return JSON.stringify(JSON.stringify(JSON.parse(JSON.parse(value)), null, 0));
1979-
} else {
1980-
return `\"${value}\"`;
1981-
}
1982-
})
1983-
.join(',')
1984-
}}'`;
2049+
return handleArrayDefault(columnDefaultAsString, column.data_type);
19852050
}
19862051

19872052
if (['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(column.data_type)) {

0 commit comments

Comments
 (0)