@payloadcms/drizzle 3.48.0-canary.5 → 3.48.0-canary.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/findDistinct.d.ts +3 -0
  2. package/dist/findDistinct.d.ts.map +1 -0
  3. package/dist/findDistinct.js +94 -0
  4. package/dist/findDistinct.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/postgres/countDistinct.js +3 -3
  10. package/dist/postgres/countDistinct.js.map +1 -1
  11. package/dist/postgres/types.d.ts +2 -0
  12. package/dist/postgres/types.d.ts.map +1 -1
  13. package/dist/postgres/types.js.map +1 -1
  14. package/dist/queries/parseParams.d.ts.map +1 -1
  15. package/dist/queries/parseParams.js +6 -1
  16. package/dist/queries/parseParams.js.map +1 -1
  17. package/dist/queries/selectDistinct.d.ts +2 -1
  18. package/dist/queries/selectDistinct.d.ts.map +1 -1
  19. package/dist/queries/selectDistinct.js +2 -2
  20. package/dist/queries/selectDistinct.js.map +1 -1
  21. package/dist/transform/write/index.d.ts +2 -1
  22. package/dist/transform/write/index.d.ts.map +1 -1
  23. package/dist/transform/write/index.js +2 -1
  24. package/dist/transform/write/index.js.map +1 -1
  25. package/dist/transform/write/traverseFields.d.ts +3 -2
  26. package/dist/transform/write/traverseFields.d.ts.map +1 -1
  27. package/dist/transform/write/traverseFields.js +9 -1
  28. package/dist/transform/write/traverseFields.js.map +1 -1
  29. package/dist/types.d.ts +1 -0
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/types.js.map +1 -1
  32. package/dist/updateOne.d.ts.map +1 -1
  33. package/dist/updateOne.js +8 -73
  34. package/dist/updateOne.js.map +1 -1
  35. package/dist/upsertRow/index.d.ts +1 -1
  36. package/dist/upsertRow/index.d.ts.map +1 -1
  37. package/dist/upsertRow/index.js +354 -334
  38. package/dist/upsertRow/index.js.map +1 -1
  39. package/dist/upsertRow/shouldUseOptimizedUpsertRow.d.ts +10 -0
  40. package/dist/upsertRow/shouldUseOptimizedUpsertRow.d.ts.map +1 -0
  41. package/dist/upsertRow/shouldUseOptimizedUpsertRow.js +24 -0
  42. package/dist/upsertRow/shouldUseOptimizedUpsertRow.js.map +1 -0
  43. package/dist/utilities/rawConstraint.d.ts +1 -0
  44. package/dist/utilities/rawConstraint.d.ts.map +1 -1
  45. package/dist/utilities/rawConstraint.js +1 -0
  46. package/dist/utilities/rawConstraint.js.map +1 -1
  47. package/package.json +3 -3
@@ -6,396 +6,416 @@ import { transformForWrite } from '../transform/write/index.js';
6
6
  import { deleteExistingArrayRows } from './deleteExistingArrayRows.js';
7
7
  import { deleteExistingRowsByPath } from './deleteExistingRowsByPath.js';
8
8
  import { insertArrays } from './insertArrays.js';
