diff --git a/.vscode/launch.json b/.vscode/launch.json index f6a20bf1c5..ebd1e7e69b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "type": "node-terminal" }, { - "command": "pnpm run dev:postgres postgres -- -I", // Allow input + "command": "pnpm run dev:postgres collections-graphql -- -I", // Allow input "cwd": "${workspaceFolder}", "name": "Run Dev Postgres", "request": "launch", diff --git a/packages/db-postgres/src/find.ts b/packages/db-postgres/src/find.ts index d7c349b73e..3589ce88c1 100644 --- a/packages/db-postgres/src/find.ts +++ b/packages/db-postgres/src/find.ts @@ -114,14 +114,7 @@ export const find: Find = async function find( if (where) { findManyArgs.where = where } - // orderBy will only be set if a complex sort is needed on a relation - if (sort) { - if (sort[0] === '-') { - findManyArgs.orderBy = desc(this.tables[tableName][sort.substring(1)]) - } else { - findManyArgs.orderBy = asc(this.tables[tableName][sort]) - } - } + findManyArgs.orderBy = orderBy.order(orderBy.column) } const findPromise = db.query[tableName].findMany(findManyArgs) diff --git a/packages/db-postgres/src/queries/buildQuery.ts b/packages/db-postgres/src/queries/buildQuery.ts index 79fb68a240..ee98ca6cfb 100644 --- a/packages/db-postgres/src/queries/buildQuery.ts +++ b/packages/db-postgres/src/queries/buildQuery.ts @@ -1,12 +1,12 @@ -import type { SQL } from 'drizzle-orm'; -import type { Field, Where } from 'payload/types'; +import type { SQL } from 'drizzle-orm' +import type { Field, Where } from 'payload/types' -import { asc, desc } from 'drizzle-orm'; +import { asc, desc } from 'drizzle-orm' -import type { GenericColumn, PostgresAdapter } from '../types'; +import type { GenericColumn, PostgresAdapter } from '../types' -import { getTableColumnFromPath } from './getTableColumnFromPath'; -import { parseParams } from './parseParams'; +import { getTableColumnFromPath } from './getTableColumnFromPath' +import { parseParams } from './parseParams' export type BuildQueryJoins = Record @@ -38,28 +38,25 @@ const buildQuery = async function buildQuery({ }: BuildQueryArgs): Promise { const selectFields: Record = { id: adapter.tables[tableName].id, - }; - const joins: BuildQueryJoins = {}; + } + const joins: BuildQueryJoins = {} const orderBy: Result['orderBy'] = { column: null, order: null, - }; + } if (sort) { - let sortPath; + let sortPath if (sort[0] === '-') { - sortPath = sort.substring(1); - orderBy.order = desc; + sortPath = sort.substring(1) + orderBy.order = desc } else { - sortPath = sort; - orderBy.order = asc; + sortPath = sort + orderBy.order = asc } - const { - columnName: sortTableColumnName, - table: sortTable, - } = getTableColumnFromPath({ + const { columnName: sortTableColumnName, table: sortTable } = getTableColumnFromPath({ adapter, collectionPath: sortPath, fields, @@ -68,16 +65,25 @@ const buildQuery = async function buildQuery({ pathSegments: sortPath.split('.'), selectFields, tableName, - }); + }) - orderBy.column = sortTable[sortTableColumnName]; + orderBy.column = sortTable[sortTableColumnName] + } else { + orderBy.order = desc + const createdAt = adapter.tables[tableName]?.createdAt - if (orderBy.column) { - selectFields.sort = orderBy.column; + if (createdAt) { + orderBy.column = createdAt + } else { + orderBy.column = adapter.tables[tableName].id } } - let where: SQL; + if (orderBy.column) { + selectFields.sort = orderBy.column + } + + let where: SQL if (Object.keys(incomingWhere).length > 0) { where = await parseParams({ @@ -88,7 +94,7 @@ const buildQuery = async function buildQuery({ selectFields, tableName, where: incomingWhere, - }); + }) } return { @@ -96,7 +102,7 @@ const buildQuery = async function buildQuery({ orderBy, selectFields, where, - }; -}; + } +} -export default buildQuery; +export default buildQuery diff --git a/packages/db-postgres/src/schema/traverseFields.ts b/packages/db-postgres/src/schema/traverseFields.ts index d337176862..cf1caffbd1 100644 --- a/packages/db-postgres/src/schema/traverseFields.ts +++ b/packages/db-postgres/src/schema/traverseFields.ts @@ -1,9 +1,9 @@ /* eslint-disable no-param-reassign */ -import type { Relation } from 'drizzle-orm'; -import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'; -import type { Field } from 'payload/types'; +import type { Relation } from 'drizzle-orm' +import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core' +import type { Field } from 'payload/types' -import { relations } from 'drizzle-orm'; +import { relations } from 'drizzle-orm' import { PgNumericBuilder, PgVarcharBuilder, @@ -13,16 +13,16 @@ import { text, unique, varchar, -} from 'drizzle-orm/pg-core'; -import { fieldAffectsData } from 'payload/types'; -import toSnakeCase from 'to-snake-case'; +} from 'drizzle-orm/pg-core' +import { fieldAffectsData } from 'payload/types' +import toSnakeCase from 'to-snake-case' -import type { GenericColumns, PostgresAdapter } from '../types'; +import type { GenericColumns, PostgresAdapter } from '../types' -import { hasLocalesTable } from '../utilities/hasLocalesTable'; -import { buildTable } from './build'; -import { createIndex } from './createIndex'; -import { parentIDColumnMap } from './parentIDColumnMap'; +import { hasLocalesTable } from '../utilities/hasLocalesTable' +import { buildTable } from './build' +import { createIndex } from './createIndex' +import { parentIDColumnMap } from './parentIDColumnMap' type Args = { adapter: PostgresAdapter @@ -62,33 +62,40 @@ export const traverseFields = ({ parentTableName, relationships, }: Args): Result => { - let hasLocalizedField = false; - let hasLocalizedRelationshipField = false; + let hasLocalizedField = false + let hasLocalizedRelationshipField = false - let parentIDColType = 'integer'; - if (columns.id instanceof PgNumericBuilder) parentIDColType = 'numeric'; - if (columns.id instanceof PgVarcharBuilder) parentIDColType = 'varchar'; + let parentIDColType = 'integer' + if (columns.id instanceof PgNumericBuilder) parentIDColType = 'numeric' + if (columns.id instanceof PgVarcharBuilder) parentIDColType = 'varchar' fields.forEach((field) => { - if ('name' in field && field.name === 'id') return; - let columnName: string; + if ('name' in field && field.name === 'id') return + let columnName: string - let targetTable = columns; - let targetIndexes = indexes; + let targetTable = columns + let targetIndexes = indexes if (fieldAffectsData(field)) { - columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`; + columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}` // If field is localized, // add the column to the locale table instead of main table if (adapter.payload.config.localization && (field.localized || forceLocalized)) { - hasLocalizedField = true; - targetTable = localesColumns; - targetIndexes = localesIndexes; + hasLocalizedField = true + targetTable = localesColumns + targetIndexes = localesIndexes } - if ((field.unique || field.index) && !['array', 'blocks', 'group', 'relationship', 'upload'].includes(field.type)) { - targetIndexes[`${field.name}Idx`] = createIndex({ columnName, name: field.name, unique: field.unique }); + if ( + (field.unique || field.index) && + !['array', 'blocks', 'group', 'relationship', 'upload'].includes(field.type) + ) { + targetIndexes[`${field.name}Idx`] = createIndex({ + name: field.name, + columnName, + unique: field.unique, + }) } } @@ -99,55 +106,61 @@ export const traverseFields = ({ case 'textarea': { // TODO: handle hasMany // TODO: handle min / max length - targetTable[`${fieldPrefix || ''}${field.name}`] = varchar(columnName); - break; + targetTable[`${fieldPrefix || ''}${field.name}`] = varchar(columnName) + break } case 'number': { // TODO: handle hasMany // TODO: handle min / max - targetTable[`${fieldPrefix || ''}${field.name}`] = numeric(columnName); - break; + targetTable[`${fieldPrefix || ''}${field.name}`] = numeric(columnName) + break } case 'richText': case 'json': { - targetTable[`${fieldPrefix || ''}${field.name}`] = jsonb(columnName); - break; + targetTable[`${fieldPrefix || ''}${field.name}`] = jsonb(columnName) + break } case 'date': { - break; + break } case 'point': { - break; + break } case 'radio': { - break; + break } case 'select': { - break; + break } case 'array': { const baseColumns: Record = { _order: integer('_order').notNull(), - _parentID: parentIDColumnMap[parentIDColType]('_parent_id').references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }).notNull(), - }; - - const baseExtraConfig: Record IndexBuilder | UniqueConstraintBuilder> = {}; - - if (field.localized && adapter.payload.config.localization) { - baseColumns._locale = adapter.enums._locales('_locale').notNull(); - baseExtraConfig._parentOrderLocale = (cols) => unique().on(cols._parentID, cols._order, cols._locale); - } else { - baseExtraConfig._parentOrder = (cols) => unique().on(cols._parentID, cols._order); + _parentID: parentIDColumnMap[parentIDColType]('_parent_id') + .references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }) + .notNull(), } - const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`; + const baseExtraConfig: Record< + string, + (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder + > = {} + + if (field.localized && adapter.payload.config.localization) { + baseColumns._locale = adapter.enums._locales('_locale').notNull() + baseExtraConfig._parentOrderLocale = (cols) => + unique().on(cols._parentID, cols._order, cols._locale) + } else { + baseExtraConfig._parentOrder = (cols) => unique().on(cols._parentID, cols._order) + } + + const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}` const { arrayBlockRelations: subArrayBlockRelations } = buildTable({ adapter, @@ -155,9 +168,9 @@ export const traverseFields = ({ baseExtraConfig, fields: field.fields, tableName: arrayTableName, - }); + }) - arrayBlockRelations.set(`${fieldPrefix || ''}${field.name}`, arrayTableName); + arrayBlockRelations.set(`${fieldPrefix || ''}${field.name}`, arrayTableName) const arrayTableRelations = relations(adapter.tables[arrayTableName], ({ many, one }) => { const result: Record> = { @@ -165,41 +178,48 @@ export const traverseFields = ({ fields: [adapter.tables[arrayTableName]._parentID], references: [adapter.tables[parentTableName].id], }), - }; + } if (hasLocalesTable(field.fields)) { - result._locales = many(adapter.tables[`${arrayTableName}_locales`]); + result._locales = many(adapter.tables[`${arrayTableName}_locales`]) } subArrayBlockRelations.forEach((val, key) => { - result[key] = many(adapter.tables[val]); - }); + result[key] = many(adapter.tables[val]) + }) - return result; - }); + return result + }) - adapter.relations[`relations_${arrayTableName}`] = arrayTableRelations; + adapter.relations[`relations_${arrayTableName}`] = arrayTableRelations - break; + break } case 'blocks': { field.blocks.forEach((block) => { - const blockTableName = `${newTableName}_${toSnakeCase(block.slug)}`; + const blockTableName = `${newTableName}_${toSnakeCase(block.slug)}` if (!adapter.tables[blockTableName]) { const baseColumns: Record = { _order: integer('_order').notNull(), - _parentID: parentIDColumnMap[parentIDColType]('_parent_id').references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }).notNull(), + _parentID: parentIDColumnMap[parentIDColType]('_parent_id') + .references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' }) + .notNull(), _path: text('_path').notNull(), - }; + } - const baseExtraConfig: Record IndexBuilder | UniqueConstraintBuilder> = {}; + const baseExtraConfig: Record< + string, + (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder + > = {} if (field.localized && adapter.payload.config.localization) { - baseColumns._locale = adapter.enums._locales('_locale').notNull(); - baseExtraConfig._parentPathOrderLocale = (cols) => unique().on(cols._parentID, cols._path, cols._order, cols._locale); + baseColumns._locale = adapter.enums._locales('_locale').notNull() + baseExtraConfig._parentPathOrderLocale = (cols) => + unique().on(cols._parentID, cols._path, cols._order, cols._locale) } else { - baseExtraConfig._parentPathOrder = (cols) => unique().on(cols._parentID, cols._path, cols._order); + baseExtraConfig._parentPathOrder = (cols) => + unique().on(cols._parentID, cols._path, cols._order) } const { arrayBlockRelations: subArrayBlockRelations } = buildTable({ @@ -208,34 +228,37 @@ export const traverseFields = ({ baseExtraConfig, fields: block.fields, tableName: blockTableName, - }); + }) - const blockTableRelations = relations(adapter.tables[blockTableName], ({ many, one }) => { - const result: Record> = { - _parentID: one(adapter.tables[parentTableName], { - fields: [adapter.tables[blockTableName]._parentID], - references: [adapter.tables[parentTableName].id], - }), - }; + const blockTableRelations = relations( + adapter.tables[blockTableName], + ({ many, one }) => { + const result: Record> = { + _parentID: one(adapter.tables[parentTableName], { + fields: [adapter.tables[blockTableName]._parentID], + references: [adapter.tables[parentTableName].id], + }), + } - if (hasLocalesTable(block.fields)) { - result._locales = many(adapter.tables[`${blockTableName}_locales`]); - } + if (hasLocalesTable(block.fields)) { + result._locales = many(adapter.tables[`${blockTableName}_locales`]) + } - subArrayBlockRelations.forEach((val, key) => { - result[key] = many(adapter.tables[val]); - }); + subArrayBlockRelations.forEach((val, key) => { + result[key] = many(adapter.tables[val]) + }) - return result; - }); + return result + }, + ) - adapter.relations[`relations_${blockTableName}`] = blockTableRelations; + adapter.relations[`relations_${blockTableName}`] = blockTableRelations } - arrayBlockRelations.set(`_blocks_${block.slug}`, blockTableName); - }); + arrayBlockRelations.set(`_blocks_${block.slug}`, blockTableName) + }) - break; + break } case 'group': { @@ -257,11 +280,11 @@ export const traverseFields = ({ newTableName: `${parentTableName}_${toSnakeCase(field.name)}`, parentTableName, relationships, - }); + }) - if (groupHasLocalizedField) hasLocalizedField = true; - if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true; - break; + if (groupHasLocalizedField) hasLocalizedField = true + if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true + break } case 'tabs': { @@ -274,7 +297,7 @@ export const traverseFields = ({ adapter, arrayBlockRelations, buildRelationships, - columnPrefix: `${columnName}_`, + columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`, columns, fieldPrefix: `${fieldPrefix || ''}${tab.name}_`, fields: tab.fields, @@ -284,19 +307,18 @@ export const traverseFields = ({ newTableName: `${parentTableName}_${toSnakeCase(tab.name)}`, parentTableName, relationships, - }); + }) - if (tabHasLocalizedField) hasLocalizedField = true; - if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true; + if (tabHasLocalizedField) hasLocalizedField = true + if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true } else { - ({ - hasLocalizedField, - hasLocalizedRelationshipField, - } = traverseFields({ + ;({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, + columnPrefix, columns, + fieldPrefix, fields: tab.fields, indexes, localesColumns, @@ -304,22 +326,21 @@ export const traverseFields = ({ newTableName: parentTableName, parentTableName, relationships, - })); + })) } - }); - break; + }) + break } case 'row': case 'collapsible': { - ({ - hasLocalizedField, - hasLocalizedRelationshipField, - } = traverseFields({ + ;({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, + columnPrefix, columns, + fieldPrefix, fields: field.fields, indexes, localesColumns, @@ -327,27 +348,27 @@ export const traverseFields = ({ newTableName: parentTableName, parentTableName, relationships, - })); - break; + })) + break } case 'relationship': case 'upload': if (Array.isArray(field.relationTo)) { - field.relationTo.forEach((relation) => relationships.add(relation)); + field.relationTo.forEach((relation) => relationships.add(relation)) } else { - relationships.add(field.relationTo); + relationships.add(field.relationTo) } if (field.localized && adapter.payload.config.localization) { - hasLocalizedRelationshipField = true; + hasLocalizedRelationshipField = true } - break; + break default: - break; + break } - }); + }) - return { hasLocalizedField, hasLocalizedRelationshipField }; -}; + return { hasLocalizedField, hasLocalizedRelationshipField } +} diff --git a/packages/db-postgres/src/transform/read/index.ts b/packages/db-postgres/src/transform/read/index.ts index 49f9d49b9b..2d57109644 100644 --- a/packages/db-postgres/src/transform/read/index.ts +++ b/packages/db-postgres/src/transform/read/index.ts @@ -1,10 +1,10 @@ /* eslint-disable no-param-reassign */ -import type { SanitizedConfig } from 'payload/config'; -import type { Field, TypeWithID } from 'payload/types'; +import type { SanitizedConfig } from 'payload/config' +import type { Field, TypeWithID } from 'payload/types' -import { createBlocksMap } from '../../utilities/createBlocksMap'; -import { createRelationshipMap } from '../../utilities/createRelationshipMap'; -import { traverseFields } from './traverseFields'; +import { createBlocksMap } from '../../utilities/createBlocksMap' +import { createRelationshipMap } from '../../utilities/createRelationshipMap' +import { traverseFields } from './traverseFields' type TransformArgs = { config: SanitizedConfig @@ -16,34 +16,28 @@ type TransformArgs = { // This is the entry point to transform Drizzle output data // into the shape Payload expects based on field schema -export const transform = ({ - config, - data, - fields, -}: TransformArgs): T => { - let relationships: Record[]> = {}; +export const transform = ({ config, data, fields }: TransformArgs): T => { + let relationships: Record[]> = {} if ('_relationships' in data) { - relationships = createRelationshipMap(data._relationships); - delete data._relationships; + relationships = createRelationshipMap(data._relationships) + delete data._relationships } - const blocks = createBlocksMap(data); + const blocks = createBlocksMap(data) const result = traverseFields({ blocks, + columnPrefix: '', config, - data, + dataRef: { + id: data.id, + }, fields, path: '', relationships, - siblingData: data, table: data, - }); + }) - if ('_locales' in result) { - delete result._locales; - } - - return result; -}; + return result +} diff --git a/packages/db-postgres/src/transform/read/traverseFields.ts b/packages/db-postgres/src/transform/read/traverseFields.ts index b573ba9130..1cb6983e4d 100644 --- a/packages/db-postgres/src/transform/read/traverseFields.ts +++ b/packages/db-postgres/src/transform/read/traverseFields.ts @@ -1,30 +1,35 @@ /* eslint-disable no-param-reassign */ -import type { SanitizedConfig } from 'payload/config'; -import type { Field} from 'payload/types'; +import type { SanitizedConfig } from 'payload/config' +import type { Field, TabAsField } from 'payload/types' -import { fieldAffectsData } from 'payload/types'; +import { fieldAffectsData, tabHasName } from 'payload/types' +import toSnakeCase from 'to-snake-case' -import type { BlocksMap } from '../../utilities/createBlocksMap'; +import type { BlocksMap } from '../../utilities/createBlocksMap' -import { transformRelationship } from './relationship'; +import { transformRelationship } from './relationship' type TraverseFieldsArgs = { /** * Pre-formatted blocks map */ blocks: BlocksMap + /** + * Column prefix can be built up by group and named tab fields + */ + columnPrefix: string /** * The full Payload config */ config: SanitizedConfig /** - * The full data, as returned from the Drizzle query + * The data reference to be mutated within this recursive function */ - data: Record + dataRef: Record /** * An array of Payload fields to traverse */ - fields: Field[] + fields: (Field | TabAsField)[] /** * The current field path (in dot notation), used to merge in relationships */ @@ -33,158 +38,151 @@ type TraverseFieldsArgs = { * All related documents, as returned by Drizzle, keyed on an object by field path */ relationships: Record[]> - /** - * Sibling data of the fields to traverse - */ - siblingData: Record /** * Data structure representing the nearest table from db */ table: Record } -// TODO: clean up internal data structures: -// _order, _path, _parentID, _locales, etc. - // Traverse fields recursively, transforming data // for each field type into required Payload shape export const traverseFields = >({ blocks, + columnPrefix, config, - data, + dataRef, fields, path, relationships, - siblingData, table, }: TraverseFieldsArgs): T => { - const sanitizedPath = path ? `${path}.` : path; + const sanitizedPath = path ? `${path}.` : path const formatted = fields.reduce((result, field) => { if (fieldAffectsData(field)) { if (field.type === 'array') { - const fieldData = result[field.name]; + const fieldData = table[field.name] if (Array.isArray(fieldData)) { if (field.localized) { result[field.name] = fieldData.reduce((arrayResult, row) => { if (typeof row._locale === 'string') { - if (!arrayResult[row._locale]) arrayResult[row._locale] = []; + if (!arrayResult[row._locale]) arrayResult[row._locale] = [] const rowResult = traverseFields({ blocks, + columnPrefix: '', config, - data, + dataRef: row, fields: field.fields, path: `${sanitizedPath}${field.name}.${row._order - 1}`, relationships, - siblingData: row, table: row, - }); + }) - arrayResult[row._locale].push(rowResult); - delete rowResult._locale; + arrayResult[row._locale].push(rowResult) + delete rowResult._locale } - return arrayResult; - }, {}); + return arrayResult + }, {}) } else { result[field.name] = fieldData.map((row, i) => { return traverseFields({ blocks, + columnPrefix: '', config, - data, + dataRef: row, fields: field.fields, path: `${sanitizedPath}${field.name}.${i}`, relationships, - siblingData: row, table: row, - }); - }); + }) + }) } } - return result; + return result } if (field.type === 'blocks') { - const blockFieldPath = `${sanitizedPath}${field.name}`; + const blockFieldPath = `${sanitizedPath}${field.name}` if (Array.isArray(blocks[blockFieldPath])) { if (field.localized) { - result[field.name] = {}; + result[field.name] = {} blocks[blockFieldPath].forEach((row) => { if (typeof row._locale === 'string') { - if (!result[field.name][row._locale]) result[field.name][row._locale] = []; - result[field.name][row._locale].push(row); - delete row._locale; + if (!result[field.name][row._locale]) result[field.name][row._locale] = [] + result[field.name][row._locale].push(row) + delete row._locale } - }); + }) Object.entries(result[field.name]).forEach(([locale, localizedBlocks]) => { result[field.name][locale] = localizedBlocks.map((row) => { - const block = field.blocks.find(({ slug }) => slug === row.blockType); + const block = field.blocks.find(({ slug }) => slug === row.blockType) if (block) { const blockResult = traverseFields({ blocks, + columnPrefix: '', config, - data, + dataRef: row, fields: block.fields, path: `${blockFieldPath}.${row._order - 1}`, relationships, - siblingData: row, table: row, - }); + }) - delete blockResult._order; - return blockResult; + delete blockResult._order + return blockResult } - return {}; - }); - }); + return {} + }) + }) } else { result[field.name] = blocks[blockFieldPath].map((row, i) => { - delete row._order; - const block = field.blocks.find(({ slug }) => slug === row.blockType); + delete row._order + const block = field.blocks.find(({ slug }) => slug === row.blockType) if (block) { return traverseFields({ blocks, + columnPrefix: '', config, - data, + dataRef: row, fields: block.fields, path: `${blockFieldPath}.${i}`, relationships, - siblingData: row, table: row, - }); + }) } - return {}; - }); + return {} + }) } } - return result; + return result } if (field.type === 'relationship') { - const relationPathMatch = relationships[`${sanitizedPath}${field.name}`]; - if (!relationPathMatch) return result; + const relationPathMatch = relationships[`${sanitizedPath}${field.name}`] + if (!relationPathMatch) return result if (field.localized) { - result[field.name] = {}; - const relationsByLocale: Record[]> = {}; + result[field.name] = {} + const relationsByLocale: Record[]> = {} relationPathMatch.forEach((row) => { if (typeof row.locale === 'string') { - if (!relationsByLocale[row.locale]) relationsByLocale[row.locale] = []; - relationsByLocale[row.locale].push(row); + if (!relationsByLocale[row.locale]) relationsByLocale[row.locale] = [] + relationsByLocale[row.locale].push(row) } - }); + }) Object.entries(relationsByLocale).forEach(([locale, relations]) => { transformRelationship({ @@ -192,132 +190,170 @@ export const traverseFields = >({ locale, ref: result, relations, - }); - }); + }) + }) } else { transformRelationship({ field, ref: result, relations: relationPathMatch, - }); + }) } - return result; + return result } - const localizedFieldData = {}; - const valuesToTransform: { localeRow: Record, ref: Record }[] = []; + const localizedFieldData = {} + const valuesToTransform: { + ref: Record + table: Record + }[] = [] if (field.localized && Array.isArray(table._locales)) { table._locales.forEach((localeRow) => { - valuesToTransform.push({ localeRow, ref: localizedFieldData }); - }); + valuesToTransform.push({ ref: localizedFieldData, table: localeRow }) + }) } else { - valuesToTransform.push({ localeRow: undefined, ref: result }); + valuesToTransform.push({ ref: result, table }) } - valuesToTransform.forEach(({ localeRow, ref }) => { - const fieldData = localeRow?.[field.name] || ref[field.name]; - const locale = localeRow?._locale; + valuesToTransform.forEach(({ ref, table }) => { + const fieldData = table[field.name] + const locale = table?._locale switch (field.type) { + case 'tab': case 'group': { - const groupData = {}; + // if (field.type === 'tab') { + // console.log('got one') + // } - field.fields.forEach((subField) => { - if (fieldAffectsData(subField)) { - const subFieldKey = `${sanitizedPath.replace(/\./g, '_')}${field.name}_${subField.name}`; + if (!tabHasName(field)) { + traverseFields({ + blocks, + columnPrefix, + config, + dataRef, + fields: field.fields, + path: sanitizedPath, + relationships, + table, + }) - if (typeof locale === 'string') { - if (!ref[locale]) ref[locale] = {}; - ref[locale][subField.name] = localeRow[subFieldKey]; - } else { - groupData[subField.name] = table[subFieldKey]; - delete table[subFieldKey]; - } - } - }); + return + } + + const groupColumnPrefix = `${columnPrefix || ''}${toSnakeCase(field.name)}_` + const groupData = {} if (field.localized) { + if (typeof locale === 'string' && !ref[locale]) ref[locale] = {} + Object.entries(ref).forEach(([groupLocale, groupLocaleData]) => { ref[groupLocale] = traverseFields>({ blocks, + columnPrefix: groupColumnPrefix, config, - data, + dataRef: groupLocaleData as Record, fields: field.fields, path: `${sanitizedPath}${field.name}`, relationships, - siblingData: groupLocaleData as Record, table, - }); - }); + }) + }) } else { ref[field.name] = traverseFields>({ blocks, + columnPrefix: groupColumnPrefix, config, - data, + dataRef: groupData as Record, fields: field.fields, path: `${sanitizedPath}${field.name}`, relationships, - siblingData: groupData as Record, table, - }); + }) } - break; + break } case 'number': { - let val = fieldData; + let val = fieldData // TODO: handle hasMany if (typeof fieldData === 'string') { - val = Number.parseFloat(fieldData); + val = Number.parseFloat(fieldData) } if (typeof locale === 'string') { - ref[locale] = val; + ref[locale] = val } else { - result[field.name] = val; + result[field.name] = val } - break; + break } case 'date': { if (fieldData instanceof Date) { - const val = fieldData.toISOString(); + const val = fieldData.toISOString() if (typeof locale === 'string') { - ref[locale] = val; + ref[locale] = val } else { - result[field.name] = val; + result[field.name] = val } } - break; + break } default: { if (typeof locale === 'string') { - ref[locale] = fieldData; + ref[locale] = fieldData } else { - result[field.name] = fieldData; + result[field.name] = fieldData } - break; + break } } - }); + }) if (Object.keys(localizedFieldData).length > 0) { - result[field.name] = localizedFieldData; + result[field.name] = localizedFieldData } - return result; + return result } - return siblingData; - }, siblingData); + if (field.type === 'tabs') { + traverseFields({ + blocks, + columnPrefix, + config, + dataRef, + fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), + path: sanitizedPath, + relationships, + table, + }) + } - return formatted as T; -}; + if (field.type === 'collapsible' || field.type === 'row') { + traverseFields({ + blocks, + columnPrefix, + config, + dataRef, + fields: field.fields, + path: sanitizedPath, + relationships, + table, + }) + } + + return dataRef + }, dataRef) + + return formatted as T +} diff --git a/packages/db-postgres/src/transform/write/traverseFields.ts b/packages/db-postgres/src/transform/write/traverseFields.ts index da6db82cc6..c3f8871d23 100644 --- a/packages/db-postgres/src/transform/write/traverseFields.ts +++ b/packages/db-postgres/src/transform/write/traverseFields.ts @@ -1,15 +1,15 @@ /* eslint-disable no-param-reassign */ -import type { Field } from 'payload/types'; +import type { Field } from 'payload/types' -import { fieldAffectsData } from 'payload/types'; -import toSnakeCase from 'to-snake-case'; +import { fieldAffectsData } from 'payload/types' +import toSnakeCase from 'to-snake-case' -import type { ArrayRowToInsert, BlockRowToInsert } from './types'; +import type { ArrayRowToInsert, BlockRowToInsert } from './types' -import { isArrayOfRows } from '../../utilities/isArrayOfRows'; -import { transformArray } from './array'; -import { transformBlocks } from './blocks'; -import { transformRelationship } from './relationships'; +import { isArrayOfRows } from '../../utilities/isArrayOfRows' +import { transformArray } from './array' +import { transformBlocks } from './blocks' +import { transformRelationship } from './relationships' type Args = { arrays: { @@ -49,17 +49,17 @@ export const traverseFields = ({ row, }: Args) => { fields.forEach((field) => { - let columnName = ''; - let fieldData: unknown; + let columnName = '' + let fieldData: unknown if (fieldAffectsData(field)) { - columnName = `${columnPrefix || ''}${field.name}`; - fieldData = data[field.name]; + columnName = `${columnPrefix || ''}${field.name}` + fieldData = data[field.name] } if (field.type === 'array') { - const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`; - if (!arrays[arrayTableName]) arrays[arrayTableName] = []; + const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}` + if (!arrays[arrayTableName]) arrays[arrayTableName] = [] if (field.localized) { if (typeof data[field.name] === 'object' && data[field.name] !== null) { @@ -74,11 +74,11 @@ export const traverseFields = ({ locale: localeKey, path, relationships, - }); + }) - arrays[arrayTableName] = arrays[arrayTableName].concat(newRows); + arrays[arrayTableName] = arrays[arrayTableName].concat(newRows) } - }); + }) } } else { const newRows = transformArray({ @@ -89,12 +89,12 @@ export const traverseFields = ({ field, path, relationships, - }); + }) - arrays[arrayTableName] = arrays[arrayTableName].concat(newRows); + arrays[arrayTableName] = arrays[arrayTableName].concat(newRows) } - return; + return } if (field.type === 'blocks') { @@ -110,9 +110,9 @@ export const traverseFields = ({ path, relationships, tableName: newTableName, - }); + }) } - }); + }) } } else if (isArrayOfRows(fieldData)) { transformBlocks({ @@ -122,10 +122,10 @@ export const traverseFields = ({ path, relationships, tableName: newTableName, - }); + }) } - return; + return } if (field.type === 'group') { @@ -146,8 +146,8 @@ export const traverseFields = ({ path: `${path || ''}${field.name}.`, relationships, row, - }); - }); + }) + }) } else { traverseFields({ arrays, @@ -162,15 +162,90 @@ export const traverseFields = ({ path: `${path || ''}${field.name}.`, relationships, row, - }); + }) } } - return; + return + } + + if (field.type === 'tabs') { + field.tabs.forEach((tab) => { + if ('name' in tab) { + if (typeof data[tab.name] === 'object' && data[tab.name] !== null) { + if (tab.localized) { + Object.entries(data[tab.name]).forEach(([localeKey, localeData]) => { + traverseFields({ + arrays, + blocks, + columnPrefix: `${columnPrefix || ''}${tab.name}_`, + data: localeData as Record, + existingLocales, + fields: tab.fields, + forcedLocale: localeKey, + locales, + newTableName: `${parentTableName}_${toSnakeCase(tab.name)}`, + parentTableName, + path: `${path || ''}${tab.name}.`, + relationships, + row, + }) + }) + } else { + traverseFields({ + arrays, + blocks, + columnPrefix: `${columnPrefix || ''}${tab.name}_`, + data: data[tab.name] as Record, + existingLocales, + fields: tab.fields, + locales, + newTableName: `${parentTableName}_${toSnakeCase(tab.name)}`, + parentTableName, + path: `${path || ''}${tab.name}.`, + relationships, + row, + }) + } + } + } else { + traverseFields({ + arrays, + blocks, + columnPrefix, + data, + existingLocales, + fields: tab.fields, + locales, + newTableName: parentTableName, + parentTableName, + path, + relationships, + row, + }) + } + }) + } + + if (field.type === 'row' || field.type === 'collapsible') { + traverseFields({ + arrays, + blocks, + columnPrefix, + data, + existingLocales, + fields: field.fields, + locales, + newTableName: parentTableName, + parentTableName, + path, + relationships, + row, + }) } if (field.type === 'relationship') { - const relationshipPath = `${path || ''}${field.name}`; + const relationshipPath = `${path || ''}${field.name}` if (field.localized) { if (typeof fieldData === 'object') { @@ -183,8 +258,8 @@ export const traverseFields = ({ data: localeData, field, relationships, - }); - }); + }) + }) } } else { transformRelationship({ @@ -194,134 +269,93 @@ export const traverseFields = ({ data: fieldData, field, relationships, - }); + }) } - return; + return } if (fieldAffectsData(field)) { - const valuesToTransform: { localeKey?: string, ref: unknown, value: unknown }[] = []; + const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = [] - if ((field.localized)) { + if (field.localized) { if (typeof fieldData === 'object' && fieldData !== null) { Object.entries(fieldData).forEach(([localeKey, localeData]) => { - if (!locales[localeKey]) locales[localeKey] = {}; + if (!locales[localeKey]) locales[localeKey] = {} valuesToTransform.push({ localeKey, ref: locales, value: localeData, - }); - }); + }) + }) } } else { - let ref = row; + let ref = row if (forcedLocale) { - if (!locales[forcedLocale]) locales[forcedLocale] = {}; - ref = locales[forcedLocale]; + if (!locales[forcedLocale]) locales[forcedLocale] = {} + ref = locales[forcedLocale] } - valuesToTransform.push({ ref, value: fieldData }); + valuesToTransform.push({ ref, value: fieldData }) } valuesToTransform.forEach(({ localeKey, ref, value }) => { if (typeof value !== 'undefined') { - let formattedValue = value; + let formattedValue = value switch (field.type) { case 'number': { // TODO: handle hasMany - break; + break } case 'select': { - break; + break } case 'date': { if (typeof fieldData === 'string') { - const parsedDate = new Date(fieldData); - formattedValue = parsedDate; + const parsedDate = new Date(fieldData) + formattedValue = parsedDate } - break; + break } - // case 'tabs': { - // await Promise.all(field.tabs.map(async (tab) => { - // if ('name' in tab) { - // if (typeof data[tab.name] === 'object' && data[tab.name] !== null) { - // await traverseFields({ - // adapter, - // arrayRowPromises, - // blockRows, - // columnPrefix: `${columnName}_`, - // data: data[tab.name] as Record, - // fields: tab.fields, - // locale, - // localeRow, - // operation, - // path: `${path || ''}${tab.name}.`, - // relationshipRows, - // row, - // tableName, - // }); - // } - // } else { - // await traverseFields({ - // adapter, - // arrayRowPromises, - // blockRows, - // columnPrefix, - // data, - // fields: tab.fields, - // locale, - // localeRow, - // operation, - // path, - // relationshipRows, - // row, - // tableName, - // }); - // } - // })); - // break; - // } - - // case 'row': - // case 'collapsible': { - // await traverseFields({ - // adapter, - // arrayRowPromises, - // blockRows, - // columnPrefix, - // data, - // fields: field.fields, - // locale, - // localeRow, - // operation, - // path, - // relationshipRows, - // row, - // tableName, - // }); - // break; - // } + case 'row': + case 'collapsible': { + traverseFields({ + adapter, + arrayRowPromises, + blockRows, + columnPrefix, + data, + fields: field.fields, + locale, + localeRow, + operation, + path, + relationshipRows, + row, + tableName, + }) + break + } default: { - break; + break } } if (localeKey) { - ref[localeKey][columnName] = formattedValue; + ref[localeKey][columnName] = formattedValue } else { - ref[columnName] = formattedValue; + ref[columnName] = formattedValue } } - }); + }) } - }); -}; + }) +} diff --git a/packages/db-postgres/src/update/index.ts b/packages/db-postgres/src/update/index.ts index 9e9485c2ac..b74ac65be6 100644 --- a/packages/db-postgres/src/update/index.ts +++ b/packages/db-postgres/src/update/index.ts @@ -1,42 +1,39 @@ -import type { UpdateOne } from 'payload/database'; +import type { UpdateOne } from 'payload/database' -import toSnakeCase from 'to-snake-case'; +import toSnakeCase from 'to-snake-case' -import buildQuery from '../queries/buildQuery'; -import { upsertRow } from '../upsertRow'; +import type { PostgresAdapter } from '../types' -export const updateOne: UpdateOne = async function updateOne({ - collection: collectionSlug, - data, - draft, - id, - locale, - req, - where: whereArg, -}) { - const db = req.transactionID ? this.sessions[req.transactionID] : this.db; - const collection = this.payload.collections[collectionSlug].config; - const tableName = toSnakeCase(collection); - const whereToUse = whereArg || { id: { equals: id } }; +import buildQuery from '../queries/buildQuery' +import { upsertRow } from '../upsertRow' + +export const updateOne: UpdateOne = async function updateOne( + this: PostgresAdapter, + { id, collection: collectionSlug, data, draft, locale, req, where: whereArg }, +) { + const db = this.sessions?.[req.transactionID] || this.db + const collection = this.payload.collections[collectionSlug].config + const tableName = toSnakeCase(collectionSlug) + const whereToUse = whereArg || { id: { equals: id } } const { where } = await buildQuery({ adapter: this, fields: collection.fields, locale, tableName, - where: whereToUse - }); + where: whereToUse, + }) const result = await upsertRow({ + id, adapter: this, data, db, fields: collection.fields, - id, operation: 'update', tableName: toSnakeCase(collectionSlug), where, - }); + }) - return result; -}; + return result +} diff --git a/packages/payload/src/collections/graphql/init.ts b/packages/payload/src/collections/graphql/init.ts index 89f89635d7..19da8b7e7d 100644 --- a/packages/payload/src/collections/graphql/init.ts +++ b/packages/payload/src/collections/graphql/init.ts @@ -87,7 +87,7 @@ function initCollectionsGraphQL(payload: Payload): void { baseFields.id = { type: idType } whereInputFields.push({ name: 'id', - type: 'text', + type: payload.db.defaultIDType as 'text', }) } diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index da109f5dc7..fe986ef59f 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -8,7 +8,7 @@ import { mongooseAdapter } from '../packages/db-mongodb/src/index' import { postgresAdapter } from '../packages/db-postgres/src/index' import { buildConfig as buildPayloadConfig } from '../packages/payload/src/config/build' -process.env.PAYLOAD_DATABASE = 'postgres' +// process.env.PAYLOAD_DATABASE = 'postgres' const databaseAdapters = { mongoose: mongooseAdapter({ diff --git a/test/collections-graphql/config.ts b/test/collections-graphql/config.ts index 0412f529c6..4bfd621df1 100644 --- a/test/collections-graphql/config.ts +++ b/test/collections-graphql/config.ts @@ -312,7 +312,7 @@ export default buildConfigWithDefaults({ }, ], onInit: async (payload) => { - await payload.create({ + const user = await payload.create({ collection: 'users', data: { email: devUser.email, diff --git a/test/collections-graphql/int.spec.ts b/test/collections-graphql/int.spec.ts index c513a5b0f2..f966269ac0 100644 --- a/test/collections-graphql/int.spec.ts +++ b/test/collections-graphql/int.spec.ts @@ -38,9 +38,12 @@ describe('collections-graphql', () => { describe('CRUD', () => { let existingDoc: Post + let existingDocGraphQLID beforeEach(async () => { existingDoc = await createPost() + existingDocGraphQLID = + payload.db.defaultIDType === 'number' ? existingDoc.id : `"${existingDoc.id}"` }) it('should create', async () => { @@ -54,7 +57,7 @@ describe('collections-graphql', () => { const doc: Post = response.createPost expect(doc).toMatchObject({ title }) - expect(doc.id.length).toBeGreaterThan(0) + expect(doc.id).toBeDefined() }) it('should create using graphql variables', async () => { @@ -68,12 +71,12 @@ describe('collections-graphql', () => { const doc: Post = response.createPost expect(doc).toMatchObject({ title }) - expect(doc.id.length).toBeGreaterThan(0) + expect(doc.id).toBeDefined() }) it('should read', async () => { const query = `query { - Post(id: "${existingDoc.id}") { + Post(id: ${existingDocGraphQLID}) { id title } @@ -123,7 +126,7 @@ describe('collections-graphql', () => { const updatedTitle = 'updated title' const query = `mutation { - updatePost(id: "${existingDoc.id}", data: { title: "${updatedTitle}"}) { + updatePost(id: ${existingDocGraphQLID}, data: { title: "${updatedTitle}"}) { id title } @@ -136,7 +139,7 @@ describe('collections-graphql', () => { it('should delete', async () => { const query = `mutation { - deletePost(id: "${existingDoc.id}") { + deletePost(id: ${existingDocGraphQLID}) { id title }