@payloadcms/db-sqlite 3.0.0-canary.ff8c8fd → 3.0.0

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