diff --git a/packages/db-postgres/src/init.ts b/packages/db-postgres/src/init.ts index ddce9aa922..38a54e43cd 100644 --- a/packages/db-postgres/src/init.ts +++ b/packages/db-postgres/src/init.ts @@ -12,7 +12,7 @@ import { buildTable } from './schema/build' export const init: Init = async function init(this: PostgresAdapter) { if (this.payload.config.localization) { - this.enums._locales = pgEnum( + this.enums.enum__locales = pgEnum( '_locales', // TODO: types out of sync with core, monorepo please // this.payload.config.localization.localeCodes, diff --git a/packages/db-postgres/src/schema/build.ts b/packages/db-postgres/src/schema/build.ts index e812131b80..08cadf4ba4 100644 --- a/packages/db-postgres/src/schema/build.ts +++ b/packages/db-postgres/src/schema/build.ts @@ -33,7 +33,7 @@ type Args = { } type Result = { - arrayBlockRelations: Map + relationsToBuild: Map } export const buildTable = ({ @@ -61,7 +61,7 @@ export const buildTable = ({ const relationships: Set = new Set() let relationshipsTable: GenericTable - const arrayBlockRelations: Map = new Map() + const relationsToBuild: Map = new Map() const idField = fields.find((field) => fieldAffectsData(field) && field.name === 'id') let idColType = 'integer' @@ -87,7 +87,6 @@ export const buildTable = ({ hasManyNumberField, } = traverseFields({ adapter, - arrayBlockRelations, buildRelationships, columns, fields, @@ -96,6 +95,7 @@ export const buildTable = ({ localesIndexes, newTableName: tableName, parentTableName: tableName, + relationsToBuild, relationships, })) @@ -121,7 +121,7 @@ export const buildTable = ({ if (hasLocalizedField) { const localeTableName = `${tableName}_locales` localesColumns.id = serial('id').primaryKey() - localesColumns._locale = adapter.enums._locales('_locale').notNull() + localesColumns._locale = adapter.enums.enum__locales('_locale').notNull() localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id') .references(() => table.id, { onDelete: 'cascade' }) .notNull() @@ -163,7 +163,7 @@ export const buildTable = ({ } if (hasLocalizedManyNumberField) { - columns.locale = adapter.enums._locales('locale') + columns.locale = adapter.enums.enum__locales('locale') } numbersTable = pgTable(numbersTableName, columns, (cols) => { @@ -206,7 +206,7 @@ export const buildTable = ({ } if (hasLocalizedRelationshipField) { - relationshipColumns.locale = adapter.enums._locales('locale') + relationshipColumns.locale = adapter.enums.enum__locales('locale') } relationships.forEach((relationTo) => { @@ -273,7 +273,7 @@ export const buildTable = ({ const tableRelations = relations(table, ({ many }) => { const result: Record> = {} - arrayBlockRelations.forEach((val, key) => { + relationsToBuild.forEach((val, key) => { result[key] = many(adapter.tables[val]) }) @@ -296,5 +296,5 @@ export const buildTable = ({ adapter.relations[`relations_${tableName}`] = tableRelations - return { arrayBlockRelations } + return { relationsToBuild } } diff --git a/packages/db-postgres/src/schema/traverseFields.ts b/packages/db-postgres/src/schema/traverseFields.ts index e27be65a4b..e68755a570 100644 --- a/packages/db-postgres/src/schema/traverseFields.ts +++ b/packages/db-postgres/src/schema/traverseFields.ts @@ -7,6 +7,7 @@ import { relations } from 'drizzle-orm' import { PgNumericBuilder, PgVarcharBuilder, + index, integer, jsonb, numeric, @@ -28,7 +29,6 @@ import { parentIDColumnMap } from './parentIDColumnMap' type Args = { adapter: PostgresAdapter - arrayBlockRelations: Map buildRelationships: boolean columnPrefix?: string columns: Record @@ -40,6 +40,7 @@ type Args = { localesIndexes: Record IndexBuilder> newTableName: string parentTableName: string + relationsToBuild: Map relationships: Set } @@ -52,7 +53,6 @@ type Result = { export const traverseFields = ({ adapter, - arrayBlockRelations, buildRelationships, columnPrefix, columns, @@ -64,6 +64,7 @@ export const traverseFields = ({ localesIndexes, newTableName, parentTableName, + relationsToBuild, relationships, }: Args): Result => { let hasLocalizedField = false @@ -157,7 +158,7 @@ export const traverseFields = ({ } case 'select': { - const enumName = `${newTableName}_${columnPrefix || ''}${toSnakeCase(field.name)}` + const enumName = `enum_${newTableName}_${columnPrefix || ''}${toSnakeCase(field.name)}` const fieldName = `${fieldPrefix || ''}${field.name}` adapter.enums[enumName] = pgEnum( @@ -172,7 +173,56 @@ export const traverseFields = ({ ) if (field.hasMany) { - // build table here + const baseColumns: Record = { + order: integer('order').notNull(), + parent: parentIDColumnMap[parentIDColType]('parent_id') + .references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }) + .notNull(), + value: adapter.enums[enumName]('value'), + } + + const baseExtraConfig: Record< + string, + (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder + > = {} + + if (field.localized) { + baseColumns.locale = adapter.enums.enum__locales('locale').notNull() + baseExtraConfig.parentOrderLocale = (cols) => + unique().on(cols.parent, cols.order, cols.locale) + } else { + baseExtraConfig.parent = (cols) => index('parent_idx').on(cols.parent) + baseExtraConfig.order = (cols) => index('order_idx').on(cols.order) + } + + if (field.index) { + baseExtraConfig.value = (cols) => index('value_idx').on(cols.value) + } + + const selectTableName = `${newTableName}_${toSnakeCase(fieldName)}` + + buildTable({ + adapter, + baseColumns, + baseExtraConfig, + fields: [], + tableName: selectTableName, + }) + + relationsToBuild.set(fieldName, selectTableName) + + const selectTableRelations = relations(adapter.tables[selectTableName], ({ one }) => { + const result: Record> = { + parent: one(adapter.tables[parentTableName], { + fields: [adapter.tables[selectTableName].parent], + references: [adapter.tables[parentTableName].id], + }), + } + + return result + }) + + adapter.relations[`relation_${selectTableName}`] = selectTableRelations } else { targetTable[fieldName] = adapter.enums[enumName](fieldName) } @@ -206,7 +256,7 @@ export const traverseFields = ({ const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}` - const { arrayBlockRelations: subArrayBlockRelations } = buildTable({ + const { relationsToBuild: subRelationsToBuild } = buildTable({ adapter, baseColumns, baseExtraConfig, @@ -214,7 +264,7 @@ export const traverseFields = ({ tableName: arrayTableName, }) - arrayBlockRelations.set(`${fieldPrefix || ''}${field.name}`, arrayTableName) + relationsToBuild.set(`${fieldPrefix || ''}${field.name}`, arrayTableName) const arrayTableRelations = relations(adapter.tables[arrayTableName], ({ many, one }) => { const result: Record> = { @@ -228,7 +278,7 @@ export const traverseFields = ({ result._locales = many(adapter.tables[`${arrayTableName}_locales`]) } - subArrayBlockRelations.forEach((val, key) => { + subRelationsToBuild.forEach((val, key) => { result[key] = many(adapter.tables[val]) }) @@ -266,7 +316,7 @@ export const traverseFields = ({ unique().on(cols._parentID, cols._path, cols._order) } - const { arrayBlockRelations: subArrayBlockRelations } = buildTable({ + const { relationsToBuild: subRelationsToBuild } = buildTable({ adapter, baseColumns, baseExtraConfig, @@ -288,7 +338,7 @@ export const traverseFields = ({ result._locales = many(adapter.tables[`${blockTableName}_locales`]) } - subArrayBlockRelations.forEach((val, key) => { + subRelationsToBuild.forEach((val, key) => { result[key] = many(adapter.tables[val]) }) @@ -299,7 +349,7 @@ export const traverseFields = ({ adapter.relations[`relations_${blockTableName}`] = blockTableRelations } - arrayBlockRelations.set(`_blocks_${block.slug}`, blockTableName) + relationsToBuild.set(`_blocks_${block.slug}`, blockTableName) }) break @@ -313,7 +363,6 @@ export const traverseFields = ({ hasManyNumberField: groupHasManyNumberField, } = traverseFields({ adapter, - arrayBlockRelations, buildRelationships, columnPrefix: `${columnName}_`, columns, @@ -325,6 +374,7 @@ export const traverseFields = ({ localesIndexes, newTableName: `${parentTableName}_${toSnakeCase(field.name)}`, parentTableName, + relationsToBuild, relationships, }) @@ -345,7 +395,6 @@ export const traverseFields = ({ hasManyNumberField: tabHasManyNumberField, } = traverseFields({ adapter, - arrayBlockRelations, buildRelationships, columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`, columns, @@ -356,6 +405,7 @@ export const traverseFields = ({ localesIndexes, newTableName: `${parentTableName}_${toSnakeCase(tab.name)}`, parentTableName, + relationsToBuild, relationships, }) @@ -366,7 +416,6 @@ export const traverseFields = ({ } else { ;({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({ adapter, - arrayBlockRelations, buildRelationships, columnPrefix, columns, @@ -377,6 +426,7 @@ export const traverseFields = ({ localesIndexes, newTableName: parentTableName, parentTableName, + relationsToBuild, relationships, })) } @@ -393,7 +443,6 @@ export const traverseFields = ({ hasManyNumberField, } = traverseFields({ adapter, - arrayBlockRelations, buildRelationships, columnPrefix, columns, @@ -404,6 +453,7 @@ export const traverseFields = ({ localesIndexes, newTableName: parentTableName, parentTableName, + relationsToBuild, relationships, })) break diff --git a/packages/db-postgres/src/transform/write/index.ts b/packages/db-postgres/src/transform/write/index.ts index 6d1f672b31..9dffc1ea3b 100644 --- a/packages/db-postgres/src/transform/write/index.ts +++ b/packages/db-postgres/src/transform/write/index.ts @@ -22,6 +22,7 @@ export const transformForWrite = ({ data, fields, path = '', tableName }: Args): numbers: [], relationships: [], row: {}, + selects: {}, } // This function is responsible for building up the @@ -39,6 +40,7 @@ export const transformForWrite = ({ data, fields, path = '', tableName }: Args): path, relationships: rowToInsert.relationships, row: rowToInsert.row, + selects: rowToInsert.selects, }) return rowToInsert diff --git a/packages/db-postgres/src/transform/write/selects.ts b/packages/db-postgres/src/transform/write/selects.ts new file mode 100644 index 0000000000..daf36bb18f --- /dev/null +++ b/packages/db-postgres/src/transform/write/selects.ts @@ -0,0 +1,28 @@ +/* eslint-disable no-param-reassign */ +import { isArrayOfRows } from '../../utilities/isArrayOfRows' + +type Args = { + data: unknown + locale?: string +} + +export const transformSelects = ({ data, locale }: Args) => { + const newRows: Record[] = [] + + if (isArrayOfRows(data)) { + data.forEach((value, i) => { + const newRow: Record = { + order: i + 1, + value, + } + + if (locale) { + newRow.locale = locale + } + + newRows.push(newRow) + }) + } + + return newRows +} diff --git a/packages/db-postgres/src/transform/write/traverseFields.ts b/packages/db-postgres/src/transform/write/traverseFields.ts index 2002c12b7f..04d0819009 100644 --- a/packages/db-postgres/src/transform/write/traverseFields.ts +++ b/packages/db-postgres/src/transform/write/traverseFields.ts @@ -11,6 +11,7 @@ import { transformArray } from './array' import { transformBlocks } from './blocks' import { transformNumbers } from './numbers' import { transformRelationship } from './relationships' +import { transformSelects } from './selects' type Args = { arrays: { @@ -33,6 +34,9 @@ type Args = { path: string relationships: Record[] row: Record + selects: { + [tableName: string]: Record[] + } } export const traverseFields = ({ @@ -50,6 +54,7 @@ export const traverseFields = ({ path, relationships, row, + selects, }: Args) => { fields.forEach((field) => { let columnName = '' @@ -150,6 +155,7 @@ export const traverseFields = ({ path: `${path || ''}${field.name}.`, relationships, row, + selects, }) }) } else { @@ -167,6 +173,7 @@ export const traverseFields = ({ path: `${path || ''}${field.name}.`, relationships, row, + selects, }) } } @@ -195,6 +202,7 @@ export const traverseFields = ({ path: `${path || ''}${tab.name}.`, relationships, row, + selects, }) }) } else { @@ -212,6 +220,7 @@ export const traverseFields = ({ path: `${path || ''}${tab.name}.`, relationships, row, + selects, }) } } @@ -230,6 +239,7 @@ export const traverseFields = ({ path, relationships, row, + selects, }) } }) @@ -250,6 +260,7 @@ export const traverseFields = ({ path, relationships, row, + selects, }) } @@ -313,6 +324,34 @@ export const traverseFields = ({ return } + if (field.type === 'select' && field.hasMany && Array.isArray(fieldData)) { + const selectTableName = `${newTableName}_${toSnakeCase(field.name)}` + if (!selects[selectTableName]) selects[selectTableName] = [] + + if (field.localized) { + if (typeof data[field.name] === 'object' && data[field.name] !== null) { + Object.entries(data[field.name]).forEach(([localeKey, localeData]) => { + if (Array.isArray(localeData)) { + const newRows = transformSelects({ + data: localeData, + locale: localeKey, + }) + + selects[selectTableName] = selects[selectTableName].concat(newRows) + } + }) + } + } else { + const newRows = transformSelects({ + data: data[field.name], + }) + + selects[selectTableName] = selects[selectTableName].concat(newRows) + } + + return + } + if (fieldAffectsData(field)) { const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = [] diff --git a/packages/db-postgres/src/transform/write/types.ts b/packages/db-postgres/src/transform/write/types.ts index 6830f94214..3fcaa3ff04 100644 --- a/packages/db-postgres/src/transform/write/types.ts +++ b/packages/db-postgres/src/transform/write/types.ts @@ -32,4 +32,7 @@ export type RowToInsert = { numbers: Record[] relationships: Record[] row: Record + selects: { + [tableName: string]: Record[] + } } diff --git a/packages/db-postgres/src/upsertRow/index.ts b/packages/db-postgres/src/upsertRow/index.ts index 1816fa7fa3..272ff897ea 100644 --- a/packages/db-postgres/src/upsertRow/index.ts +++ b/packages/db-postgres/src/upsertRow/index.ts @@ -60,6 +60,7 @@ export const upsertRow = async ({ const relationsToInsert: Record[] = [] const numbersToInsert: Record[] = [] const blocksToInsert: { [blockType: string]: BlockRowToInsert[] } = {} + const selectsToInsert: { [selectTableName: string]: Record[] } = {} // Maintain a list of promises to run locale, blocks, and relationships // all in parallel @@ -90,6 +91,18 @@ export const upsertRow = async ({ }) } + // If there are selects, add parent to each, and then + // store by table name and rows + if (Object.keys(rowToInsert.selects).length > 0) { + Object.entries(rowToInsert.selects).forEach(([selectTableName, selectRows]) => { + selectRows.forEach((row) => { + row.parent = insertedRow.id + if (!selectsToInsert[selectTableName]) selectsToInsert[selectTableName] = [] + selectsToInsert[selectTableName].push(row) + }) + }) + } + // If there are blocks, add parent to each, and then // store by table name and rows Object.keys(rowToInsert.blocks).forEach((blockName) => { @@ -230,7 +243,7 @@ export const upsertRow = async ({ promises.push(async () => { if (operation === 'update') { await Promise.all( - Object.entries(rowToInsert.arrays).map(async ([arrayTableName, tableRows]) => { + Object.entries(rowToInsert.arrays).map(async ([arrayTableName]) => { await deleteExistingArrayRows({ adapter, parentID: insertedRow.id, @@ -247,6 +260,22 @@ export const upsertRow = async ({ }) }) + // ////////////////////////////////// + // INSERT hasMany SELECTS + // ////////////////////////////////// + + promises.push(async () => { + await Promise.all( + Object.entries(selectsToInsert).map(async ([selectTableName, tableRows]) => { + const selectTable = adapter.tables[selectTableName] + if (operation === 'update') { + await db.delete(selectTable).where(eq(selectTable.id, insertedRow.id)) + } + await db.insert(selectTable).values(tableRows).returning() + }), + ) + }) + await Promise.all(promises.map((promise) => promise())) // ////////////////////////////////// diff --git a/packages/db-postgres/src/utilities/appendPrefixToKeys.ts b/packages/db-postgres/src/utilities/appendPrefixToKeys.ts new file mode 100644 index 0000000000..6e8d52a463 --- /dev/null +++ b/packages/db-postgres/src/utilities/appendPrefixToKeys.ts @@ -0,0 +1,5 @@ +export const appendPrefixToObjectKeys = (obj: Record, prefix: string): T => + Object.entries(obj).reduce((res, [key, val]) => { + res[`${prefix}_${key}`] = val + return res + }, {} as T) diff --git a/test/_community/config.ts b/test/_community/config.ts index f3b424c3e9..7d6a2d7d3a 100644 --- a/test/_community/config.ts +++ b/test/_community/config.ts @@ -7,12 +7,28 @@ import { MenuGlobal } from './globals/Menu' export default buildConfigWithDefaults({ // ...extend config here collections: [ - PostsCollection, - MediaCollection, + // PostsCollection, + // MediaCollection, // ...add more collections here + { + slug: 'posts', + fields: [ + { + name: 'mySelect', + type: 'select', + options: ['test', 'test2', 'test3'], + }, + { + name: 'mySelectHasMany', + type: 'select', + hasMany: true, + options: ['test', 'test2', 'test3'], + }, + ], + }, ], globals: [ - MenuGlobal, + // MenuGlobal, // ...add more globals here ], graphQL: { @@ -20,19 +36,14 @@ export default buildConfigWithDefaults({ }, onInit: async (payload) => { - await payload.create({ - collection: 'users', + const test = await payload.create({ + collection: 'posts', data: { - email: devUser.email, - password: devUser.password, + mySelect: 'test3', + mySelectHasMany: ['test', 'test3'], }, }) - await payload.create({ - collection: postsSlug, - data: { - text: 'example post', - }, - }) + console.log(test) }, })