diff --git a/packages/drizzle/src/upsertRow/index.ts b/packages/drizzle/src/upsertRow/index.ts index 6c3e9c46bae..9c718ec9266 100644 --- a/packages/drizzle/src/upsertRow/index.ts +++ b/packages/drizzle/src/upsertRow/index.ts @@ -240,6 +240,9 @@ export const upsertRow = async | TypeWithID>( const blocksToInsert: { [blockType: string]: BlockRowToInsert[] } = {} const selectsToInsert: { [selectTableName: string]: Record[] } = {} + const promisesToDelete: (() => Promise)[] = [] + const promisesToInsert: (() => Promise)[] = [] + // If there are locale rows with data, add the parent and locale to each if (Object.keys(rowToInsert.locales).length > 0) { Object.entries(rowToInsert.locales).forEach(([locale, localeRow]) => { @@ -313,18 +316,22 @@ export const upsertRow = async | TypeWithID>( const localeTable = adapter.tables[`${tableName}${adapter.localesSuffix}`] if (operation === 'update') { - await adapter.deleteWhere({ - db, - tableName: localeTableName, - where: eq(localeTable._parentID, insertedRow.id), - }) + promisesToDelete.push(() => + adapter.deleteWhere({ + db, + tableName: localeTableName, + where: eq(localeTable._parentID, insertedRow.id), + }), + ) } - await adapter.insert({ - db, - tableName: localeTableName, - values: localesToInsert, - }) + promisesToInsert.push(() => + adapter.insert({ + db, + tableName: localeTableName, + values: localesToInsert, + }), + ) } // ////////////////////////////////// @@ -339,24 +346,28 @@ export const upsertRow = async | TypeWithID>( (rel) => !('itemToRemove' in rel), ) - await deleteExistingRowsByPath({ - adapter, - db, - localeColumnName: 'locale', - parentColumnName: 'parent', - parentID: insertedRow.id, - pathColumnName: 'path', - rows: [...relationsToInsert, ...generalRelationshipDeletes], - tableName: relationshipsTableName, - }) + promisesToDelete.push(() => + deleteExistingRowsByPath({ + adapter, + db, + localeColumnName: 'locale', + parentColumnName: 'parent', + parentID: insertedRow.id, + pathColumnName: 'path', + rows: [...relationsToInsert, ...generalRelationshipDeletes], + tableName: relationshipsTableName, + }), + ) } if (relationsToInsert.length > 0) { - await adapter.insert({ - db, - tableName: relationshipsTableName, - values: relationsToInsert, - }) + promisesToInsert.push(() => + adapter.insert({ + db, + tableName: relationshipsTableName, + values: relationsToInsert, + }), + ) } // ////////////////////////////////// @@ -387,102 +398,105 @@ export const upsertRow = async | TypeWithID>( }) if (relationshipsToInsert.length > 0) { - // Check for potential duplicates - const relationshipTable = adapter.tables[relationshipsTableName] - - if (relationshipTable) { - // Build conditions only if we have relationships to check - if (relationshipsToInsert.length === 0) { - return // No relationships to insert - } - - const conditions = relationshipsToInsert.map((row: RelationshipRow) => { - const parts = [ - eq(relationshipTable.parent, row.parent), - eq(relationshipTable.path, row.path), - ] - - // Add locale condition - if (row.locale !== undefined && relationshipTable.locale) { - parts.push(eq(relationshipTable.locale, row.locale)) - } else if (relationshipTable.locale) { - parts.push(isNull(relationshipTable.locale)) + promisesToInsert.push(async () => { + // Check for potential duplicates + const relationshipTable = adapter.tables[relationshipsTableName] + + if (relationshipTable) { + // Build conditions only if we have relationships to check + if (relationshipsToInsert.length === 0) { + return // No relationships to insert } - // Add all relationship ID matches using schema fields - for (const [key, value] of Object.entries(row)) { - if (key.endsWith('ID') && value != null) { - const column = relationshipTable[key] - if (column && typeof column === 'object') { - parts.push(eq(column, value)) + const conditions = relationshipsToInsert.map((row: RelationshipRow) => { + const parts = [ + eq(relationshipTable.parent, row.parent), + eq(relationshipTable.path, row.path), + ] + + // Add locale condition + if (row.locale !== undefined && relationshipTable.locale) { + parts.push(eq(relationshipTable.locale, row.locale)) + } else if (relationshipTable.locale) { + parts.push(isNull(relationshipTable.locale)) + } + + // Add all relationship ID matches using schema fields + for (const [key, value] of Object.entries(row)) { + if (key.endsWith('ID') && value != null) { + const column = relationshipTable[key] + if (column && typeof column === 'object') { + parts.push(eq(column, value)) + } } } - } - return and(...parts) - }) + return and(...parts) + }) - // Get both existing relationships AND max order in a single query - let existingRels: Record[] = [] - let maxOrder = 0 + // Get both existing relationships AND max order in a single query + let existingRels: Record[] = [] + let maxOrder = 0 + + if (conditions.length > 0) { + // Query for existing relationships + existingRels = await (db as any) + .select() + .from(relationshipTable) + .where(or(...conditions)) + } - if (conditions.length > 0) { - // Query for existing relationships - existingRels = await (db as any) - .select() + // Get max order for this parent across all paths in a single query + const parentId = id || insertedRow.id + const maxOrderResult = await (db as any) + .select({ maxOrder: relationshipTable.order }) .from(relationshipTable) - .where(or(...conditions)) - } + .where(eq(relationshipTable.parent, parentId)) + .orderBy(desc(relationshipTable.order)) + .limit(1) - // Get max order for this parent across all paths in a single query - const parentId = id || insertedRow.id - const maxOrderResult = await (db as any) - .select({ maxOrder: relationshipTable.order }) - .from(relationshipTable) - .where(eq(relationshipTable.parent, parentId)) - .orderBy(desc(relationshipTable.order)) - .limit(1) - - if (maxOrderResult.length > 0 && maxOrderResult[0].maxOrder) { - maxOrder = maxOrderResult[0].maxOrder - } + if (maxOrderResult.length > 0 && maxOrderResult[0].maxOrder) { + maxOrder = maxOrderResult[0].maxOrder + } - // Set order values for all relationships based on max order - relationshipsToInsert.forEach((row, index) => { - row.order = maxOrder + index + 1 - }) + // Set order values for all relationships based on max order + relationshipsToInsert.forEach((row, index) => { + row.order = maxOrder + index + 1 + }) - // Filter out relationships that already exist - const relationshipsToActuallyInsert = relationshipsToInsert.filter((newRow) => { - return !existingRels.some((existingRow: Record) => { - // Check if this relationship already exists - let matches = existingRow.parent === newRow.parent && existingRow.path === newRow.path + // Filter out relationships that already exist + const relationshipsToActuallyInsert = relationshipsToInsert.filter((newRow) => { + return !existingRels.some((existingRow: Record) => { + // Check if this relationship already exists + let matches = + existingRow.parent === newRow.parent && existingRow.path === newRow.path - if (newRow.locale !== undefined) { - matches = matches && existingRow.locale === newRow.locale - } + if (newRow.locale !== undefined) { + matches = matches && existingRow.locale === newRow.locale + } - // Check relationship value matches - convert to camelCase for comparison - for (const key of Object.keys(newRow)) { - if (key.endsWith('ID')) { - // Now using camelCase keys - matches = matches && existingRow[key] === newRow[key] + // Check relationship value matches - convert to camelCase for comparison + for (const key of Object.keys(newRow)) { + if (key.endsWith('ID')) { + // Now using camelCase keys + matches = matches && existingRow[key] === newRow[key] + } } - } - return matches + return matches + }) }) - }) - // Insert only non-duplicate relationships - if (relationshipsToActuallyInsert.length > 0) { - await adapter.insert({ - db, - tableName: relationshipsTableName, - values: relationshipsToActuallyInsert, - }) + // Insert only non-duplicate relationships + if (relationshipsToActuallyInsert.length > 0) { + await adapter.insert({ + db, + tableName: relationshipsTableName, + values: relationshipsToActuallyInsert, + }) + } } - } + }) } } @@ -529,11 +543,13 @@ export const upsertRow = async | TypeWithID>( } // Execute DELETE using Drizzle query builder - await adapter.deleteWhere({ - db, - tableName: relationshipsTableName, - where: and(...conditions), - }) + promisesToDelete.push(() => + adapter.deleteWhere({ + db, + tableName: relationshipsTableName, + where: and(...conditions), + }), + ) } } } @@ -546,24 +562,28 @@ export const upsertRow = async | TypeWithID>( const textsTableName = `${tableName}_texts` if (operation === 'update') { - await deleteExistingRowsByPath({ - adapter, - db, - localeColumnName: 'locale', - parentColumnName: 'parent', - parentID: insertedRow.id, - pathColumnName: 'path', - rows: [...textsToInsert, ...rowToInsert.textsToDelete], - tableName: textsTableName, - }) + promisesToDelete.push(() => + deleteExistingRowsByPath({ + adapter, + db, + localeColumnName: 'locale', + parentColumnName: 'parent', + parentID: insertedRow.id, + pathColumnName: 'path', + rows: [...textsToInsert, ...rowToInsert.textsToDelete], + tableName: textsTableName, + }), + ) } if (textsToInsert.length > 0) { - await adapter.insert({ - db, - tableName: textsTableName, - values: textsToInsert, - }) + promisesToInsert.push(() => + adapter.insert({ + db, + tableName: textsTableName, + values: textsToInsert, + }), + ) } // ////////////////////////////////// @@ -573,24 +593,28 @@ export const upsertRow = async | TypeWithID>( const numbersTableName = `${tableName}_numbers` if (operation === 'update') { - await deleteExistingRowsByPath({ - adapter, - db, - localeColumnName: 'locale', - parentColumnName: 'parent', - parentID: insertedRow.id, - pathColumnName: 'path', - rows: [...numbersToInsert, ...rowToInsert.numbersToDelete], - tableName: numbersTableName, - }) + promisesToDelete.push(() => + deleteExistingRowsByPath({ + adapter, + db, + localeColumnName: 'locale', + parentColumnName: 'parent', + parentID: insertedRow.id, + pathColumnName: 'path', + rows: [...numbersToInsert, ...rowToInsert.numbersToDelete], + tableName: numbersTableName, + }), + ) } if (numbersToInsert.length > 0) { - await adapter.insert({ - db, - tableName: numbersTableName, - values: numbersToInsert, - }) + promisesToInsert.push(() => + adapter.insert({ + db, + tableName: numbersTableName, + values: numbersToInsert, + }), + ) } // ////////////////////////////////// @@ -602,11 +626,13 @@ export const upsertRow = async | TypeWithID>( if (operation === 'update') { for (const tableName of rowToInsert.blocksToDelete) { const blockTable = adapter.tables[tableName] - await adapter.deleteWhere({ - db, - tableName, - where: eq(blockTable._parentID, insertedRow.id), - }) + promisesToDelete.push(() => + adapter.deleteWhere({ + db, + tableName, + where: eq(blockTable._parentID, insertedRow.id), + }), + ) } } @@ -614,53 +640,55 @@ export const upsertRow = async | TypeWithID>( const arraysBlocksUUIDMap: Record = {} for (const [tableName, blockRows] of Object.entries(blocksToInsert)) { - insertedBlockRows[tableName] = await adapter.insert({ - db, - tableName, - values: blockRows.map(({ row }) => row), - }) + promisesToInsert.push(async () => { + insertedBlockRows[tableName] = await adapter.insert({ + db, + tableName, + values: blockRows.map(({ row }) => row), + }) - insertedBlockRows[tableName].forEach((row, i) => { - blockRows[i].row = row - if ( - typeof row._uuid === 'string' && - (typeof row.id === 'string' || typeof row.id === 'number') - ) { - arraysBlocksUUIDMap[row._uuid] = row.id - } - }) + insertedBlockRows[tableName].forEach((row, i) => { + blockRows[i].row = row + if ( + typeof row._uuid === 'string' && + (typeof row.id === 'string' || typeof row.id === 'number') + ) { + arraysBlocksUUIDMap[row._uuid] = row.id + } + }) - const blockLocaleIndexMap: number[] = [] + const blockLocaleIndexMap: number[] = [] - const blockLocaleRowsToInsert = blockRows.reduce((acc, blockRow, i) => { - if (Object.entries(blockRow.locales).length > 0) { - Object.entries(blockRow.locales).forEach(([blockLocale, blockLocaleData]) => { - if (Object.keys(blockLocaleData).length > 0) { - blockLocaleData._parentID = blockRow.row.id - blockLocaleData._locale = blockLocale - acc.push(blockLocaleData) - blockLocaleIndexMap.push(i) - } - }) - } - - return acc - }, []) + const blockLocaleRowsToInsert = blockRows.reduce((acc, blockRow, i) => { + if (Object.entries(blockRow.locales).length > 0) { + Object.entries(blockRow.locales).forEach(([blockLocale, blockLocaleData]) => { + if (Object.keys(blockLocaleData).length > 0) { + blockLocaleData._parentID = blockRow.row.id + blockLocaleData._locale = blockLocale + acc.push(blockLocaleData) + blockLocaleIndexMap.push(i) + } + }) + } - if (blockLocaleRowsToInsert.length > 0) { - await adapter.insert({ - db, - tableName: `${tableName}${adapter.localesSuffix}`, - values: blockLocaleRowsToInsert, - }) - } + return acc + }, []) - await insertArrays({ - adapter, - arrays: blockRows.map(({ arrays }) => arrays), - db, - parentRows: insertedBlockRows[tableName], - uuidMap: arraysBlocksUUIDMap, + await Promise.all([ + blockLocaleRowsToInsert.length > 0 && + adapter.insert({ + db, + tableName: `${tableName}${adapter.localesSuffix}`, + values: blockLocaleRowsToInsert, + }), + insertArrays({ + adapter, + arrays: blockRows.map(({ arrays }) => arrays), + db, + parentRows: insertedBlockRows[tableName], + uuidMap: arraysBlocksUUIDMap, + }), + ]) }) } @@ -669,14 +697,16 @@ export const upsertRow = async | TypeWithID>( // ////////////////////////////////// if (operation === 'update') { - for (const arrayTableName of Object.keys(rowToInsert.arrays)) { - await deleteExistingArrayRows({ - adapter, - db, - parentID: insertedRow.id, - tableName: arrayTableName, - }) - } + await Promise.all( + Object.keys(rowToInsert.arrays).map((arrayTableName) => + deleteExistingArrayRows({ + adapter, + db, + parentID: insertedRow.id, + tableName: arrayTableName, + }), + ), + ) } await insertArrays({ @@ -694,11 +724,13 @@ export const upsertRow = async | TypeWithID>( for (const [selectTableName, tableRows] of Object.entries(selectsToInsert)) { const selectTable = adapter.tables[selectTableName] if (operation === 'update') { - await adapter.deleteWhere({ - db, - tableName: selectTableName, - where: eq(selectTable.parent, insertedRow.id), - }) + promisesToDelete.push(() => + adapter.deleteWhere({ + db, + tableName: selectTableName, + where: eq(selectTable.parent, insertedRow.id), + }), + ) } if (Object.keys(arraysBlocksUUIDMap).length > 0) { @@ -710,14 +742,20 @@ export const upsertRow = async | TypeWithID>( } if (tableRows.length) { - await adapter.insert({ - db, - tableName: selectTableName, - values: tableRows, - }) + promisesToInsert.push(() => + adapter.insert({ + db, + tableName: selectTableName, + values: tableRows, + }), + ) } } + // Run updates in parallel + await Promise.all(promisesToDelete.map((promise) => promise())) + await Promise.all(promisesToInsert.map((promise) => promise())) + // ////////////////////////////////// // Error Handling // ////////////////////////////////// diff --git a/packages/drizzle/src/upsertRow/insertArrays.ts b/packages/drizzle/src/upsertRow/insertArrays.ts index 19d1998a5e3..af0c07c9125 100644 --- a/packages/drizzle/src/upsertRow/insertArrays.ts +++ b/packages/drizzle/src/upsertRow/insertArrays.ts @@ -29,6 +29,7 @@ export const insertArrays = async ({ uuidMap = {}, }: Args): Promise => { // Maintain a map of flattened rows by table + const promises: (() => Promise)[] = [] const rowsByTable: RowsByTable = {} arrays.forEach((arraysByTable, parentRowIndex) => { @@ -76,51 +77,55 @@ export const insertArrays = async ({ // Insert all corresponding arrays // (one insert per array table) for (const [tableName, row] of Object.entries(rowsByTable)) { - // the nested arrays need the ID for the parentID foreign key - let insertedRows: Args['parentRows'] - if (row.rows.length > 0) { - insertedRows = await adapter.insert({ - db, - tableName, - values: row.rows, - }) - - insertedRows.forEach((row) => { - if ( - typeof row._uuid === 'string' && - (typeof row.id === 'string' || typeof row.id === 'number') - ) { - uuidMap[row._uuid] = row.id - } - }) - } + promises.push(async () => { + // the nested arrays need the ID for the parentID foreign key + let insertedRows: Args['parentRows'] + if (row.rows.length > 0) { + insertedRows = await adapter.insert({ + db, + tableName, + values: row.rows, + }) - // Insert locale rows - if (adapter.tables[`${tableName}${adapter.localesSuffix}`] && row.locales.length > 0) { - if (!row.locales[0]._parentID) { - row.locales = row.locales.map((localeRow) => { - if (typeof localeRow._getParentID === 'function') { - localeRow._parentID = localeRow._getParentID(insertedRows) - delete localeRow._getParentID + insertedRows.forEach((row) => { + if ( + typeof row._uuid === 'string' && + (typeof row.id === 'string' || typeof row.id === 'number') + ) { + uuidMap[row._uuid] = row.id } - return localeRow }) } - await adapter.insert({ - db, - tableName: `${tableName}${adapter.localesSuffix}`, - values: row.locales, - }) - } - // If there are sub arrays, call this function recursively - if (row.arrays.length > 0) { - await insertArrays({ - adapter, - arrays: row.arrays, - db, - parentRows: insertedRows, - }) - } + // Insert locale rows + if (adapter.tables[`${tableName}${adapter.localesSuffix}`] && row.locales.length > 0) { + if (!row.locales[0]._parentID) { + row.locales = row.locales.map((localeRow) => { + if (typeof localeRow._getParentID === 'function') { + localeRow._parentID = localeRow._getParentID(insertedRows) + delete localeRow._getParentID + } + return localeRow + }) + } + await adapter.insert({ + db, + tableName: `${tableName}${adapter.localesSuffix}`, + values: row.locales, + }) + } + + // If there are sub arrays, call this function recursively + if (row.arrays.length > 0) { + await insertArrays({ + adapter, + arrays: row.arrays, + db, + parentRows: insertedRows, + }) + } + }) } + + await Promise.all(promises.map((promise) => promise())) }