9
+ import { shouldUseOptimizedUpsertRow } from './shouldUseOptimizedUpsertRow.js';
9
10
  /**
10
11
  * If `id` is provided, it will update the row with that ID.
11
12
  * If `where` is provided, it will update the row that matches the `where`
12
13
  * If neither `id` nor `where` is provided, it will create a new row.
13
14
  *
14
- * This function replaces the entire row and does not support partial updates.
15
+ * adapter function replaces the entire row and does not support partial updates.
15
16
  */ export const upsertRow = async ({ id, adapter, data, db, fields, ignoreResult, // TODO:
16
17
  // When we support joins for write operations (create/update) - pass collectionSlug to the buildFindManyArgs
17
18
  // Make a new argument in upsertRow.ts and pass the slug from every operation.
18
19
  joinQuery: _joinQuery, operation, path = '', req, select, tableName, upsertTarget, where })=>{
19
- // Split out the incoming data into the corresponding:
20
- // base row, locales, relationships, blocks, and arrays
21
- const rowToInsert = transformForWrite({
22
- adapter,
20
+ let insertedRow = {
21
+ id
22
+ };
23
+ if (id && shouldUseOptimizedUpsertRow({
23
24
  data,
24
- fields,
25
- path,
26
- tableName
27
- });
28
- // First, we insert the main row
29
- let insertedRow;
30
- try {
31
- if (operation === 'update') {
32
- const target = upsertTarget || adapter.tables[tableName].id;
33
- if (id) {
34
- rowToInsert.row.id = id;
35
- [insertedRow] = await adapter.insert({
36
- db,
37
- onConflictDoUpdate: {
38
- set: rowToInsert.row,
39
- target
40
- },
41
- tableName,
42
- values: rowToInsert.row
43
- });
25
+ fields
26
+ })) {
27
+ const { row } = transformForWrite({
28
+ adapter,
29
+ data,
30
+ enableAtomicWrites: true,
31
+ fields,
32
+ tableName
33
+ });
34
+ const drizzle = db;
35
+ await drizzle.update(adapter.tables[tableName]).set(row)// TODO: we can skip fetching idToUpdate here with using the incoming where
36
+ .where(eq(adapter.tables[tableName].id, id));
37
+ } else {
38
+ // Split out the incoming data into the corresponding:
39
+ // base row, locales, relationships, blocks, and arrays
40
+ const rowToInsert = transformForWrite({
41
+ adapter,
42
+ data,
43
+ enableAtomicWrites: false,
44
+ fields,
45
+ path,
46
+ tableName
47
+ });
48
+ // First, we insert the main row
49
+ try {
50
+ if (operation === 'update') {
51
+ const target = upsertTarget || adapter.tables[tableName].id;
52
+ if (id) {
53
+ rowToInsert.row.id = id;
54
+ [insertedRow] = await adapter.insert({
55
+ db,
56
+ onConflictDoUpdate: {
57
+ set: rowToInsert.row,
58
+ target
59
+ },
60
+ tableName,
61
+ values: rowToInsert.row
62
+ });
63
+ } else {
64
+ ;
65
+ [insertedRow] = await adapter.insert({
66
+ db,
67
+ onConflictDoUpdate: {
68
+ set: rowToInsert.row,
69
+ target,
70
+ where
71
+ },
72
+ tableName,
73
+ values: rowToInsert.row
74
+ });
75
+ }
44
76
  } else {
77
+ if (adapter.allowIDOnCreate && data.id) {
78
+ rowToInsert.row.id = data.id;
79
+ }
45
80
  ;
46
81
  [insertedRow] = await adapter.insert({
47
82
  db,
48
- onConflictDoUpdate: {
49
- set: rowToInsert.row,
50
- target,
51
- where
52
- },
53
83
  tableName,
54
84
  values: rowToInsert.row
55
85
  });
56
86
  }
57
- } else {
58
- if (adapter.allowIDOnCreate && data.id) {
59
- rowToInsert.row.id = data.id;
87
+ const localesToInsert = [];
88
+ const relationsToInsert = [];
89
+ const textsToInsert = [];
90
+ const numbersToInsert = [];
91
+ const blocksToInsert = {};
92
+ const selectsToInsert = {};
93
+ // If there are locale rows with data, add the parent and locale to each
94
+ if (Object.keys(rowToInsert.locales).length > 0) {
95
+ Object.entries(rowToInsert.locales).forEach(([locale, localeRow])=>{
96
+ localeRow._parentID = insertedRow.id;
97
+ localeRow._locale = locale;
98
+ localesToInsert.push(localeRow);
99
+ });
60
100
  }
61
- ;
62
- [insertedRow] = await adapter.insert({
63
- db,
64
- tableName,
65
- values: rowToInsert.row
66
- });
67
- }
68
- const localesToInsert = [];
69
- const relationsToInsert = [];
70
- const textsToInsert = [];
71
- const numbersToInsert = [];
72
- const blocksToInsert = {};
73
- const selectsToInsert = {};
74
- // If there are locale rows with data, add the parent and locale to each
75
- if (Object.keys(rowToInsert.locales).length > 0) {
76
- Object.entries(rowToInsert.locales).forEach(([locale, localeRow])=>{
77
- localeRow._parentID = insertedRow.id;
78
- localeRow._locale = locale;
79
- localesToInsert.push(localeRow);
80
- });
81
- }
82
- // If there are relationships, add parent to each
83
- if (rowToInsert.relationships.length > 0) {
84
- rowToInsert.relationships.forEach((relation)=>{
85
- relation.parent = insertedRow.id;
86
- relationsToInsert.push(relation);
87
- });
88
- }
89
- // If there are texts, add parent to each
90
- if (rowToInsert.texts.length > 0) {
91
- rowToInsert.texts.forEach((textRow)=>{
92
- textRow.parent = insertedRow.id;
93
- textsToInsert.push(textRow);
94
- });
95
- }
96
- // If there are numbers, add parent to each
97
- if (rowToInsert.numbers.length > 0) {
98
- rowToInsert.numbers.forEach((numberRow)=>{
99
- numberRow.parent = insertedRow.id;
100
- numbersToInsert.push(numberRow);
101
- });
102
- }
103
- // If there are selects, add parent to each, and then
104
- // store by table name and rows
105
- if (Object.keys(rowToInsert.selects).length > 0) {
106
- Object.entries(rowToInsert.selects).forEach(([selectTableName, selectRows])=>{
107
- selectsToInsert[selectTableName] = [];
108
- selectRows.forEach((row)=>{
109
- if (typeof row.parent === 'undefined') {
110
- row.parent = insertedRow.id;
101
+ // If there are relationships, add parent to each
102
+ if (rowToInsert.relationships.length > 0) {
103
+ rowToInsert.relationships.forEach((relation)=>{
104
+ relation.parent = insertedRow.id;
105
+ relationsToInsert.push(relation);
106
+ });
107
+ }
108
+ // If there are texts, add parent to each
109
+ if (rowToInsert.texts.length > 0) {
110
+ rowToInsert.texts.forEach((textRow)=>{
111
+ textRow.parent = insertedRow.id;
112
+ textsToInsert.push(textRow);
113
+ });
114
+ }
115
+ // If there are numbers, add parent to each
116
+ if (rowToInsert.numbers.length > 0) {
117
+ rowToInsert.numbers.forEach((numberRow)=>{
118
+ numberRow.parent = insertedRow.id;
119
+ numbersToInsert.push(numberRow);
120
+ });
121
+ }
122
+ // If there are selects, add parent to each, and then
123
+ // store by table name and rows
124
+ if (Object.keys(rowToInsert.selects).length > 0) {
125
+ Object.entries(rowToInsert.selects).forEach(([selectTableName, selectRows])=>{
126
+ selectsToInsert[selectTableName] = [];
127
+ selectRows.forEach((row)=>{
128
+ if (typeof row.parent === 'undefined') {
129
+ row.parent = insertedRow.id;
130
+ }
131
+ selectsToInsert[selectTableName].push(row);
132
+ });
133
+ });
134
+ }
135
+ // If there are blocks, add parent to each, and then
136
+ // store by table name and rows
137
+ Object.keys(rowToInsert.blocks).forEach((tableName)=>{
138
+ rowToInsert.blocks[tableName].forEach((blockRow)=>{
139
+ blockRow.row._parentID = insertedRow.id;
140
+ if (!blocksToInsert[tableName]) {
141
+ blocksToInsert[tableName] = [];
111
142
  }
112
- selectsToInsert[selectTableName].push(row);
143
+ if (blockRow.row.uuid) {
144
+ delete blockRow.row.uuid;
145
+ }
146
+ blocksToInsert[tableName].push(blockRow);
113
147
  });
114
148
  });
115
- }
116
- // If there are blocks, add parent to each, and then
117
- // store by table name and rows
118
- Object.keys(rowToInsert.blocks).forEach((tableName)=>{
119
- rowToInsert.blocks[tableName].forEach((blockRow)=>{
120
- blockRow.row._parentID = insertedRow.id;
121
- if (!blocksToInsert[tableName]) {
122
- blocksToInsert[tableName] = [];
123
- }
124
- if (blockRow.row.uuid) {
125
- delete blockRow.row.uuid;
149
+ // //////////////////////////////////
150
+ // INSERT LOCALES
151
+ // //////////////////////////////////
152
+ if (localesToInsert.length > 0) {
153
+ const localeTableName = `${tableName}${adapter.localesSuffix}`;
154
+ const localeTable = adapter.tables[`${tableName}${adapter.localesSuffix}`];
155
+ if (operation === 'update') {
156
+ await adapter.deleteWhere({
157
+ db,
158
+ tableName: localeTableName,
159
+ where: eq(localeTable._parentID, insertedRow.id)
160
+ });
126
161
  }
127
- blocksToInsert[tableName].push(blockRow);
128
- });
129
- });
130
- // //////////////////////////////////
131
- // INSERT LOCALES
132
- // //////////////////////////////////
133
- if (localesToInsert.length > 0) {
134
- const localeTableName = `${tableName}${adapter.localesSuffix}`;
135
- const localeTable = adapter.tables[`${tableName}${adapter.localesSuffix}`];
136
- if (operation === 'update') {
137
- await adapter.deleteWhere({
162
+ await adapter.insert({
138
163
  db,
139
164
  tableName: localeTableName,
140
- where: eq(localeTable._parentID, insertedRow.id)
165
+ values: localesToInsert
141
166
  });
142
167
  }
143
- await adapter.insert({
144
- db,
145
- tableName: localeTableName,
146
- values: localesToInsert
147
- });
148
- }
149
- // //////////////////////////////////
150
- // INSERT RELATIONSHIPS
151
- // //////////////////////////////////
152
- const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`;
153
- if (operation === 'update') {
154
- await deleteExistingRowsByPath({
155
- adapter,
156
- db,
157
- localeColumnName: 'locale',
158
- parentColumnName: 'parent',
159
- parentID: insertedRow.id,
160
- pathColumnName: 'path',
161
- rows: [
162
- ...relationsToInsert,
163
- ...rowToInsert.relationshipsToDelete
164
- ],
165
- tableName: relationshipsTableName
166
- });
167
- }
168
- if (relationsToInsert.length > 0) {
169
- await adapter.insert({
170
- db,
171
- tableName: relationshipsTableName,
172
- values: relationsToInsert
173
- });
174
- }
175
- // //////////////////////////////////
176
- // INSERT hasMany TEXTS
177
- // //////////////////////////////////
178
- const textsTableName = `${tableName}_texts`;
179
- if (operation === 'update') {
180
- await deleteExistingRowsByPath({
181
- adapter,
182
- db,
183
- localeColumnName: 'locale',
184
- parentColumnName: 'parent',
185
- parentID: insertedRow.id,
186
- pathColumnName: 'path',
187
- rows: [
188
- ...textsToInsert,
189
- ...rowToInsert.textsToDelete
190
- ],
191
- tableName: textsTableName
192
- });
193
- }
194
- if (textsToInsert.length > 0) {
195
- await adapter.insert({
196
- db,
197
- tableName: textsTableName,
198
- values: textsToInsert
199
- });
200
- }
201
- // //////////////////////////////////
202
- // INSERT hasMany NUMBERS
203
- // //////////////////////////////////
204
- const numbersTableName = `${tableName}_numbers`;
205
- if (operation === 'update') {
206
- await deleteExistingRowsByPath({
207
- adapter,
208
- db,
209
- localeColumnName: 'locale',
210
- parentColumnName: 'parent',
211
- parentID: insertedRow.id,
212
- pathColumnName: 'path',
213
- rows: [
214
- ...numbersToInsert,
215
- ...rowToInsert.numbersToDelete
216
- ],
217
- tableName: numbersTableName
218
- });
219
- }
220
- if (numbersToInsert.length > 0) {
221
- await adapter.insert({
222
- db,
223
- tableName: numbersTableName,
224
- values: numbersToInsert
225
- });
226
- }
227
- // //////////////////////////////////
228
- // INSERT BLOCKS
229
- // //////////////////////////////////
230
- const insertedBlockRows = {};
231
- if (operation === 'update') {
232
- for (const tableName of rowToInsert.blocksToDelete){
233
- const blockTable = adapter.tables[tableName];
234
- await adapter.deleteWhere({
168
+ // //////////////////////////////////
169
+ // INSERT RELATIONSHIPS
170
+ // //////////////////////////////////
171
+ const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`;
172
+ if (operation === 'update') {
173
+ await deleteExistingRowsByPath({
174
+ adapter,
235
175
  db,
236
- tableName,
237
- where: eq(blockTable._parentID, insertedRow.id)
176
+ localeColumnName: 'locale',
177
+ parentColumnName: 'parent',
178
+ parentID: insertedRow.id,
179
+ pathColumnName: 'path',
180
+ rows: [
181
+ ...relationsToInsert,
182
+ ...rowToInsert.relationshipsToDelete
183
+ ],
184
+ tableName: relationshipsTableName
238
185
  });
239
186
  }
240
- }
241
- // When versions are enabled, this is used to track mapping between blocks/arrays ObjectID to their numeric generated representation, then we use it for nested to arrays/blocks select hasMany in versions.
242
- const arraysBlocksUUIDMap = {};
243
- for (const [tableName, blockRows] of Object.entries(blocksToInsert)){
244
- insertedBlockRows[tableName] = await adapter.insert({
245
- db,
246
- tableName,
247
- values: blockRows.map(({ row })=>row)
248
- });
249
- insertedBlockRows[tableName].forEach((row, i)=>{
250
- blockRows[i].row = row;
251
- if (typeof row._uuid === 'string' && (typeof row.id === 'string' || typeof row.id === 'number')) {
252
- arraysBlocksUUIDMap[row._uuid] = row.id;
253
- }
254
- });
255
- const blockLocaleIndexMap = [];
256
- const blockLocaleRowsToInsert = blockRows.reduce((acc, blockRow, i)=>{
257
- if (Object.entries(blockRow.locales).length > 0) {
258
- Object.entries(blockRow.locales).forEach(([blockLocale, blockLocaleData])=>{
259
- if (Object.keys(blockLocaleData).length > 0) {
260
- blockLocaleData._parentID = blockRow.row.id;
261
- blockLocaleData._locale = blockLocale;
262
- acc.push(blockLocaleData);
263
- blockLocaleIndexMap.push(i);
264
- }
265
- });
266
- }
267
- return acc;
268
- }, []);
269
- if (blockLocaleRowsToInsert.length > 0) {
187
+ if (relationsToInsert.length > 0) {
270
188
  await adapter.insert({
271
189
  db,
272
- tableName: `${tableName}${adapter.localesSuffix}`,
273
- values: blockLocaleRowsToInsert
190
+ tableName: relationshipsTableName,
191
+ values: relationsToInsert
274
192
  });
275
193
  }
276
- await insertArrays({
277
- adapter,
278
- arrays: blockRows.map(({ arrays })=>arrays),
279
- db,
280
- parentRows: insertedBlockRows[tableName],
281
- uuidMap: arraysBlocksUUIDMap
282
- });
283
- }
284
- // //////////////////////////////////
285
- // INSERT ARRAYS RECURSIVELY
286
- // //////////////////////////////////
287
- if (operation === 'update') {
288
- for (const arrayTableName of Object.keys(rowToInsert.arrays)){
289
- await deleteExistingArrayRows({
194
+ // //////////////////////////////////
195
+ // INSERT hasMany TEXTS
196
+ // //////////////////////////////////
197
+ const textsTableName = `${tableName}_texts`;
198
+ if (operation === 'update') {
199
+ await deleteExistingRowsByPath({
290
200
  adapter,
291
201
  db,
202
+ localeColumnName: 'locale',
203
+ parentColumnName: 'parent',
292
204
  parentID: insertedRow.id,
293
- tableName: arrayTableName
205
+ pathColumnName: 'path',
206
+ rows: [
207
+ ...textsToInsert,
208
+ ...rowToInsert.textsToDelete
209
+ ],
210
+ tableName: textsTableName
294
211
  });
295
212
  }
296
- }
297
- await insertArrays({
298
- adapter,
299
- arrays: [
300
- rowToInsert.arrays
301
- ],
302
- db,
303
- parentRows: [
304
- insertedRow
305
- ],
306
- uuidMap: arraysBlocksUUIDMap
307
- });
308
- // //////////////////////////////////
309
- // INSERT hasMany SELECTS
310
- // //////////////////////////////////
311
- for (const [selectTableName, tableRows] of Object.entries(selectsToInsert)){
312
- const selectTable = adapter.tables[selectTableName];
313
- if (operation === 'update') {
314
- await adapter.deleteWhere({
213
+ if (textsToInsert.length > 0) {
214
+ await adapter.insert({
315
215
  db,
316
- tableName: selectTableName,
317
- where: eq(selectTable.parent, insertedRow.id)
216
+ tableName: textsTableName,
217
+ values: textsToInsert
318
218
  });
319
219
  }
320
- if (Object.keys(arraysBlocksUUIDMap).length > 0) {
321
- tableRows.forEach((row)=>{
322
- if (row.parent in arraysBlocksUUIDMap) {
323
- row.parent = arraysBlocksUUIDMap[row.parent];
324
- }
220
+ // //////////////////////////////////
221
+ // INSERT hasMany NUMBERS
222
+ // //////////////////////////////////
223
+ const numbersTableName = `${tableName}_numbers`;
224
+ if (operation === 'update') {
225
+ await deleteExistingRowsByPath({
226
+ adapter,
227
+ db,
228
+ localeColumnName: 'locale',
229
+ parentColumnName: 'parent',
230
+ parentID: insertedRow.id,
231
+ pathColumnName: 'path',
232
+ rows: [
233
+ ...numbersToInsert,
234
+ ...rowToInsert.numbersToDelete
235
+ ],
236
+ tableName: numbersTableName
325
237
  });
326
238
  }
327
- if (tableRows.length) {
239
+ if (numbersToInsert.length > 0) {
328
240
  await adapter.insert({
329
241
  db,
330
- tableName: selectTableName,
331
- values: tableRows
242
+ tableName: numbersTableName,
243
+ values: numbersToInsert
332
244
  });
333
245
  }
334
- }
335
- // //////////////////////////////////
336
- // Error Handling
337
- // //////////////////////////////////
338
- } catch (caughtError) {
339
- // Unique constraint violation error
340
- // '23505' is the code for PostgreSQL, and 'SQLITE_CONSTRAINT_UNIQUE' is for SQLite
341
- let error = caughtError;
342
- if (typeof caughtError === 'object' && 'cause' in caughtError) {
343
- error = caughtError.cause;
344
- }
345
- if (error.code === '23505' || error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
346
- let fieldName = null;
347
- // We need to try and find the right constraint for the field but if we can't we fallback to a generic message
348
- if (error.code === '23505') {
349
- // For PostgreSQL, we can try to extract the field name from the error constraint
350
- if (adapter.fieldConstraints?.[tableName]?.[error.constraint]) {
351
- fieldName = adapter.fieldConstraints[tableName]?.[error.constraint];
352
- } else {
353
- const replacement = `${tableName}_`;
354
- if (error.constraint.includes(replacement)) {
355
- const replacedConstraint = error.constraint.replace(replacement, '');
356
- if (replacedConstraint && adapter.fieldConstraints[tableName]?.[replacedConstraint]) {
357
- fieldName = adapter.fieldConstraints[tableName][replacedConstraint];
358
- }
359
- }
246
+ // //////////////////////////////////
247
+ // INSERT BLOCKS
248
+ // //////////////////////////////////
249
+ const insertedBlockRows = {};
250
+ if (operation === 'update') {
251
+ for (const tableName of rowToInsert.blocksToDelete){
252
+ const blockTable = adapter.tables[tableName];
253
+ await adapter.deleteWhere({
254
+ db,
255
+ tableName,
256
+ where: eq(blockTable._parentID, insertedRow.id)
257
+ });
360
258
  }
361
- if (!fieldName) {
362
- // Last case scenario we extract the key and value from the detail on the error
363
- const detail = error.detail;
364
- const regex = /Key \(([^)]+)\)=\(([^)]+)\)/;
365
- const match = detail.match(regex);
366
- if (match && match[1]) {
367
- const key = match[1];
368
- fieldName = key;
259
+ }
260
+ // When versions are enabled, adapter is used to track mapping between blocks/arrays ObjectID to their numeric generated representation, then we use it for nested to arrays/blocks select hasMany in versions.
261
+ const arraysBlocksUUIDMap = {};
262
+ for (const [tableName, blockRows] of Object.entries(blocksToInsert)){
263
+ insertedBlockRows[tableName] = await adapter.insert({
264
+ db,
265
+ tableName,
266
+ values: blockRows.map(({ row })=>row)
267
+ });
268
+ insertedBlockRows[tableName].forEach((row, i)=>{
269
+ blockRows[i].row = row;
270
+ if (typeof row._uuid === 'string' && (typeof row.id === 'string' || typeof row.id === 'number')) {
271
+ arraysBlocksUUIDMap[row._uuid] = row.id;
272
+ }
273
+ });
274
+ const blockLocaleIndexMap = [];
275
+ const blockLocaleRowsToInsert = blockRows.reduce((acc, blockRow, i)=>{
276
+ if (Object.entries(blockRow.locales).length > 0) {
277
+ Object.entries(blockRow.locales).forEach(([blockLocale, blockLocaleData])=>{
278
+ if (Object.keys(blockLocaleData).length > 0) {
279
+ blockLocaleData._parentID = blockRow.row.id;
280
+ blockLocaleData._locale = blockLocale;
281
+ acc.push(blockLocaleData);
282
+ blockLocaleIndexMap.push(i);
283
+ }
284
+ });
369
285
  }
286
+ return acc;
287
+ }, []);
288
+ if (blockLocaleRowsToInsert.length > 0) {
289
+ await adapter.insert({
290
+ db,
291
+ tableName: `${tableName}${adapter.localesSuffix}`,
292
+ values: blockLocaleRowsToInsert
293
+ });
294
+ }
295
+ await insertArrays({
296
+ adapter,
297
+ arrays: blockRows.map(({ arrays })=>arrays),
298
+ db,
299
+ parentRows: insertedBlockRows[tableName],
300
+ uuidMap: arraysBlocksUUIDMap
301
+ });
302
+ }
303
+ // //////////////////////////////////
304
+ // INSERT ARRAYS RECURSIVELY
305
+ // //////////////////////////////////
306
+ if (operation === 'update') {
307
+ for (const arrayTableName of Object.keys(rowToInsert.arrays)){
308
+ await deleteExistingArrayRows({
309
+ adapter,
310
+ db,
311
+ parentID: insertedRow.id,
312
+ tableName: arrayTableName
313
+ });
314
+ }
315
+ }
316
+ await insertArrays({
317
+ adapter,
318
+ arrays: [
319
+ rowToInsert.arrays
320
+ ],
321
+ db,
322
+ parentRows: [
323
+ insertedRow
324
+ ],
325
+ uuidMap: arraysBlocksUUIDMap
326
+ });
327
+ // //////////////////////////////////
328
+ // INSERT hasMany SELECTS
329
+ // //////////////////////////////////
330
+ for (const [selectTableName, tableRows] of Object.entries(selectsToInsert)){
331
+ const selectTable = adapter.tables[selectTableName];
332
+ if (operation === 'update') {
333
+ await adapter.deleteWhere({
334
+ db,
335
+ tableName: selectTableName,
336
+ where: eq(selectTable.parent, insertedRow.id)
337
+ });
338
+ }
339
+ if (Object.keys(arraysBlocksUUIDMap).length > 0) {
340
+ tableRows.forEach((row)=>{
341
+ if (row.parent in arraysBlocksUUIDMap) {
342
+ row.parent = arraysBlocksUUIDMap[row.parent];
343
+ }
344
+ });
370
345
  }
371
- } else if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
372
- /**
373
- * For SQLite, we can try to extract the field name from the error message
374
- * The message typically looks like:
375
- * "UNIQUE constraint failed: table_name.field_name"
376
- */ const regex = /UNIQUE constraint failed: ([^.]+)\.([^.]+)/;
377
- const match = error.message.match(regex);
378
- if (match && match[2]) {
379
- if (adapter.fieldConstraints[tableName]) {
380
- fieldName = adapter.fieldConstraints[tableName][`${match[2]}_idx`];
346
+ if (tableRows.length) {
347
+ await adapter.insert({
348
+ db,
349
+ tableName: selectTableName,
350
+ values: tableRows
351
+ });
352
+ }
353
+ }
354
+ // //////////////////////////////////
355
+ // Error Handling
356
+ // //////////////////////////////////
357
+ } catch (caughtError) {
358
+ // Unique constraint violation error
359
+ // '23505' is the code for PostgreSQL, and 'SQLITE_CONSTRAINT_UNIQUE' is for SQLite
360
+ let error = caughtError;
361
+ if (typeof caughtError === 'object' && 'cause' in caughtError) {
362
+ error = caughtError.cause;
363
+ }
364
+ if (error.code === '23505' || error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
365
+ let fieldName = null;
366
+ // We need to try and find the right constraint for the field but if we can't we fallback to a generic message
367
+ if (error.code === '23505') {
368
+ // For PostgreSQL, we can try to extract the field name from the error constraint
369
+ if (adapter.fieldConstraints?.[tableName]?.[error.constraint]) {
370
+ fieldName = adapter.fieldConstraints[tableName]?.[error.constraint];
371
+ } else {
372
+ const replacement = `${tableName}_`;
373
+ if (error.constraint.includes(replacement)) {
374
+ const replacedConstraint = error.constraint.replace(replacement, '');
375
+ if (replacedConstraint && adapter.fieldConstraints[tableName]?.[replacedConstraint]) {
376
+ fieldName = adapter.fieldConstraints[tableName][replacedConstraint];
377
+ }
378
+ }
381
379
  }
382
380
  if (!fieldName) {
383
- fieldName = match[2];
381
+ // Last case scenario we extract the key and value from the detail on the error
382
+ const detail = error.detail;
383
+ const regex = /Key \(([^)]+)\)=\(([^)]+)\)/;
384
+ const match = detail.match(regex);
385
+ if (match && match[1]) {
386
+ const key = match[1];
387
+ fieldName = key;
388
+ }
389
+ }
390
+ } else if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
391
+ /**
392
+ * For SQLite, we can try to extract the field name from the error message
393
+ * The message typically looks like:
394
+ * "UNIQUE constraint failed: table_name.field_name"
395
+ */ const regex = /UNIQUE constraint failed: ([^.]+)\.([^.]+)/;
396
+ const match = error.message.match(regex);
397
+ if (match && match[2]) {
398
+ if (adapter.fieldConstraints[tableName]) {
399
+ fieldName = adapter.fieldConstraints[tableName][`${match[2]}_idx`];
400
+ }
401
+ if (!fieldName) {
402
+ fieldName = match[2];
403
+ }
384
404
  }
385
405
  }
406
+ throw new ValidationError({
407
+ id,
408
+ errors: [
409
+ {
410
+ message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',
411
+ path: fieldName
412
+ }
413
+ ],
414
+ req
415
+ }, req?.t);
416
+ } else {
417
+ throw error;
386
418
  }
387
- throw new ValidationError({
388
- id,
389
- errors: [
390
- {
391
- message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',
392
- path: fieldName
393
- }
394
- ],
395
- req
396
- }, req?.t);
397
- } else {
398
- throw error;
399
419
  }
400
420
  }
401
421
  if (ignoreResult === 'idOnly') {