Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
704 lines
24 KiB
Plaintext
704 lines
24 KiB
Plaintext
import { InvalidConfiguration } from 'payload';
|
|
import toSnakeCase from 'to-snake-case';
|
|
import { createTableName } from '../createTableName.js';
|
|
import { buildForeignKeyName } from '../utilities/buildForeignKeyName.js';
|
|
import { buildIndexName } from '../utilities/buildIndexName.js';
|
|
import { traverseFields } from './traverseFields.js';
|
|
export const buildTable = ({ adapter, baseColumns = {}, baseForeignKeys = {}, baseIndexes = {}, blocksTableNameMap, compoundIndexes, disableNotNull, disableRelsTableUnique = false, disableUnique = false, fields, parentIsLocalized, rootRelationships, rootRelationsToBuild, rootTableIDColType, rootTableName: incomingRootTableName, rootUniqueRelationships, setColumnID, tableName, timestamps, versions, withinLocalizedArrayOrBlock })=>{
|
|
const isRoot = !incomingRootTableName;
|
|
const rootTableName = incomingRootTableName || tableName;
|
|
const columns = baseColumns;
|
|
const indexes = baseIndexes;
|
|
const localesColumns = {};
|
|
const localesIndexes = {};
|
|
let localesTable;
|
|
let textsTable;
|
|
let numbersTable;
|
|
// Relationships to the base collection
|
|
const relationships = rootRelationships || new Set();
|
|
// Unique relationships to the base collection
|
|
const uniqueRelationships = rootUniqueRelationships || new Set();
|
|
let relationshipsTable;
|
|
// Drizzle relations
|
|
const relationsToBuild = new Map();
|
|
const idColType = setColumnID({
|
|
adapter,
|
|
columns,
|
|
fields
|
|
});
|
|
const { hasLocalizedField, hasLocalizedManyNumberField, hasLocalizedManyTextField, hasLocalizedRelationshipField, hasManyNumberField, hasManyTextField } = traverseFields({
|
|
adapter,
|
|
blocksTableNameMap,
|
|
columns,
|
|
disableNotNull,
|
|
disableRelsTableUnique,
|
|
disableUnique,
|
|
fields,
|
|
indexes,
|
|
localesColumns,
|
|
localesIndexes,
|
|
newTableName: tableName,
|
|
parentIsLocalized,
|
|
parentTableName: tableName,
|
|
relationships,
|
|
relationsToBuild,
|
|
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
|
|
rootTableIDColType: rootTableIDColType || idColType,
|
|
rootTableName,
|
|
setColumnID,
|
|
uniqueRelationships,
|
|
versions,
|
|
withinLocalizedArrayOrBlock
|
|
});
|
|
// split the relationsToBuild by localized and non-localized
|
|
const localizedRelations = new Map();
|
|
const nonLocalizedRelations = new Map();
|
|
relationsToBuild.forEach(({ type, localized, relationName, target }, key)=>{
|
|
const map = localized ? localizedRelations : nonLocalizedRelations;
|
|
map.set(key, {
|
|
type,
|
|
relationName,
|
|
target
|
|
});
|
|
});
|
|
if (timestamps) {
|
|
columns.createdAt = {
|
|
name: 'created_at',
|
|
type: 'timestamp',
|
|
defaultNow: true,
|
|
mode: 'string',
|
|
notNull: true,
|
|
precision: 3,
|
|
withTimezone: true
|
|
};
|
|
columns.updatedAt = {
|
|
name: 'updated_at',
|
|
type: 'timestamp',
|
|
defaultNow: true,
|
|
mode: 'string',
|
|
notNull: true,
|
|
precision: 3,
|
|
withTimezone: true
|
|
};
|
|
}
|
|
const table = {
|
|
name: tableName,
|
|
columns,
|
|
foreignKeys: baseForeignKeys,
|
|
indexes
|
|
};
|
|
adapter.rawTables[tableName] = table;
|
|
if (hasLocalizedField || localizedRelations.size) {
|
|
const localeTableName = `${tableName}${adapter.localesSuffix}`;
|
|
adapter.rawTables[localeTableName] = localesTable;
|
|
localesColumns.id = {
|
|
name: 'id',
|
|
type: 'serial',
|
|
primaryKey: true
|
|
};
|
|
localesColumns._locale = {
|
|
name: '_locale',
|
|
type: 'enum',
|
|
locale: true,
|
|
notNull: true
|
|
};
|
|
localesColumns._parentID = {
|
|
name: '_parent_id',
|
|
type: idColType,
|
|
notNull: true
|
|
};
|
|
localesIndexes._localeParent = {
|
|
name: buildIndexName({
|
|
name: `${localeTableName}_locale_parent_id_unique`,
|
|
adapter,
|
|
appendSuffix: false
|
|
}),
|
|
on: [
|
|
'_locale',
|
|
'_parentID'
|
|
],
|
|
unique: true
|
|
};
|
|
localesTable = {
|
|
name: localeTableName,
|
|
columns: localesColumns,
|
|
foreignKeys: {
|
|
_parentIdFk: {
|
|
name: buildForeignKeyName({
|
|
name: `${localeTableName}_parent_id`,
|
|
adapter
|
|
}),
|
|
columns: [
|
|
'_parentID'
|
|
],
|
|
foreignColumns: [
|
|
{
|
|
name: 'id',
|
|
table: tableName
|
|
}
|
|
],
|
|
onDelete: 'cascade'
|
|
}
|
|
},
|
|
indexes: localesIndexes
|
|
};
|
|
adapter.rawTables[localeTableName] = localesTable;
|
|
const localeRelations = {
|
|
_parentID: {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: '_parentID',
|
|
table: localeTableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: '_locales',
|
|
to: tableName
|
|
}
|
|
};
|
|
localizedRelations.forEach(({ type, target }, key)=>{
|
|
if (type === 'one') {
|
|
localeRelations[key] = {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: key,
|
|
table: localeTableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: key,
|
|
to: target
|
|
};
|
|
}
|
|
if (type === 'many') {
|
|
localeRelations[key] = {
|
|
type: 'many',
|
|
relationName: key,
|
|
to: target
|
|
};
|
|
}
|
|
});
|
|
adapter.rawRelations[localeTableName] = localeRelations;
|
|
}
|
|
if (compoundIndexes) {
|
|
for (const index of compoundIndexes){
|
|
let someLocalized = null;
|
|
const columns = [];
|
|
const getTableToUse = ()=>{
|
|
if (someLocalized) {
|
|
return localesTable;
|
|
}
|
|
return table;
|
|
};
|
|
for (const { path, pathHasLocalized } of index.fields){
|
|
if (someLocalized === null) {
|
|
someLocalized = pathHasLocalized;
|
|
}
|
|
if (someLocalized !== pathHasLocalized) {
|
|
throw new InvalidConfiguration(`Compound indexes within localized and non localized fields are not supported in SQL. Expected ${path} to be ${someLocalized ? 'non' : ''} localized.`);
|
|
}
|
|
const columnPath = path.replaceAll('.', '_');
|
|
if (!getTableToUse().columns[columnPath]) {
|
|
throw new InvalidConfiguration(`Column ${columnPath} for compound index on ${path} was not found in the ${getTableToUse().name} table.`);
|
|
}
|
|
columns.push(columnPath);
|
|
}
|
|
if (someLocalized) {
|
|
columns.push('_locale');
|
|
}
|
|
let name = columns.join('_');
|
|
// truncate against the limit, buildIndexName will handle collisions
|
|
if (name.length > 63) {
|
|
name = 'compound_index';
|
|
}
|
|
const indexName = buildIndexName({
|
|
name,
|
|
adapter
|
|
});
|
|
getTableToUse().indexes[indexName] = {
|
|
name: indexName,
|
|
on: columns,
|
|
unique: disableUnique ? false : index.unique
|
|
};
|
|
}
|
|
}
|
|
if (isRoot) {
|
|
if (hasManyTextField) {
|
|
const textsTableName = `${rootTableName}_texts`;
|
|
adapter.rawTables[textsTableName] = textsTable;
|
|
const columns = {
|
|
id: {
|
|
name: 'id',
|
|
type: 'serial',
|
|
primaryKey: true
|
|
},
|
|
order: {
|
|
name: 'order',
|
|
type: 'integer',
|
|
notNull: true
|
|
},
|
|
parent: {
|
|
name: 'parent_id',
|
|
type: idColType,
|
|
notNull: true
|
|
},
|
|
path: {
|
|
name: 'path',
|
|
type: 'varchar',
|
|
notNull: true
|
|
},
|
|
text: {
|
|
name: 'text',
|
|
type: 'varchar'
|
|
}
|
|
};
|
|
if (hasLocalizedManyTextField) {
|
|
columns.locale = {
|
|
name: 'locale',
|
|
type: 'enum',
|
|
locale: true
|
|
};
|
|
}
|
|
const textsTableIndexes = {
|
|
orderParentIdx: {
|
|
name: buildIndexName({
|
|
name: `${textsTableName}_order_parent`,
|
|
adapter,
|
|
appendSuffix: false
|
|
}),
|
|
on: [
|
|
'order',
|
|
'parent'
|
|
]
|
|
}
|
|
};
|
|
if (hasManyTextField === 'index') {
|
|
textsTableIndexes.text_idx = {
|
|
name: buildIndexName({
|
|
name: `${textsTableName}_text`,
|
|
adapter
|
|
}),
|
|
on: 'text'
|
|
};
|
|
}
|
|
if (hasLocalizedManyTextField) {
|
|
textsTableIndexes.localeParent = {
|
|
name: buildIndexName({
|
|
name: `${textsTableName}_locale_parent`,
|
|
adapter,
|
|
appendSuffix: false
|
|
}),
|
|
on: [
|
|
'locale',
|
|
'parent'
|
|
]
|
|
};
|
|
}
|
|
textsTable = {
|
|
name: textsTableName,
|
|
columns,
|
|
foreignKeys: {
|
|
parentFk: {
|
|
name: buildForeignKeyName({
|
|
name: `${textsTableName}_parent`,
|
|
adapter
|
|
}),
|
|
columns: [
|
|
'parent'
|
|
],
|
|
foreignColumns: [
|
|
{
|
|
name: 'id',
|
|
table: tableName
|
|
}
|
|
],
|
|
onDelete: 'cascade'
|
|
}
|
|
},
|
|
indexes: textsTableIndexes
|
|
};
|
|
adapter.rawTables[textsTableName] = textsTable;
|
|
adapter.rawRelations[textsTableName] = {
|
|
parent: {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: 'parent',
|
|
table: textsTableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: '_texts',
|
|
to: tableName
|
|
}
|
|
};
|
|
}
|
|
if (hasManyNumberField) {
|
|
const numbersTableName = `${rootTableName}_numbers`;
|
|
adapter.rawTables[numbersTableName] = numbersTable;
|
|
const columns = {
|
|
id: {
|
|
name: 'id',
|
|
type: 'serial',
|
|
primaryKey: true
|
|
},
|
|
number: {
|
|
name: 'number',
|
|
type: 'numeric'
|
|
},
|
|
order: {
|
|
name: 'order',
|
|
type: 'integer',
|
|
notNull: true
|
|
},
|
|
parent: {
|
|
name: 'parent_id',
|
|
type: idColType,
|
|
notNull: true
|
|
},
|
|
path: {
|
|
name: 'path',
|
|
type: 'varchar',
|
|
notNull: true
|
|
}
|
|
};
|
|
if (hasLocalizedManyNumberField) {
|
|
columns.locale = {
|
|
name: 'locale',
|
|
type: 'enum',
|
|
locale: true
|
|
};
|
|
}
|
|
const numbersTableIndexes = {
|
|
orderParentIdx: {
|
|
name: buildIndexName({
|
|
name: `${numbersTableName}_order_parent`,
|
|
adapter
|
|
}),
|
|
on: [
|
|
'order',
|
|
'parent'
|
|
]
|
|
}
|
|
};
|
|
if (hasManyNumberField === 'index') {
|
|
numbersTableIndexes.numberIdx = {
|
|
name: buildIndexName({
|
|
name: `${numbersTableName}_number`,
|
|
adapter
|
|
}),
|
|
on: 'number'
|
|
};
|
|
}
|
|
if (hasLocalizedManyNumberField) {
|
|
numbersTableIndexes.localeParent = {
|
|
name: buildIndexName({
|
|
name: `${numbersTableName}_locale_parent`,
|
|
adapter,
|
|
appendSuffix: false
|
|
}),
|
|
on: [
|
|
'locale',
|
|
'parent'
|
|
]
|
|
};
|
|
}
|
|
numbersTable = {
|
|
name: numbersTableName,
|
|
columns,
|
|
foreignKeys: {
|
|
parentFk: {
|
|
name: buildForeignKeyName({
|
|
name: `${numbersTableName}_parent`,
|
|
adapter
|
|
}),
|
|
columns: [
|
|
'parent'
|
|
],
|
|
foreignColumns: [
|
|
{
|
|
name: 'id',
|
|
table: tableName
|
|
}
|
|
],
|
|
onDelete: 'cascade'
|
|
}
|
|
},
|
|
indexes: numbersTableIndexes
|
|
};
|
|
adapter.rawTables[numbersTableName] = numbersTable;
|
|
adapter.rawRelations[numbersTableName] = {
|
|
parent: {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: 'parent',
|
|
table: numbersTableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: '_numbers',
|
|
to: tableName
|
|
}
|
|
};
|
|
}
|
|
if (relationships.size) {
|
|
const relationshipColumns = {
|
|
id: {
|
|
name: 'id',
|
|
type: 'serial',
|
|
primaryKey: true
|
|
},
|
|
order: {
|
|
name: 'order',
|
|
type: 'integer'
|
|
},
|
|
parent: {
|
|
name: 'parent_id',
|
|
type: idColType,
|
|
notNull: true
|
|
},
|
|
path: {
|
|
name: 'path',
|
|
type: 'varchar',
|
|
notNull: true
|
|
}
|
|
};
|
|
if (hasLocalizedRelationshipField) {
|
|
relationshipColumns.locale = {
|
|
name: 'locale',
|
|
type: 'enum',
|
|
locale: true
|
|
};
|
|
}
|
|
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`;
|
|
const relationshipIndexes = {
|
|
order: {
|
|
name: buildIndexName({
|
|
name: `${relationshipsTableName}_order`,
|
|
adapter
|
|
}),
|
|
on: 'order'
|
|
},
|
|
parentIdx: {
|
|
name: buildIndexName({
|
|
name: `${relationshipsTableName}_parent`,
|
|
adapter
|
|
}),
|
|
on: 'parent'
|
|
},
|
|
pathIdx: {
|
|
name: buildIndexName({
|
|
name: `${relationshipsTableName}_path`,
|
|
adapter
|
|
}),
|
|
on: 'path'
|
|
}
|
|
};
|
|
if (hasLocalizedRelationshipField) {
|
|
relationshipIndexes.localeIdx = {
|
|
name: buildIndexName({
|
|
name: `${relationshipsTableName}_locale`,
|
|
adapter
|
|
}),
|
|
on: 'locale'
|
|
};
|
|
}
|
|
const relationshipForeignKeys = {
|
|
parentFk: {
|
|
name: buildForeignKeyName({
|
|
name: `${relationshipsTableName}_parent`,
|
|
adapter
|
|
}),
|
|
columns: [
|
|
'parent'
|
|
],
|
|
foreignColumns: [
|
|
{
|
|
name: 'id',
|
|
table: tableName
|
|
}
|
|
],
|
|
onDelete: 'cascade'
|
|
}
|
|
};
|
|
relationships.forEach((relationTo)=>{
|
|
const relationshipConfig = adapter.payload.collections[relationTo].config;
|
|
const formattedRelationTo = createTableName({
|
|
adapter,
|
|
config: relationshipConfig,
|
|
throwValidationError: true
|
|
});
|
|
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer';
|
|
const relatedCollectionCustomIDType = adapter.payload.collections[relationshipConfig.slug]?.customIDType;
|
|
if (relatedCollectionCustomIDType === 'number') {
|
|
colType = 'numeric';
|
|
}
|
|
if (relatedCollectionCustomIDType === 'text') {
|
|
colType = 'varchar';
|
|
}
|
|
const colName = `${relationTo}ID`;
|
|
relationshipColumns[colName] = {
|
|
name: `${formattedRelationTo}_id`,
|
|
type: colType
|
|
};
|
|
relationshipForeignKeys[`${relationTo}IdFk`] = {
|
|
name: buildForeignKeyName({
|
|
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}`,
|
|
adapter
|
|
}),
|
|
columns: [
|
|
colName
|
|
],
|
|
foreignColumns: [
|
|
{
|
|
name: 'id',
|
|
table: formattedRelationTo
|
|
}
|
|
],
|
|
onDelete: 'cascade'
|
|
};
|
|
const indexColumns = [
|
|
colName
|
|
];
|
|
const unique = !disableUnique && uniqueRelationships.has(relationTo);
|
|
if (unique) {
|
|
indexColumns.push('path');
|
|
}
|
|
if (hasLocalizedRelationshipField) {
|
|
indexColumns.push('locale');
|
|
}
|
|
const indexName = buildIndexName({
|
|
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
|
|
adapter
|
|
});
|
|
relationshipIndexes[indexName] = {
|
|
name: indexName,
|
|
on: indexColumns,
|
|
unique
|
|
};
|
|
});
|
|
relationshipsTable = {
|
|
name: relationshipsTableName,
|
|
columns: relationshipColumns,
|
|
foreignKeys: relationshipForeignKeys,
|
|
indexes: relationshipIndexes
|
|
};
|
|
adapter.rawTables[relationshipsTableName] = relationshipsTable;
|
|
const relationshipsTableRelations = {
|
|
parent: {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: 'parent',
|
|
table: relationshipsTableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: '_rels',
|
|
to: tableName
|
|
}
|
|
};
|
|
relationships.forEach((relationTo)=>{
|
|
const relatedTableName = createTableName({
|
|
adapter,
|
|
config: adapter.payload.collections[relationTo].config,
|
|
throwValidationError: true
|
|
});
|
|
const idColumnName = `${relationTo}ID`;
|
|
relationshipsTableRelations[idColumnName] = {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: idColumnName,
|
|
table: relationshipsTableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: relationTo,
|
|
to: relatedTableName
|
|
};
|
|
});
|
|
adapter.rawRelations[relationshipsTableName] = relationshipsTableRelations;
|
|
}
|
|
}
|
|
const tableRelations = {};
|
|
nonLocalizedRelations.forEach(({ type, relationName, target }, key)=>{
|
|
if (type === 'one') {
|
|
tableRelations[key] = {
|
|
type: 'one',
|
|
fields: [
|
|
{
|
|
name: key,
|
|
table: tableName
|
|
}
|
|
],
|
|
references: [
|
|
'id'
|
|
],
|
|
relationName: key,
|
|
to: target
|
|
};
|
|
}
|
|
if (type === 'many') {
|
|
tableRelations[key] = {
|
|
type: 'many',
|
|
relationName: relationName || key,
|
|
to: target
|
|
};
|
|
}
|
|
});
|
|
if (hasLocalizedField) {
|
|
tableRelations._locales = {
|
|
type: 'many',
|
|
relationName: '_locales',
|
|
to: localesTable.name
|
|
};
|
|
}
|
|
if (isRoot && textsTable) {
|
|
tableRelations._texts = {
|
|
type: 'many',
|
|
relationName: '_texts',
|
|
to: textsTable.name
|
|
};
|
|
}
|
|
if (isRoot && numbersTable) {
|
|
tableRelations._numbers = {
|
|
type: 'many',
|
|
relationName: '_numbers',
|
|
to: numbersTable.name
|
|
};
|
|
}
|
|
if (relationships.size && relationshipsTable) {
|
|
tableRelations._rels = {
|
|
type: 'many',
|
|
relationName: '_rels',
|
|
to: relationshipsTable.name
|
|
};
|
|
}
|
|
adapter.rawRelations[tableName] = tableRelations;
|
|
return {
|
|
hasLocalizedManyNumberField,
|
|
hasLocalizedManyTextField,
|
|
hasLocalizedRelationshipField,
|
|
hasManyNumberField,
|
|
hasManyTextField,
|
|
relationsToBuild
|
|
};
|
|
};
|
|
|
|
//# sourceMappingURL=build.js.map |