@payloadcms/db-sqlite 3.0.0-canary.2c2c95f → 3.0.0-canary.2d0dd26

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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/connect.d.ts.map +1 -1
  3. package/dist/connect.js +15 -3
  4. package/dist/connect.js.map +1 -1
  5. package/dist/countDistinct.d.ts.map +1 -1
  6. package/dist/countDistinct.js +2 -3
  7. package/dist/countDistinct.js.map +1 -1
  8. package/dist/createMigration.d.ts.map +1 -1
  9. package/dist/createMigration.js +7 -4
  10. package/dist/createMigration.js.map +1 -1
  11. package/dist/defaultSnapshot.d.ts +1 -1
  12. package/dist/defaultSnapshot.d.ts.map +1 -1
  13. package/dist/defaultSnapshot.js +2 -1
  14. package/dist/defaultSnapshot.js.map +1 -1
  15. package/dist/getMigrationTemplate.d.ts +1 -0
  16. package/dist/getMigrationTemplate.d.ts.map +1 -1
  17. package/dist/getMigrationTemplate.js +5 -4
  18. package/dist/getMigrationTemplate.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +14 -8
  21. package/dist/index.js.map +1 -1
  22. package/dist/init.d.ts.map +1 -1
  23. package/dist/init.js +34 -4
  24. package/dist/init.js.map +1 -1
  25. package/dist/requireDrizzleKit.d.ts +3 -0
  26. package/dist/requireDrizzleKit.d.ts.map +1 -1
  27. package/dist/requireDrizzleKit.js +5 -2
  28. package/dist/requireDrizzleKit.js.map +1 -1
  29. package/dist/schema/build.d.ts +19 -16
  30. package/dist/schema/build.d.ts.map +1 -1
  31. package/dist/schema/build.js +44 -11
  32. package/dist/schema/build.js.map +1 -1
  33. package/dist/schema/createIndex.d.ts +4 -5
  34. package/dist/schema/createIndex.d.ts.map +1 -1
  35. package/dist/schema/createIndex.js +6 -4
  36. package/dist/schema/createIndex.js.map +1 -1
  37. package/dist/schema/traverseFields.d.ts +9 -2
  38. package/dist/schema/traverseFields.d.ts.map +1 -1
  39. package/dist/schema/traverseFields.js +395 -271
  40. package/dist/schema/traverseFields.js.map +1 -1
  41. package/dist/schema/withDefault.d.ts +4 -0
  42. package/dist/schema/withDefault.d.ts.map +1 -0
  43. package/dist/schema/withDefault.js +12 -0
  44. package/dist/schema/withDefault.js.map +1 -0
  45. package/dist/types.d.ts +54 -27
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/types.js.map +1 -1
  48. package/license.md +22 -0
  49. package/package.json +19 -10
  50. package/dist/generateDrizzleJSON.d.ts +0 -3
  51. package/dist/generateDrizzleJSON.d.ts.map +0 -1
  52. package/dist/generateDrizzleJSON.js +0 -8
  53. package/dist/generateDrizzleJSON.js.map +0 -1
@@ -1,14 +1,15 @@
1
- /* eslint-disable no-param-reassign */ import { createTableName, hasLocalesTable, validateExistingBlockIsIdentical } from '@payloadcms/drizzle';
1
+ import { buildIndexName, createTableName, hasLocalesTable, validateExistingBlockIsIdentical } from '@payloadcms/drizzle';
2
2
  import { relations } from 'drizzle-orm';
3
- import { SQLiteIntegerBuilder, SQLiteNumericBuilder, SQLiteTextBuilder, foreignKey, index, integer, numeric, text } from 'drizzle-orm/sqlite-core';
3
+ import { foreignKey, index, integer, numeric, SQLiteIntegerBuilder, SQLiteNumericBuilder, SQLiteTextBuilder, text } from 'drizzle-orm/sqlite-core';
4
4
  import { InvalidConfiguration } from 'payload';
5
- import { fieldAffectsData, optionIsObject } from 'payload/shared';
5
+ import { fieldAffectsData, fieldIsVirtual, optionIsObject } from 'payload/shared';
6
6
  import toSnakeCase from 'to-snake-case';
7
7
  import { buildTable } from './build.js';
8
8
  import { createIndex } from './createIndex.js';
9
9
  import { getIDColumn } from './getIDColumn.js';
10
10
  import { idToUUID } from './idToUUID.js';
