Skip to content
Merged
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
21 changes: 21 additions & 0 deletions packages/csv-to-pg/__tests__/__snapshots__/export.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ exports[`test case arrays 1`] = `
('450e3b3b-b68d-4abc-990c-65cb8a1dcdb4', '{a,b}');"
`;

exports[`test case empty array fields emit empty array literal 1`] = `
"INSERT INTO metaschema_modules_public.secure_table_provision (
id,
node_type,
fields,
grant_roles
) VALUES
('450e3b3b-b68d-4abc-990c-65cb8a1dcdb4', 'DataTimestamps', '{}', '{}');"
`;

exports[`test case image/attachment 1`] = `
"INSERT INTO metaschema_public.field (
id,
Expand Down Expand Up @@ -61,6 +71,17 @@ exports[`test case jsonb/json 1`] = `
('450e3b3b-b68d-4abc-990c-65cb8a1dcdb4', 'name here', '{"a":1}');"
`;

exports[`test case null array fields emit empty array literal instead of NULL 1`] = `
"INSERT INTO metaschema_modules_public.secure_table_provision (
id,
node_type,
fields,
grant_privileges,
out_fields
) VALUES
('450e3b3b-b68d-4abc-990c-65cb8a1dcdb4', 'DataTimestamps', '{}', '{}', '{}');"
`;

exports[`test case test case 1`] = `Promise {}`;

exports[`test case test case parser 1`] = `
Expand Down
58 changes: 58 additions & 0 deletions packages/csv-to-pg/__tests__/export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,64 @@ it('interval type', async () => {
expect(sql).toMatchSnapshot();
});

it('null array fields emit empty array literal instead of NULL', async () => {
const parser = new Parser({
schema: 'metaschema_modules_public',
singleStmts: true,
table: 'secure_table_provision',
fields: {
id: 'uuid',
node_type: 'text',
fields: 'jsonb[]',
grant_privileges: 'jsonb[]',
out_fields: 'uuid[]'
}
});

const sql = await parser.parse([
{
id: '450e3b3b-b68d-4abc-990c-65cb8a1dcdb4',
node_type: 'DataTimestamps',
fields: null,
grant_privileges: null,
out_fields: null
}
]);

// Should emit '{}' for array columns instead of NULL
expect(sql).toContain("'{}'");
expect(sql).not.toContain('NULL');
expect(sql).toMatchSnapshot();
});

it('empty array fields emit empty array literal', async () => {
const parser = new Parser({
schema: 'metaschema_modules_public',
singleStmts: true,
table: 'secure_table_provision',
fields: {
id: 'uuid',
node_type: 'text',
fields: 'jsonb[]',
grant_roles: 'text[]'
}
});

const sql = await parser.parse([
{
id: '450e3b3b-b68d-4abc-990c-65cb8a1dcdb4',
node_type: 'DataTimestamps',
fields: [],
grant_roles: []
}
]);

// Empty arrays should also emit '{}' not NULL
expect(sql).toContain("'{}'");
expect(sql).not.toContain('NULL');
expect(sql).toMatchSnapshot();
});

it('interval type with string value', async () => {
const parser = new Parser({
schema: 'metaschema_modules_public',
Expand Down
19 changes: 17 additions & 2 deletions packages/csv-to-pg/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,13 @@ const escapeArrayElement = (value: unknown): string => {

/**
* Convert an array to PostgreSQL array literal format with proper escaping.
* Returns '{}' for empty arrays instead of undefined.
*/
const psqlArray = (value: unknown): string | undefined => {
if (Array.isArray(value) && value.length) {
if (Array.isArray(value)) {
if (value.length === 0) {
return '{}';
}
return `{${value.map(escapeArrayElement).join(',')}}`;
}
return undefined;
Expand Down Expand Up @@ -213,12 +217,23 @@ export class ValidationError extends Error {
type CoercionFunc = (record: Record<string, unknown>) => Node;

/**
* Helper to create a NULL node or throw if field is required
* Check if a type is an array type (e.g. 'text[]', 'uuid[]', 'jsonb[]')
*/
const isArrayType = (type: string): boolean => type.endsWith('[]');

/**
* Helper to create a NULL node or throw if field is required.
* For array types, emits an empty array literal '{}' instead of NULL.
*/
const makeNullOrThrow = (fieldName: string, rawValue: unknown, type: string, required: boolean, reason: string): Node => {
if (required) {
throw new ValidationError(fieldName, rawValue, type, reason);
}
if (isArrayType(type)) {
return nodes.aConst({
sval: ast.string({ sval: '{}' })
});
}
return nodes.aConst({ isnull: true });
};

Expand Down
Loading