Files
payload/packages/db-postgres/src/insertArrays.ts

117 lines
3.7 KiB
TypeScript

/* eslint-disable no-param-reassign */
import { PostgresAdapter } from './types';
import { ArrayRowToInsert } from './transform/write/types';
type Args = {
adapter: PostgresAdapter
arrays: {
[tableName: string]: ArrayRowToInsert[]
}[]
parentRows: Record<string, unknown>[]
}
type RowsByTable = {
[tableName: string]: {
rows: Record<string, unknown>[]
locales: Record<string, unknown>[]
columnName: string
rowIndexMap: [number, number][]
arrays: {
[tableName: string]: ArrayRowToInsert[]
}[]
}
}
// We want to insert ALL array rows per table with a single insertion
// rather than inserting each array row separately.
// To do this, we take in an array of arrays by table name and parent rows
// Parent rows and the array of arrays need to be the same length
// so we can "hoist" the created array rows back into the parent rows
export const insertArrays = async ({
adapter,
arrays,
parentRows,
}: Args): Promise<void> => {
// Maintain a map of flattened rows by table
const rowsByTable: RowsByTable = {};
arrays.forEach((arraysByTable, parentRowIndex) => {
Object.entries(arraysByTable).forEach(([tableName, arrayRows]) => {
// If the table doesn't exist in map, initialize it
if (!rowsByTable[tableName]) {
rowsByTable[tableName] = {
rows: [],
locales: [],
columnName: arrayRows[0]?.columnName,
rowIndexMap: [],
arrays: [],
};
}
const parentID = parentRows[parentRowIndex].id;
// We store row indexes to "slice out" the array rows
// that belong to each parent row
rowsByTable[tableName].rowIndexMap.push([
rowsByTable[tableName].rows.length, rowsByTable[tableName].rows.length + arrayRows.length,
]);
// Add any sub arrays that need to be created
// We will call this recursively below
arrayRows.forEach((arrayRow) => {
if (Object.keys(arrayRow.arrays).length > 0) {
rowsByTable[tableName].arrays.push(arrayRow.arrays);
}
// Set up parent IDs for both row and locale row
arrayRow.row._parentID = parentID;
rowsByTable[tableName].rows.push(arrayRow.row);
arrayRow.locale._parentID = arrayRow.row.id;
rowsByTable[tableName].locales.push(arrayRow.locale);
});
});
});
// Insert all corresponding arrays in parallel
// (one insert per array table)
await Promise.all(Object.entries(rowsByTable).map(async (
[tableName, row],
) => {
const insertedRows = await adapter.db.insert(adapter.tables[tableName])
.values(row.rows).returning();
rowsByTable[tableName].rows = insertedRows.map((arrayRow) => {
delete arrayRow._parentID;
delete arrayRow._order;
return arrayRow;
});
// Insert locale rows
if (adapter.tables[`${tableName}_locales`]) {
const insertedLocaleRows = await adapter.db.insert(adapter.tables[`${tableName}_locales`])
.values(row.locales).returning();
insertedLocaleRows.forEach((localeRow, i) => {
delete localeRow._parentID;
rowsByTable[tableName].rows[i]._locales = [localeRow];
});
}
// If there are sub arrays, call this function recursively
if (row.arrays.length > 0) {
await insertArrays({
adapter,
arrays: row.arrays,
parentRows: row.rows,
});
}
}));
// Finally, hoist up the newly inserted arrays to their parent row
// by slicing out the appropriate range from rowIndexMap
Object.values(rowsByTable).forEach(({ rows, columnName, rowIndexMap }) => {
rowIndexMap.forEach(([start, finish], i) => {
parentRows[i][columnName] = rows.slice(start, finish);
});
});
};