11
- export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull, disableUnique = false, fieldPrefix, fields, forceLocalized, indexes, locales, localesColumns, localesIndexes, newTableName, parentTableName, relationsToBuild, relationships, rootRelationsToBuild, rootTableIDColType, rootTableName, versions })=>{
11
+ import { withDefault } from './withDefault.js';
12
+ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull, disableRelsTableUnique, disableUnique = false, fieldPrefix, fields, forceLocalized, indexes, locales, localesColumns, localesIndexes, newTableName, parentTableName, relationships, relationsToBuild, rootRelationsToBuild, rootTableIDColType, rootTableName, uniqueRelationships, versions, withinLocalizedArrayOrBlock })=>{
12
13
  let hasLocalizedField = false;
13
14
  let hasLocalizedRelationshipField = false;
14
15
  let hasManyTextField = false;
@@ -16,11 +17,22 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
16
17
  let hasManyNumberField = false;
17
18
  let hasLocalizedManyNumberField = false;
18
19
  let parentIDColType = 'integer';
19
- if (columns.id instanceof SQLiteIntegerBuilder) parentIDColType = 'integer';
20
- if (columns.id instanceof SQLiteNumericBuilder) parentIDColType = 'numeric';
21
- if (columns.id instanceof SQLiteTextBuilder) parentIDColType = 'text';
20
+ if (columns.id instanceof SQLiteIntegerBuilder) {
21
+ parentIDColType = 'integer';
22
+ }
23
+ if (columns.id instanceof SQLiteNumericBuilder) {
24
+ parentIDColType = 'numeric';
25
+ }
26
+ if (columns.id instanceof SQLiteTextBuilder) {
27
+ parentIDColType = 'text';
28
+ }
22
29
  fields.forEach((field)=>{
23
- if ('name' in field && field.name === 'id') return;
30
+ if ('name' in field && field.name === 'id') {
31
+ return;
32
+ }
33
+ if (fieldIsVirtual(field)) {
34
+ return;
35
+ }
24
36
  let columnName;
25
37
  let fieldName;
26
38
  let targetTable = columns;
@@ -35,14 +47,15 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
35
47
  targetTable = localesColumns;
36
48
  targetIndexes = localesIndexes;
37
49
  }
38
- if ((field.unique || field.index) && ![
50
+ if ((field.unique || field.index || [
51
+ 'relationship',
52
+ 'upload'
53
+ ].includes(field.type)) && ![
39
54
  'array',
40
55
  'blocks',
41
56
  'group',
42
- 'point',
43
- 'relationship',
44
- 'upload'
45
- ].includes(field.type) && !('hasMany' in field && field.hasMany === true)) {
57
+ 'point'
58
+ ].includes(field.type) && !('hasMany' in field && field.hasMany === true) && !('relationTo' in field && Array.isArray(field.relationTo))) {
46
59
  const unique = disableUnique !== true && field.unique;
47
60
  if (unique) {
48
61
  const constraintValue = `${fieldPrefix || ''}${field.name}`;
@@ -51,170 +64,21 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
51
64
  }
52
65
  adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue;
53
66
  }
54
- targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
55
- name: fieldName,
56
- columnName,
57
- tableName: newTableName,
67
+ const indexName = buildIndexName({
68
+ name: `${newTableName}_${columnName}`,
69
+ adapter: adapter
70
+ });
71
+ targetIndexes[indexName] = createIndex({
72
+ name: field.localized ? [
73
+ fieldName,
74
+ '_locale'
75
+ ] : fieldName,
76
+ indexName,
58
77
  unique
59
78
  });
60
79
  }
61
80
  }
62
81
  switch(field.type){
63
- case 'text':
64
- {
65
- if (field.hasMany) {
66
- if (field.localized) {
67
- hasLocalizedManyTextField = true;
68
- }
69
- if (field.index) {
70
- hasManyTextField = 'index';
71
- } else if (!hasManyTextField) {
72
- hasManyTextField = true;
73
- }
74
- if (field.unique) {
75
- throw new InvalidConfiguration('Unique is not supported in SQLite for hasMany text fields.');
76
- }
77
- } else {
78
- targetTable[fieldName] = text(columnName);
79
- }
80
- break;
81
- }
82
- case 'email':
83
- case 'code':
84
- case 'textarea':
85
- {
86
- targetTable[fieldName] = text(columnName);
87
- break;
88
- }
89
- case 'number':
90
- {
91
- if (field.hasMany) {
92
- if (field.localized) {
93
- hasLocalizedManyNumberField = true;
94
- }
95
- if (field.index) {
96
- hasManyNumberField = 'index';
97
- } else if (!hasManyNumberField) {
98
- hasManyNumberField = true;
99
- }
100
- if (field.unique) {
101
- throw new InvalidConfiguration('Unique is not supported in Postgres for hasMany number fields.');
102
- }
103
- } else {
104
- targetTable[fieldName] = numeric(columnName);
105
- }
106
- break;
107
- }
108
- case 'richText':
109
- case 'json':
110
- {
111
- targetTable[fieldName] = text(columnName, {
112
- mode: 'json'
113
- });
114
- break;
115
- }
116
- case 'date':
117
- {
118
- targetTable[fieldName] = text(columnName);
119
- break;
120
- }
121
- case 'point':
122
- {
123
- break;
124
- }
125
- case 'radio':
126
- case 'select':
127
- {
128
- const options = field.options.map((option)=>{
129
- if (optionIsObject(option)) {
130
- return option.value;
131
- }
132
- return option;
133
- });
134
- if (field.type === 'select' && field.hasMany) {
135
- const selectTableName = createTableName({
136
- adapter,
137
- config: field,
138
- parentTableName: newTableName,
139
- prefix: `${newTableName}_`,
140
- versionsCustomName: versions
141
- });
142
- const baseColumns = {
143
- order: integer('order').notNull(),
144
- parent: getIDColumn({
145
- name: 'parent_id',
146
- type: parentIDColType,
147
- notNull: true,
148
- primaryKey: false
149
- }),
150
- value: text('value', {
151
- enum: options
152
- })
153
- };
154
- const baseExtraConfig = {
155
- orderIdx: (cols)=>index(`${selectTableName}_order_idx`).on(cols.order),
156
- parentFk: (cols)=>foreignKey({
157
- name: `${selectTableName}_parent_fk`,
158
- columns: [
159
- cols.parent
160
- ],
161
- foreignColumns: [
162
- adapter.tables[parentTableName].id
163
- ]
164
- }).onDelete('cascade'),
165
- parentIdx: (cols)=>index(`${selectTableName}_parent_idx`).on(cols.parent)
166
- };
167
- if (field.localized) {
168
- baseColumns.locale = text('locale', {
169
- enum: locales
170
- }).notNull();
171
- baseExtraConfig.localeIdx = (cols)=>index(`${selectTableName}_locale_idx`).on(cols.locale);
172
- }
173
- if (field.index) {
174
- baseExtraConfig.value = (cols)=>index(`${selectTableName}_value_idx`).on(cols.value);
175
- }
176
- buildTable({
177
- adapter,
178
- baseColumns,
179
- baseExtraConfig,
180
- disableNotNull,
181
- disableUnique,
182
- fields: [],
183
- rootTableName,
184
- tableName: selectTableName,
185
- versions
186
- });
187
- relationsToBuild.set(fieldName, {
188
- type: 'many',
189
- // selects have their own localized table, independent of the base table.
190
- localized: false,
191
- target: selectTableName
192
- });
193
- adapter.relations[`relations_${selectTableName}`] = relations(adapter.tables[selectTableName], ({ one })=>({
194
- parent: one(adapter.tables[parentTableName], {
195
- fields: [
196
- adapter.tables[selectTableName].parent
197
- ],
198
- references: [
199
- adapter.tables[parentTableName].id
200
- ],
201
- relationName: fieldName
202
- })
203
- }));
204
- } else {
205
- targetTable[fieldName] = text(fieldName, {
206
- enum: options
207
- });
208
- }
209
- break;
210
- }
211
- case 'checkbox':
212
- {
213
- targetTable[fieldName] = integer(columnName, {
214
- mode: 'boolean'
215
- });
216
- break;
217
- }
218
82
  case 'array':
219
83
  {
220
84
  const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull;
@@ -247,31 +111,48 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
247
111
  }).onDelete('cascade'),
248
112
  _parentIDIdx: (cols)=>index(`${arrayTableName}_parent_id_idx`).on(cols._parentID)
249
113
  };
250
- if (field.localized && adapter.payload.config.localization) {
114
+ const isLocalized = Boolean(field.localized && adapter.payload.config.localization) || withinLocalizedArrayOrBlock || forceLocalized;
115
+ if (isLocalized) {
251
116
  baseColumns._locale = text('_locale', {
252
117
  enum: locales
253
118
  }).notNull();
254
119
  baseExtraConfig._localeIdx = (cols)=>index(`${arrayTableName}_locale_idx`).on(cols._locale);
255
120
  }
256
- const { hasManyNumberField: subHasManyNumberField, hasManyTextField: subHasManyTextField, relationsToBuild: subRelationsToBuild } = buildTable({
121
+ const { hasLocalizedManyNumberField: subHasLocalizedManyNumberField, hasLocalizedManyTextField: subHasLocalizedManyTextField, hasLocalizedRelationshipField: subHasLocalizedRelationshipField, hasManyNumberField: subHasManyNumberField, hasManyTextField: subHasManyTextField, relationsToBuild: subRelationsToBuild } = buildTable({
257
122
  adapter,
258
123
  baseColumns,
259
124
  baseExtraConfig,
260
125
  disableNotNull: disableNotNullFromHere,
126
+ disableRelsTableUnique: true,
261
127
  disableUnique,
262
128
  fields: disableUnique ? idToUUID(field.fields) : field.fields,
263
- rootRelationsToBuild,
264
129
  rootRelationships: relationships,
130
+ rootRelationsToBuild,
265
131
  rootTableIDColType,
266
132
  rootTableName,
133
+ rootUniqueRelationships: uniqueRelationships,
267
134
  tableName: arrayTableName,
268
- versions
135
+ versions,
136
+ withinLocalizedArrayOrBlock: isLocalized
269
137
  });
138
+ if (subHasLocalizedManyNumberField) {
139
+ hasLocalizedManyNumberField = subHasLocalizedManyNumberField;
140
+ }
141
+ if (subHasLocalizedRelationshipField) {
142
+ hasLocalizedRelationshipField = subHasLocalizedRelationshipField;
143
+ }
144
+ if (subHasLocalizedManyTextField) {
145
+ hasLocalizedManyTextField = subHasLocalizedManyTextField;
146
+ }
270
147
  if (subHasManyTextField) {
271
- if (!hasManyTextField || subHasManyTextField === 'index') hasManyTextField = subHasManyTextField;
148
+ if (!hasManyTextField || subHasManyTextField === 'index') {
149
+ hasManyTextField = subHasManyTextField;
150
+ }
272
151
  }
273
152
  if (subHasManyNumberField) {
274
- if (!hasManyNumberField || subHasManyNumberField === 'index') hasManyNumberField = subHasManyNumberField;
153
+ if (!hasManyNumberField || subHasManyNumberField === 'index') {
154
+ hasManyNumberField = subHasManyNumberField;
155
+ }
275
156
  }
276
157
  relationsToBuild.set(fieldName, {
277
158
  type: 'many',
@@ -343,7 +224,6 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
343
224
  };
344
225
  const baseExtraConfig = {
345
226
  _orderIdx: (cols)=>index(`${blockTableName}_order_idx`).on(cols._order),
346
- _parentIDIdx: (cols)=>index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
347
227
  _parentIdFk: (cols)=>foreignKey({
348
228
  name: `${blockTableName}_parent_id_fk`,
349
229
  columns: [
@@ -353,33 +233,51 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
353
233
  adapter.tables[rootTableName].id
354
234
  ]
355
235
  }).onDelete('cascade'),
236
+ _parentIDIdx: (cols)=>index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
356
237
  _pathIdx: (cols)=>index(`${blockTableName}_path_idx`).on(cols._path)
357
238
  };
358
- if (field.localized && adapter.payload.config.localization) {
239
+ const isLocalized = Boolean(field.localized && adapter.payload.config.localization) || withinLocalizedArrayOrBlock || forceLocalized;
240
+ if (isLocalized) {
359
241
  baseColumns._locale = text('_locale', {
360
242
  enum: locales
361
243
  }).notNull();
362
244
  baseExtraConfig._localeIdx = (cols)=>index(`${blockTableName}_locale_idx`).on(cols._locale);
363
245
  }
364
- const { hasManyNumberField: subHasManyNumberField, hasManyTextField: subHasManyTextField, relationsToBuild: subRelationsToBuild } = buildTable({
246
+ const { hasLocalizedManyNumberField: subHasLocalizedManyNumberField, hasLocalizedManyTextField: subHasLocalizedManyTextField, hasLocalizedRelationshipField: subHasLocalizedRelationshipField, hasManyNumberField: subHasManyNumberField, hasManyTextField: subHasManyTextField, relationsToBuild: subRelationsToBuild } = buildTable({
365
247
  adapter,
366
248
  baseColumns,
367
249
  baseExtraConfig,
368
250
  disableNotNull: disableNotNullFromHere,
251
+ disableRelsTableUnique: true,
369
252
  disableUnique,
370
253
  fields: disableUnique ? idToUUID(block.fields) : block.fields,
371
- rootRelationsToBuild,
372
254
  rootRelationships: relationships,
255
+ rootRelationsToBuild,
373
256
  rootTableIDColType,
374
257
  rootTableName,
258
+ rootUniqueRelationships: uniqueRelationships,
375
259
  tableName: blockTableName,
376
- versions
260
+ versions,
261
+ withinLocalizedArrayOrBlock: isLocalized
377
262
  });
263
+ if (subHasLocalizedManyNumberField) {
264
+ hasLocalizedManyNumberField = subHasLocalizedManyNumberField;
265
+ }
266
+ if (subHasLocalizedRelationshipField) {
267
+ hasLocalizedRelationshipField = subHasLocalizedRelationshipField;
268
+ }
269
+ if (subHasLocalizedManyTextField) {
270
+ hasLocalizedManyTextField = subHasLocalizedManyTextField;
271
+ }
378
272
  if (subHasManyTextField) {
379
- if (!hasManyTextField || subHasManyTextField === 'index') hasManyTextField = subHasManyTextField;
273
+ if (!hasManyTextField || subHasManyTextField === 'index') {
274
+ hasManyTextField = subHasManyTextField;
275
+ }
380
276
  }
381
277
  if (subHasManyNumberField) {
382
- if (!hasManyNumberField || subHasManyNumberField === 'index') hasManyNumberField = subHasManyNumberField;
278
+ if (!hasManyNumberField || subHasManyNumberField === 'index') {
279
+ hasManyNumberField = subHasManyNumberField;
280
+ }
383
281
  }
384
282
  adapter.relations[`relations_${blockTableName}`] = relations(adapter.tables[blockTableName], ({ many, one })=>{
385
283
  const result = {
@@ -438,8 +336,75 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
438
336
  });
439
337
  break;
440
338
  }
441
- case 'tab':
339
+ case 'checkbox':
340
+ {
341
+ targetTable[fieldName] = withDefault(integer(columnName, {
342
+ mode: 'boolean'
343
+ }), field);
344
+ break;
345
+ }
346
+ case 'code':
347
+ case 'email':
348
+ case 'textarea':
349
+ {
350
+ targetTable[fieldName] = withDefault(text(columnName), field);
351
+ break;
352
+ }
353
+ case 'collapsible':
354
+ case 'row':
355
+ {
356
+ const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull;
357
+ const { hasLocalizedField: rowHasLocalizedField, hasLocalizedManyNumberField: rowHasLocalizedManyNumberField, hasLocalizedManyTextField: rowHasLocalizedManyTextField, hasLocalizedRelationshipField: rowHasLocalizedRelationshipField, hasManyNumberField: rowHasManyNumberField, hasManyTextField: rowHasManyTextField } = traverseFields({
358
+ adapter,
359
+ columnPrefix,
360
+ columns,
361
+ disableNotNull: disableNotNullFromHere,
362
+ disableUnique,
363
+ fieldPrefix,
364
+ fields: field.fields,
365
+ forceLocalized,
366
+ indexes,
367
+ locales,
368
+ localesColumns,
369
+ localesIndexes,
370
+ newTableName,
371
+ parentTableName,
372
+ relationships,
373
+ relationsToBuild,
374
+ rootRelationsToBuild,
375
+ rootTableIDColType,
376
+ rootTableName,
377
+ uniqueRelationships,
378
+ versions,
379
+ withinLocalizedArrayOrBlock
380
+ });
381
+ if (rowHasLocalizedField) {
382
+ hasLocalizedField = true;
383
+ }
384
+ if (rowHasLocalizedRelationshipField) {
385
+ hasLocalizedRelationshipField = true;
386
+ }
387
+ if (rowHasManyTextField) {
388
+ hasManyTextField = true;
389
+ }
390
+ if (rowHasLocalizedManyTextField) {
391
+ hasLocalizedManyTextField = true;
392
+ }
393
+ if (rowHasManyNumberField) {
394
+ hasManyNumberField = true;
395
+ }
396
+ if (rowHasLocalizedManyNumberField) {
397
+ hasLocalizedManyNumberField = true;
398
+ }
399
+ break;
400
+ }
401
+ case 'date':
402
+ {
403
+ targetTable[fieldName] = withDefault(text(columnName), field);
404
+ break;
405
+ }
442
406
  case 'group':
407
+ case 'tab':
443
408
  {
444
409
  if (!('name' in field)) {
445
410
  const { hasLocalizedField: groupHasLocalizedField, hasLocalizedManyNumberField: groupHasLocalizedManyNumberField, hasLocalizedManyTextField: groupHasLocalizedManyTextField, hasLocalizedRelationshipField: groupHasLocalizedRelationshipField, hasManyNumberField: groupHasManyNumberField, hasManyTextField: groupHasManyTextField } = traverseFields({
@@ -457,19 +422,33 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
457
422
  localesIndexes,
458
423
  newTableName,
459
424
  parentTableName,
460
- relationsToBuild,
461
425
  relationships,
426
+ relationsToBuild,
462
427
  rootRelationsToBuild,
463
428
  rootTableIDColType,
464
429
  rootTableName,
465
- versions
430
+ uniqueRelationships,
431
+ versions,
432
+ withinLocalizedArrayOrBlock
466
433
  });
467
- if (groupHasLocalizedField) hasLocalizedField = true;
468
- if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true;
469
- if (groupHasManyTextField) hasManyTextField = true;
470
- if (groupHasLocalizedManyTextField) hasLocalizedManyTextField = true;
471
- if (groupHasManyNumberField) hasManyNumberField = true;
472
- if (groupHasLocalizedManyNumberField) hasLocalizedManyNumberField = true;
434
+ if (groupHasLocalizedField) {
435
+ hasLocalizedField = true;
436
+ }
437
+ if (groupHasLocalizedRelationshipField) {
438
+ hasLocalizedRelationshipField = true;
439
+ }
440
+ if (groupHasManyTextField) {
441
+ hasManyTextField = true;
442
+ }
443
+ if (groupHasLocalizedManyTextField) {
444
+ hasLocalizedManyTextField = true;
445
+ }
446
+ if (groupHasManyNumberField) {
447
+ hasManyNumberField = true;
448
+ }
449
+ if (groupHasLocalizedManyNumberField) {
450
+ hasLocalizedManyNumberField = true;
451
+ }
473
452
  break;
474
453
  }
475
454
  const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull;
@@ -488,97 +467,168 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
488
467
  localesIndexes,
489
468
  newTableName: `${parentTableName}_${columnName}`,
490
469
  parentTableName,
491
- relationsToBuild,
492
470
  relationships,
471
+ relationsToBuild,
493
472
  rootRelationsToBuild,
494
473
  rootTableIDColType,
495
474
  rootTableName,
496
- versions
475
+ uniqueRelationships,
476
+ versions,
477
+ withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized
497
478
  });
498
- if (groupHasLocalizedField) hasLocalizedField = true;
499
- if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true;
500
- if (groupHasManyTextField) hasManyTextField = true;
501
- if (groupHasLocalizedManyTextField) hasLocalizedManyTextField = true;
502
- if (groupHasManyNumberField) hasManyNumberField = true;
503
- if (groupHasLocalizedManyNumberField) hasLocalizedManyNumberField = true;
479
+ if (groupHasLocalizedField) {
480
+ hasLocalizedField = true;
481
+ }
482
+ if (groupHasLocalizedRelationshipField) {
483
+ hasLocalizedRelationshipField = true;
484
+ }
485
+ if (groupHasManyTextField) {
486
+ hasManyTextField = true;
487
+ }
488
+ if (groupHasLocalizedManyTextField) {
489
+ hasLocalizedManyTextField = true;
490
+ }
491
+ if (groupHasManyNumberField) {
492
+ hasManyNumberField = true;
493
+ }
494
+ if (groupHasLocalizedManyNumberField) {
495
+ hasLocalizedManyNumberField = true;
496
+ }
504
497
  break;
505
498
  }
506
- case 'tabs':
499
+ case 'json':
500
+ case 'richText':
507
501
  {
508
- const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull;
509
- const { hasLocalizedField: tabHasLocalizedField, hasLocalizedManyNumberField: tabHasLocalizedManyNumberField, hasLocalizedManyTextField: tabHasLocalizedManyTextField, hasLocalizedRelationshipField: tabHasLocalizedRelationshipField, hasManyNumberField: tabHasManyNumberField, hasManyTextField: tabHasManyTextField } = traverseFields({
510
- adapter,
511
- columnPrefix,
512
- columns,
513
- disableNotNull: disableNotNullFromHere,
514
- disableUnique,
515
- fieldPrefix,
516
- fields: field.tabs.map((tab)=>({
517
- ...tab,
518
- type: 'tab'
519
- })),
520
- forceLocalized,
521
- indexes,
522
- locales,
523
- localesColumns,
524
- localesIndexes,
525
- newTableName,
526
- parentTableName,
527
- relationsToBuild,
528
- relationships,
529
- rootRelationsToBuild,
530
- rootTableIDColType,
531
- rootTableName,
532
- versions
533
- });
534
- if (tabHasLocalizedField) hasLocalizedField = true;
535
- if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true;
536
- if (tabHasManyTextField) hasManyTextField = true;
537
- if (tabHasLocalizedManyTextField) hasLocalizedManyTextField = true;
538
- if (tabHasManyNumberField) hasManyNumberField = true;
539
- if (tabHasLocalizedManyNumberField) hasLocalizedManyNumberField = true;
502
+ targetTable[fieldName] = withDefault(text(columnName, {
503
+ mode: 'json'
504
+ }), field);
540
505
  break;
541
506
  }
542
- case 'row':
543
- case 'collapsible':
507
+ case 'number':
544
508
  {
545
- const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull;
546
- const { hasLocalizedField: rowHasLocalizedField, hasLocalizedManyNumberField: rowHasLocalizedManyNumberField, hasLocalizedManyTextField: rowHasLocalizedManyTextField, hasLocalizedRelationshipField: rowHasLocalizedRelationshipField, hasManyNumberField: rowHasManyNumberField, hasManyTextField: rowHasManyTextField } = traverseFields({
547
- adapter,
548
- columnPrefix,
549
- columns,
550
- disableNotNull: disableNotNullFromHere,
551
- disableUnique,
552
- fieldPrefix,
553
- fields: field.fields,
554
- forceLocalized,
555
- indexes,
556
- locales,
557
- localesColumns,
558
- localesIndexes,
559
- newTableName,
560
- parentTableName,
561
- relationsToBuild,
562
- relationships,
563
- rootRelationsToBuild,
564
- rootTableIDColType,
565
- rootTableName,
566
- versions
509
+ if (field.hasMany) {
510
+ const isLocalized = Boolean(field.localized && adapter.payload.config.localization) || withinLocalizedArrayOrBlock || forceLocalized;
511
+ if (isLocalized) {
512
+ hasLocalizedManyNumberField = true;
513
+ }
514
+ if (field.index) {
515
+ hasManyNumberField = 'index';
516
+ } else if (!hasManyNumberField) {
517
+ hasManyNumberField = true;
518
+ }
519
+ if (field.unique) {
520
+ throw new InvalidConfiguration('Unique is not supported in Postgres for hasMany number fields.');
521
+ }
522
+ } else {
523
+ targetTable[fieldName] = withDefault(numeric(columnName), field);
524
+ }
525
+ break;
526
+ }
527
+ case 'point':
528
+ {
529
+ break;
530
+ }
531
+ case 'radio':
532
+ case 'select':
533
+ {
534
+ const options = field.options.map((option)=>{
535
+ if (optionIsObject(option)) {
536
+ return option.value;
537
+ }
538
+ return option;
567
539
  });
568
- if (rowHasLocalizedField) hasLocalizedField = true;
569
- if (rowHasLocalizedRelationshipField) hasLocalizedRelationshipField = true;
570
- if (rowHasManyTextField) hasManyTextField = true;
571
- if (rowHasLocalizedManyTextField) hasLocalizedManyTextField = true;
572
- if (rowHasManyNumberField) hasManyNumberField = true;
573
- if (rowHasLocalizedManyNumberField) hasLocalizedManyNumberField = true;
540
+ if (field.type === 'select' && field.hasMany) {
541
+ const selectTableName = createTableName({
542
+ adapter,
543
+ config: field,
544
+ parentTableName: newTableName,
545
+ prefix: `${newTableName}_`,
546
+ versionsCustomName: versions
547
+ });
548
+ const baseColumns = {
549
+ order: integer('order').notNull(),
550
+ parent: getIDColumn({
551
+ name: 'parent_id',
552
+ type: parentIDColType,
553
+ notNull: true,
554
+ primaryKey: false
555
+ }),
556
+ value: text('value', {
557
+ enum: options
558
+ })
559
+ };
560
+ const baseExtraConfig = {
561
+ orderIdx: (cols)=>index(`${selectTableName}_order_idx`).on(cols.order),
562
+ parentFk: (cols)=>foreignKey({
563
+ name: `${selectTableName}_parent_fk`,
564
+ columns: [
565
+ cols.parent
566
+ ],
567
+ foreignColumns: [
568
+ adapter.tables[parentTableName].id
569
+ ]
570
+ }).onDelete('cascade'),
571
+ parentIdx: (cols)=>index(`${selectTableName}_parent_idx`).on(cols.parent)
572
+ };
573
+ const isLocalized = Boolean(field.localized && adapter.payload.config.localization) || withinLocalizedArrayOrBlock || forceLocalized;
574
+ if (isLocalized) {
575
+ baseColumns.locale = text('locale', {
576
+ enum: locales
577
+ }).notNull();
578
+ baseExtraConfig.localeIdx = (cols)=>index(`${selectTableName}_locale_idx`).on(cols.locale);
579
+ }
580
+ if (field.index) {
581
+ baseExtraConfig.value = (cols)=>index(`${selectTableName}_value_idx`).on(cols.value);
582
+ }
583
+ buildTable({
584
+ adapter,
585
+ baseColumns,
586
+ baseExtraConfig,
587
+ disableNotNull,
588
+ disableUnique,
589
+ fields: [],
590
+ rootTableName,
591
+ tableName: selectTableName,
592
+ versions
593
+ });
594
+ relationsToBuild.set(fieldName, {
595
+ type: 'many',
596
+ // selects have their own localized table, independent of the base table.
597
+ localized: false,
598
+ target: selectTableName
599
+ });
600
+ adapter.relations[`relations_${selectTableName}`] = relations(adapter.tables[selectTableName], ({ one })=>({
601
+ parent: one(adapter.tables[parentTableName], {
602
+ fields: [
603
+ adapter.tables[selectTableName].parent
604
+ ],
605
+ references: [
606
+ adapter.tables[parentTableName].id
607
+ ],
608
+ relationName: fieldName
609
+ })
610
+ }));
611
+ } else {
612
+ targetTable[fieldName] = withDefault(text(columnName, {
613
+ enum: options
614
+ }), field);
615
+ }
574
616
  break;
575
617
  }
576
618
  case 'relationship':
577
619
  case 'upload':
578
620
  if (Array.isArray(field.relationTo)) {
579
- field.relationTo.forEach((relation)=>relationships.add(relation));
580
- } else if (field.type === 'relationship' && field.hasMany) {
621
+ field.relationTo.forEach((relation)=>{
622
+ relationships.add(relation);
623
+ if (field.unique && !disableUnique && !disableRelsTableUnique) {
624
+ uniqueRelationships.add(relation);
625
+ }
626
+ });
627
+ } else if (field.hasMany) {
581
628
  relationships.add(field.relationTo);
629
+ if (field.unique && !disableUnique && !disableRelsTableUnique) {
630
+ uniqueRelationships.add(field.relationTo);
631
+ }
582
632
  } else {
583
633
  // simple relationships get a column on the targetTable with a foreign key to the relationTo table
584
634
  const relationshipConfig = adapter.payload.collections[field.relationTo].config;
@@ -586,8 +636,12 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
586
636
  // get the id type of the related collection
587
637
  let colType = 'integer';
588
638
  const relatedCollectionCustomID = relationshipConfig.fields.find((field)=>fieldAffectsData(field) && field.name === 'id');
589
- if (relatedCollectionCustomID?.type === 'number') colType = 'numeric';
590
- if (relatedCollectionCustomID?.type === 'text') colType = 'text';
639
+ if (relatedCollectionCustomID?.type === 'number') {
640
+ colType = 'numeric';
641
+ }
642
+ if (relatedCollectionCustomID?.type === 'text') {
643
+ colType = 'text';
644
+ }
591
645
  // make the foreign key column for relationship using the correct id column type
592
646
  targetTable[fieldName] = getIDColumn({
593
647
  name: `${columnName}_id`,
@@ -599,7 +653,7 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
599
653
  // add relationship to table
600
654
  relationsToBuild.set(fieldName, {
601
655
  type: 'one',
602
- localized: adapter.payload.config.localization && field.localized,
656
+ localized: adapter.payload.config.localization && (field.localized || forceLocalized),
603
657
  target: tableName
604
658
  });
605
659
  // add notNull when not required
@@ -608,10 +662,80 @@ export const traverseFields = ({ adapter, columnPrefix, columns, disableNotNull,
608
662
  }
609
663
  break;
610
664
  }
611
- if (adapter.payload.config.localization && field.localized) {
665
+ if (Boolean(field.localized && adapter.payload.config.localization) || withinLocalizedArrayOrBlock) {
612
666
  hasLocalizedRelationshipField = true;
613
667
  }
614
668
  break;
669
+ case 'tabs':
670
+ {
671
+ const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull;
672
+ const { hasLocalizedField: tabHasLocalizedField, hasLocalizedManyNumberField: tabHasLocalizedManyNumberField, hasLocalizedManyTextField: tabHasLocalizedManyTextField, hasLocalizedRelationshipField: tabHasLocalizedRelationshipField, hasManyNumberField: tabHasManyNumberField, hasManyTextField: tabHasManyTextField } = traverseFields({
673
+ adapter,
674
+ columnPrefix,
675
+ columns,
676
+ disableNotNull: disableNotNullFromHere,
677
+ disableUnique,
678
+ fieldPrefix,
679
+ fields: field.tabs.map((tab)=>({
680
+ ...tab,
681
+ type: 'tab'
682
+ })),
683
+ forceLocalized,
684
+ indexes,
685
+ locales,
686
+ localesColumns,
687
+ localesIndexes,
688
+ newTableName,
689
+ parentTableName,
690
+ relationships,
691
+ relationsToBuild,
692
+ rootRelationsToBuild,
693
+ rootTableIDColType,
694
+ rootTableName,
695
+ uniqueRelationships,
696
+ versions,
697
+ withinLocalizedArrayOrBlock
698
+ });
699
+ if (tabHasLocalizedField) {
700
+ hasLocalizedField = true;
701
+ }
702
+ if (tabHasLocalizedRelationshipField) {
703
+ hasLocalizedRelationshipField = true;
704
+ }
705
+ if (tabHasManyTextField) {
706
+ hasManyTextField = true;
707
+ }
708
+ if (tabHasLocalizedManyTextField) {
709
+ hasLocalizedManyTextField = true;
710
+ }
711
+ if (tabHasManyNumberField) {
712
+ hasManyNumberField = true;
713
+ }
714
+ if (tabHasLocalizedManyNumberField) {
715
+ hasLocalizedManyNumberField = true;
716
+ }
717
+ break;
718
+ }
719
+ case 'text':
720
+ {
721
+ if (field.hasMany) {
722
+ const isLocalized = Boolean(field.localized && adapter.payload.config.localization) || withinLocalizedArrayOrBlock || forceLocalized;
723
+ if (isLocalized) {
724
+ hasLocalizedManyTextField = true;
725
+ }
726
+ if (field.index) {
727
+ hasManyTextField = 'index';
728
+ } else if (!hasManyTextField) {
729
+ hasManyTextField = true;
730
+ }
731
+ if (field.unique) {
732
+ throw new InvalidConfiguration('Unique is not supported in SQLite for hasMany text fields.');
733
+ }
734
+ } else {
735
+ targetTable[fieldName] = withDefault(text(columnName), field);
736
+ }
737
+ break;
738
+ }
615
739
  default:
616
740
  break;
617
741
  }