diff --git a/docs/configuration/collections.mdx b/docs/configuration/collections.mdx index e7e936b3b..b532057b7 100644 --- a/docs/configuration/collections.mdx +++ b/docs/configuration/collections.mdx @@ -30,6 +30,7 @@ It's often best practice to write your Collections in separate files and then im | **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. | | **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this collection and prevent `duplicate` from all APIs. | | **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. | +| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined. | **`custom`** | Extension point for adding custom data (e.g. for plugins) | _\* An asterisk denotes that a property is required._ diff --git a/docs/configuration/globals.mdx b/docs/configuration/globals.mdx index 4c7fd59be..a4dad310f 100644 --- a/docs/configuration/globals.mdx +++ b/docs/configuration/globals.mdx @@ -26,6 +26,7 @@ As with Collection configs, it's often best practice to write your Globals in se | **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. | | **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. | | **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`dbName`** | Custom table or collection name for this global depending on the database adapter. Auto-generated from slug if not defined. _\* An asterisk denotes that a property is required._ diff --git a/docs/database/postgres.mdx b/docs/database/postgres.mdx index 6d36506dd..4e2da96b0 100644 --- a/docs/database/postgres.mdx +++ b/docs/database/postgres.mdx @@ -38,12 +38,18 @@ export default buildConfig({ ### Options -| Option | Description | -| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `pool` | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. | -| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. | -| `migrationDir` | Customize the directory that migrations are stored. | +| Option | Description | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. | +| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. | +| `migrationDir` | Customize the directory that migrations are stored. | +| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. | | `schemaName` | A string for the postgres schema to use, defaults to 'public'. | +| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. | +| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. | +| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. | + + ### Access to Drizzle diff --git a/docs/fields/array.mdx b/docs/fields/array.mdx index 18dab76ef..ed91c543b 100644 --- a/docs/fields/array.mdx +++ b/docs/fields/array.mdx @@ -45,6 +45,7 @@ keywords: array, fields, config, configuration, documentation, Content Managemen | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | +| **`dbName`** | Custom table name for the field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/blocks.mdx b/docs/fields/blocks.mdx index a6333403a..1733416a1 100644 --- a/docs/fields/blocks.mdx +++ b/docs/fields/blocks.mdx @@ -80,6 +80,7 @@ Blocks are defined as separate configs of their own. | **`imageAltText`** | Customize this block's image thumbnail alt text. | | **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | | **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. | +| **`dbName`** | Custom table name for this block type when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined. | **`custom`** | Extension point for adding custom data (e.g. for plugins) | #### Auto-generated data per block diff --git a/docs/fields/number.mdx b/docs/fields/number.mdx index ac42b246a..7fbcc4d1e 100644 --- a/docs/fields/number.mdx +++ b/docs/fields/number.mdx @@ -20,27 +20,27 @@ keywords: number, fields, config, configuration, documentation, Content Manageme ### Config -| Option | Description | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | -| **`min`** | Minimum value accepted. Used in the default `validation` function. | -| **`max`** | Maximum value accepted. Used in the default `validation` function. | -| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. | -| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. | -| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | -| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | -| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| Option | Description | +|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | +| **`min`** | Minimum value accepted. Used in the default `validation` function. | +| **`max`** | Maximum value accepted. Used in the default `validation` function. | +| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. | +| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. | +| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | +| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | +| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/radio.mdx b/docs/fields/radio.mdx index f77ed81ab..c9e37b4bf 100644 --- a/docs/fields/radio.mdx +++ b/docs/fields/radio.mdx @@ -36,6 +36,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen | **`required`** | Require this field to have a value. | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/select.mdx b/docs/fields/select.mdx index 924411626..aef704f90 100644 --- a/docs/fields/select.mdx +++ b/docs/fields/select.mdx @@ -38,6 +38,8 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co | **`required`** | Require this field to have a value. | | **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. | | **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | _\* An asterisk denotes that a property is required._ diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index c8db90061..18a18e7cb 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -30,7 +30,6 @@ "get-port": "5.1.1", "http-status": "1.6.2", "mongoose": "6.12.3", - "mongoose-aggregate-paginate-v2": "1.0.6", "mongoose-paginate-v2": "1.7.22", "prompts": "2.4.2", "uuid": "9.0.0" diff --git a/packages/db-mongodb/src/init.ts b/packages/db-mongodb/src/init.ts index 510e7ea03..3aad0f4ab 100644 --- a/packages/db-mongodb/src/init.ts +++ b/packages/db-mongodb/src/init.ts @@ -5,11 +5,7 @@ import type { SanitizedCollectionConfig } from 'payload/types' import mongoose from 'mongoose' import paginate from 'mongoose-paginate-v2' -import { - buildVersionCollectionFields, - buildVersionGlobalFields, - getVersionsModelName, -} from 'payload/versions' +import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions' import type { MongooseAdapter } from './index.js' import type { CollectionModel } from './types.js' @@ -18,13 +14,14 @@ import buildCollectionSchema from './models/buildCollectionSchema.js' import { buildGlobalModel } from './models/buildGlobalModel.js' import buildSchema from './models/buildSchema.js' import getBuildQueryPlugin from './queries/buildQuery.js' +import { getDBName } from './utilities/getDBName.js' export const init: Init = function init(this: MongooseAdapter) { this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { const schema = buildCollectionSchema(collection, this.payload.config) if (collection.versions) { - const versionModelName = getVersionsModelName(collection) + const versionModelName = getDBName({ config: collection, versions: true }) const versionCollectionFields = buildVersionCollectionFields(collection) @@ -54,7 +51,7 @@ export const init: Init = function init(this: MongooseAdapter) { } const model = mongoose.model( - collection.slug, + getDBName({ config: collection }), schema, this.autoPluralization === true ? undefined : collection.slug, ) as CollectionModel @@ -72,7 +69,7 @@ export const init: Init = function init(this: MongooseAdapter) { this.payload.config.globals.forEach((global) => { if (global.versions) { - const versionModelName = getVersionsModelName(global) + const versionModelName = getDBName({ config: global, versions: true }) const versionGlobalFields = buildVersionGlobalFields(global) diff --git a/packages/db-mongodb/src/models/buildSchema.ts b/packages/db-mongodb/src/models/buildSchema.ts index e285bedde..4dacc3566 100644 --- a/packages/db-mongodb/src/models/buildSchema.ts +++ b/packages/db-mongodb/src/models/buildSchema.ts @@ -363,7 +363,7 @@ const fieldToSchemaMap: Record = { } if (field.localized && config.localization) { config.localization.locales.forEach((locale) => { - schema.index({ [`${field.name}.${locale}`]: '2dsphere' }, indexOptions) + schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions) }) } else { schema.index({ [field.name]: '2dsphere' }, indexOptions) diff --git a/packages/db-mongodb/src/utilities/getDBName.ts b/packages/db-mongodb/src/utilities/getDBName.ts new file mode 100644 index 000000000..c35097ba0 --- /dev/null +++ b/packages/db-mongodb/src/utilities/getDBName.ts @@ -0,0 +1,41 @@ +import type { DBIdentifierName } from 'payload/database' + +type Args = { + config: { + dbName?: DBIdentifierName + enumName?: DBIdentifierName + name?: string + slug?: string + } + locales?: boolean + target?: 'dbName' | 'enumName' + versions?: boolean +} + +/** + * Used to name database enums and collections + * Returns the collection or enum name for a given entity + */ +export const getDBName = ({ + config: { name, slug }, + config, + target = 'dbName', + versions = false, +}: Args): string => { + let result: string + let custom = config[target] + + if (!custom && target === 'enumName') { + custom = config['dbName'] + } + + if (custom) { + result = typeof custom === 'function' ? custom({}) : custom + } else { + result = name ?? slug + } + + if (versions) result = `_${result}_versions` + + return result +} diff --git a/packages/db-postgres/src/create.ts b/packages/db-postgres/src/create.ts index 4af0b2c05..df4ec9ac5 100644 --- a/packages/db-postgres/src/create.ts +++ b/packages/db-postgres/src/create.ts @@ -1,9 +1,8 @@ import type { Create } from 'payload/database' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export const create: Create = async function create( @@ -20,7 +19,10 @@ export const create: Create = async function create( fields: collection.fields, operation: 'create', req, - tableName: toSnakeCase(collectionSlug), + tableName: getTableName({ + adapter: this, + config: collection, + }), }) return result diff --git a/packages/db-postgres/src/createGlobal.ts b/packages/db-postgres/src/createGlobal.ts index 965e5c36f..952adb318 100644 --- a/packages/db-postgres/src/createGlobal.ts +++ b/packages/db-postgres/src/createGlobal.ts @@ -1,10 +1,9 @@ import type { CreateGlobalArgs } from 'payload/database' import type { PayloadRequest, TypeWithID } from 'payload/types' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export async function createGlobal( @@ -21,7 +20,10 @@ export async function createGlobal( fields: globalConfig.fields, operation: 'create', req, - tableName: toSnakeCase(slug), + tableName: getTableName({ + adapter: this, + config: globalConfig, + }), }) return result diff --git a/packages/db-postgres/src/createGlobalVersion.ts b/packages/db-postgres/src/createGlobalVersion.ts index a860e187b..eaac12007 100644 --- a/packages/db-postgres/src/createGlobalVersion.ts +++ b/packages/db-postgres/src/createGlobalVersion.ts @@ -4,10 +4,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types' import { sql } from 'drizzle-orm' import { type CreateGlobalVersionArgs } from 'payload/database' import { buildVersionGlobalFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export async function createGlobalVersion( @@ -16,8 +16,11 @@ export async function createGlobalVersion( ) { const db = this.sessions[req.transactionID]?.db || this.drizzle const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug) - const globalTableName = toSnakeCase(globalSlug) - const tableName = `_${globalTableName}_v` + const tableName = getTableName({ + adapter: this, + config: global, + versions: true, + }) const result = await upsertRow>({ adapter: this, diff --git a/packages/db-postgres/src/createVersion.ts b/packages/db-postgres/src/createVersion.ts index c8326a2fc..d651ea141 100644 --- a/packages/db-postgres/src/createVersion.ts +++ b/packages/db-postgres/src/createVersion.ts @@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types' import { sql } from 'drizzle-orm' import { buildVersionCollectionFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export async function createVersion( @@ -21,8 +21,11 @@ export async function createVersion( ) { const db = this.sessions[req.transactionID]?.db || this.drizzle const collection = this.payload.collections[collectionSlug].config - const collectionTableName = toSnakeCase(collectionSlug) - const tableName = `_${collectionTableName}_v` + const tableName = getTableName({ + adapter: this, + config: collection, + versions: true, + }) const result = await upsertRow>({ adapter: this, @@ -40,7 +43,15 @@ export async function createVersion( }) const table = this.tables[tableName] - const relationshipsTable = this.tables[`${tableName}_rels`] + const relationshipsTable = + this.tables[ + getTableName({ + adapter: this, + config: collection, + relationships: true, + versions: true, + }) + ] if (collection.versions.drafts) { await db.execute(sql` diff --git a/packages/db-postgres/src/deleteMany.ts b/packages/db-postgres/src/deleteMany.ts index 8b48e6a34..a892b6ba3 100644 --- a/packages/db-postgres/src/deleteMany.ts +++ b/packages/db-postgres/src/deleteMany.ts @@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database' import type { PayloadRequest } from 'payload/types' import { inArray } from 'drizzle-orm' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const deleteMany: DeleteMany = async function deleteMany( this: PostgresAdapter, @@ -14,7 +14,7 @@ export const deleteMany: DeleteMany = async function deleteMany( ) { const db = this.sessions[req.transactionID]?.db || this.drizzle const collectionConfig = this.payload.collections[collection].config - const tableName = toSnakeCase(collection) + const tableName = getTableName({ adapter: this, config: collectionConfig }) const result = await findMany({ adapter: this, diff --git a/packages/db-postgres/src/deleteOne.ts b/packages/db-postgres/src/deleteOne.ts index 1411e9bef..ade465129 100644 --- a/packages/db-postgres/src/deleteOne.ts +++ b/packages/db-postgres/src/deleteOne.ts @@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database' import type { PayloadRequest } from 'payload/types' import { eq } from 'drizzle-orm' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import { buildFindManyArgs } from './find/buildFindManyArgs.js' import buildQuery from './queries/buildQuery.js' import { selectDistinct } from './queries/selectDistinct.js' +import { getTableName } from './schema/getTableName.js' import { transform } from './transform/read/index.js' export const deleteOne: DeleteOne = async function deleteOne( @@ -17,7 +17,10 @@ export const deleteOne: DeleteOne = async function deleteOne( ) { const db = this.sessions[req.transactionID]?.db || this.drizzle const collection = this.payload.collections[collectionSlug].config - const tableName = toSnakeCase(collectionSlug) + const tableName = getTableName({ + adapter: this, + config: collection, + }) let docToDelete: Record const { joinAliases, joins, selectFields, where } = await buildQuery({ diff --git a/packages/db-postgres/src/deleteVersions.ts b/packages/db-postgres/src/deleteVersions.ts index 5b092bda8..c93b1e09a 100644 --- a/packages/db-postgres/src/deleteVersions.ts +++ b/packages/db-postgres/src/deleteVersions.ts @@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types' import { inArray } from 'drizzle-orm' import { buildVersionCollectionFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const deleteVersions: DeleteVersions = async function deleteVersion( this: PostgresAdapter, @@ -16,7 +16,11 @@ export const deleteVersions: DeleteVersions = async function deleteVersion( const db = this.sessions[req.transactionID]?.db || this.drizzle const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config - const tableName = `_${toSnakeCase(collection)}_v` + const tableName = getTableName({ + adapter: this, + config: collectionConfig, + versions: true, + }) const fields = buildVersionCollectionFields(collectionConfig) const { docs } = await findMany({ diff --git a/packages/db-postgres/src/find.ts b/packages/db-postgres/src/find.ts index f4bd2bda7..62719c276 100644 --- a/packages/db-postgres/src/find.ts +++ b/packages/db-postgres/src/find.ts @@ -1,38 +1,41 @@ import type { Find } from 'payload/database' import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const find: Find = async function find( this: PostgresAdapter, { collection, - limit: limitArg, + limit, locale, page = 1, pagination, req = {} as PayloadRequest, sort: sortArg, - where: whereArg, + where, }, ) { const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort + const tableName = getTableName({ + adapter: this, + config: collectionConfig, + }) return findMany({ adapter: this, fields: collectionConfig.fields, - limit: limitArg, + limit, locale, page, pagination, req, sort, - tableName: toSnakeCase(collection), - where: whereArg, + tableName, + where, }) } diff --git a/packages/db-postgres/src/find/traverseFields.ts b/packages/db-postgres/src/find/traverseFields.ts index de3dc2768..e4cdc070c 100644 --- a/packages/db-postgres/src/find/traverseFields.ts +++ b/packages/db-postgres/src/find/traverseFields.ts @@ -2,11 +2,12 @@ import type { Field } from 'payload/types' import { fieldAffectsData, tabHasName } from 'payload/types' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from '../types.js' import type { Result } from './buildFindManyArgs.js' +import { getTableName } from '../schema/getTableName.js' + type TraverseFieldArgs = { _locales: Record adapter: PostgresAdapter @@ -78,9 +79,22 @@ export const traverseFields = ({ with: {}, } - const arrayTableName = `${currentTableName}_${path}${toSnakeCase(field.name)}` + const arrayTableName = getTableName({ + adapter, + config: field, + parentTableName: currentTableName, + prefix: `${currentTableName}_${path}`, + }) - if (adapter.tables[`${arrayTableName}_locales`]) withArray.with._locales = _locales + const arrayTableNameWithLocales = getTableName({ + adapter, + config: field, + locales: true, + parentTableName: currentTableName, + prefix: `${currentTableName}_${path}`, + }) + + if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales currentArgs.with[`${path}${field.name}`] = withArray traverseFields({ @@ -128,9 +142,16 @@ export const traverseFields = ({ with: {}, } - const tableName = `${topLevelTableName}_blocks_${toSnakeCase(block.slug)}` + const tableName = getTableName({ + adapter, + config: block, + parentTableName: topLevelTableName, + prefix: `${topLevelTableName}_blocks_`, + }) - if (adapter.tables[`${tableName}_locales`]) withBlock.with._locales = _locales + if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) { + withBlock.with._locales = _locales + } topLevelArgs.with[blockKey] = withBlock traverseFields({ diff --git a/packages/db-postgres/src/findGlobal.ts b/packages/db-postgres/src/findGlobal.ts index 547bec5b0..360ba6863 100644 --- a/packages/db-postgres/src/findGlobal.ts +++ b/packages/db-postgres/src/findGlobal.ts @@ -1,17 +1,19 @@ import type { FindGlobal } from 'payload/database' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const findGlobal: FindGlobal = async function findGlobal( this: PostgresAdapter, { slug, locale, req, where }, ) { const globalConfig = this.payload.globals.config.find((config) => config.slug === slug) - const tableName = toSnakeCase(slug) + const tableName = getTableName({ + adapter: this, + config: globalConfig, + }) const { docs: [doc], diff --git a/packages/db-postgres/src/findGlobalVersions.ts b/packages/db-postgres/src/findGlobalVersions.ts index 64e2788a9..df49ed059 100644 --- a/packages/db-postgres/src/findGlobalVersions.ts +++ b/packages/db-postgres/src/findGlobalVersions.ts @@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database' import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types' import { buildVersionGlobalFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions( this: PostgresAdapter, @@ -27,7 +27,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV ) const sort = typeof sortArg === 'string' ? sortArg : '-createdAt' - const tableName = `_${toSnakeCase(global)}_v` + const tableName = getTableName({ + adapter: this, + config: globalConfig, + versions: true, + }) const fields = buildVersionGlobalFields(globalConfig) return findMany({ diff --git a/packages/db-postgres/src/findOne.ts b/packages/db-postgres/src/findOne.ts index 0fa7a7cbb..938d73980 100644 --- a/packages/db-postgres/src/findOne.ts +++ b/packages/db-postgres/src/findOne.ts @@ -1,17 +1,20 @@ import type { FindOneArgs } from 'payload/database' import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export async function findOne( this: PostgresAdapter, - { collection, locale, req = {} as PayloadRequest, where: incomingWhere }: FindOneArgs, + { collection, locale, req = {} as PayloadRequest, where }: FindOneArgs, ): Promise { const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config + const tableName = getTableName({ + adapter: this, + config: collectionConfig, + }) const { docs } = await findMany({ adapter: this, @@ -22,8 +25,8 @@ export async function findOne( pagination: false, req, sort: undefined, - tableName: toSnakeCase(collection), - where: incomingWhere, + tableName, + where, }) return docs?.[0] || null diff --git a/packages/db-postgres/src/findVersions.ts b/packages/db-postgres/src/findVersions.ts index d66341587..caf7be8e4 100644 --- a/packages/db-postgres/src/findVersions.ts +++ b/packages/db-postgres/src/findVersions.ts @@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database' import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types' import { buildVersionCollectionFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const findVersions: FindVersions = async function findVersions( this: PostgresAdapter, @@ -25,7 +25,11 @@ export const findVersions: FindVersions = async function findVersions( const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort - const tableName = `_${toSnakeCase(collection)}_v` + const tableName = getTableName({ + adapter: this, + config: collectionConfig, + versions: true, + }) const fields = buildVersionCollectionFields(collectionConfig) return findMany({ diff --git a/packages/db-postgres/src/index.ts b/packages/db-postgres/src/index.ts index 01d218925..42785c2ce 100644 --- a/packages/db-postgres/src/index.ts +++ b/packages/db-postgres/src/index.ts @@ -1,6 +1,5 @@ import type { Payload } from 'payload' import type { DatabaseAdapterObj } from 'payload/database' -export type { MigrateDownArgs, MigrateUpArgs } from './types.js' import fs from 'fs' import path from 'path' @@ -39,6 +38,8 @@ import { updateGlobal } from './updateGlobal.js' import { updateGlobalVersion } from './updateGlobalVersion.js' import { updateVersion } from './updateVersion.js' +export type { MigrateDownArgs, MigrateUpArgs } from './types.js' + export { sql } from 'drizzle-orm' export function postgresAdapter(args: Args): DatabaseAdapterObj { @@ -50,20 +51,24 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj name: 'postgres', // Postgres-specific + blockTableNames: {}, drizzle: undefined, enums: {}, fieldConstraints: {}, idType, + localesSuffix: args.localesSuffix || '_locales', logger: args.logger, pgSchema: undefined, pool: undefined, poolOptions: args.pool, push: args.push, relations: {}, + relationshipsSuffix: args.relationshipsSuffix || '_rels', schema: {}, schemaName: args.schemaName, sessions: {}, tables: {}, + versionsSuffix: args.versionsSuffix || '_v', // DatabaseAdapter beginTransaction, diff --git a/packages/db-postgres/src/init.ts b/packages/db-postgres/src/init.ts index d2d199e30..089762da1 100644 --- a/packages/db-postgres/src/init.ts +++ b/packages/db-postgres/src/init.ts @@ -4,11 +4,11 @@ import type { SanitizedCollectionConfig } from 'payload/types' import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core' import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import { buildTable } from './schema/build.js' +import { getTableName } from './schema/getTableName.js' export const init: Init = function init(this: PostgresAdapter) { if (this.schemaName) { @@ -25,7 +25,10 @@ export const init: Init = function init(this: PostgresAdapter) { } this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { - const tableName = toSnakeCase(collection.slug) + const tableName = getTableName({ + adapter: this, + config: collection, + }) buildTable({ adapter: this, @@ -37,10 +40,15 @@ export const init: Init = function init(this: PostgresAdapter) { fields: collection.fields, tableName, timestamps: collection.timestamps, + versions: false, }) if (collection.versions) { - const versionsTableName = `_${tableName}_v` + const versionsTableName = getTableName({ + adapter: this, + config: collection, + versions: true, + }) const versionFields = buildVersionCollectionFields(collection) buildTable({ @@ -53,12 +61,13 @@ export const init: Init = function init(this: PostgresAdapter) { fields: versionFields, tableName: versionsTableName, timestamps: true, + versions: true, }) } }) this.payload.config.globals.forEach((global) => { - const tableName = toSnakeCase(global.slug) + const tableName = getTableName({ adapter: this, config: global }) buildTable({ adapter: this, @@ -70,10 +79,11 @@ export const init: Init = function init(this: PostgresAdapter) { fields: global.fields, tableName, timestamps: false, + versions: false, }) if (global.versions) { - const versionsTableName = `_${tableName}_v` + const versionsTableName = getTableName({ adapter: this, config: global, versions: true }) const versionFields = buildVersionGlobalFields(global) buildTable({ @@ -86,6 +96,7 @@ export const init: Init = function init(this: PostgresAdapter) { fields: versionFields, tableName: versionsTableName, timestamps: true, + versions: true, }) } }) diff --git a/packages/db-postgres/src/migrate.ts b/packages/db-postgres/src/migrate.ts index 5c743fdea..bb86cb471 100644 --- a/packages/db-postgres/src/migrate.ts +++ b/packages/db-postgres/src/migrate.ts @@ -92,7 +92,7 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n payload.logger.info({ msg: `Migrating: ${migration.name}` }) - const pgAdapter = payload.db as PostgresAdapter + const pgAdapter = payload.db const drizzleJSON = generateDrizzleJson(pgAdapter.schema) try { diff --git a/packages/db-postgres/src/queries/getTableColumnFromPath.ts b/packages/db-postgres/src/queries/getTableColumnFromPath.ts index 08e083f81..9aa39672d 100644 --- a/packages/db-postgres/src/queries/getTableColumnFromPath.ts +++ b/packages/db-postgres/src/queries/getTableColumnFromPath.ts @@ -14,6 +14,8 @@ import { v4 as uuid } from 'uuid' import type { GenericColumn, GenericTable, PostgresAdapter } from '../types.js' import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js' +import { getTableName } from '../schema/getTableName.js' + type Constraint = { columnName: string table: GenericTable | PgTableWithColumns @@ -183,7 +185,13 @@ export const getTableColumnFromPath = ({ case 'group': { if (locale && field.localized && adapter.payload.config.localization) { - newTableName = `${tableName}_locales` + newTableName = getTableName({ + adapter, + config: field, + locales: true, + parentTableName: tableName, + prefix: `${tableName}_`, + }) joins[tableName] = eq( adapter.tables[tableName].id, @@ -218,7 +226,12 @@ export const getTableColumnFromPath = ({ } case 'array': { - newTableName = `${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}` + newTableName = getTableName({ + adapter, + config: field, + parentTableName: `${tableName}_${tableNameSuffix}`, + prefix: `${tableName}_${tableNameSuffix}`, + }) constraintPath = `${constraintPath}${field.name}.%.` if (locale && field.localized && adapter.payload.config.localization) { joins[newTableName] = and( @@ -265,7 +278,12 @@ export const getTableColumnFromPath = ({ const blockTypes = Array.isArray(value) ? value : [value] blockTypes.forEach((blockType) => { const block = field.blocks.find((block) => block.slug === blockType) - newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}` + newTableName = getTableName({ + adapter, + config: block, + parentTableName: tableName, + prefix: `${tableName}_blocks_`, + }) joins[newTableName] = eq( adapter.tables[tableName].id, adapter.tables[newTableName]._parentID, @@ -285,7 +303,12 @@ export const getTableColumnFromPath = ({ } const hasBlockField = field.blocks.some((block) => { - newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}` + newTableName = getTableName({ + adapter, + config: block, + parentTableName: tableName, + prefix: `${tableName}_blocks_`, + }) constraintPath = `${constraintPath}${field.name}.%.` let result const blockConstraints = [] @@ -351,7 +374,7 @@ export const getTableColumnFromPath = ({ case 'relationship': case 'upload': { let relationshipFields - const relationTableName = `${rootTableName}_rels` + const relationTableName = `${rootTableName}${adapter.relationshipsSuffix}` const newCollectionPath = pathSegments.slice(1).join('.') const aliasRelationshipTableName = uuid() const aliasRelationshipTable = alias( @@ -392,9 +415,13 @@ export const getTableColumnFromPath = ({ let newAliasTable if (typeof field.relationTo === 'string') { - newTableName = `${toSnakeCase(field.relationTo)}` + const relationshipConfig = adapter.payload.collections[field.relationTo].config + newTableName = getTableName({ + adapter, + config: relationshipConfig, + }) // parent to relationship join table - relationshipFields = adapter.payload.collections[field.relationTo].config.fields + relationshipFields = relationshipConfig.fields newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid())) @@ -413,7 +440,11 @@ export const getTableColumnFromPath = ({ } } else if (newCollectionPath === 'value') { const tableColumnsNames = field.relationTo.map( - (relationTo) => `"${aliasRelationshipTableName}"."${toSnakeCase(relationTo)}_id"`, + (relationTo) => + `"${aliasRelationshipTableName}"."${getTableName({ + adapter, + config: adapter.payload.collections[relationTo].config, + })}_id"`, ) return { constraints, @@ -460,7 +491,7 @@ export const getTableColumnFromPath = ({ if (field.localized && adapter.payload.config.localization) { // If localized, we go to localized table and set aliasTable to undefined // so it is not picked up below to be used as targetTable - newTableName = `${tableName}_locales` + newTableName = `${tableName}${adapter.localesSuffix}` const parentTable = aliasTable || adapter.tables[tableName] diff --git a/packages/db-postgres/src/queryDrafts.ts b/packages/db-postgres/src/queryDrafts.ts index ee6ddb0e8..8012dd0c3 100644 --- a/packages/db-postgres/src/queryDrafts.ts +++ b/packages/db-postgres/src/queryDrafts.ts @@ -2,9 +2,9 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types' import { type QueryDrafts, combineQueries } from 'payload/database' import { buildVersionCollectionFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import { findMany } from './find/findMany.js' +import { getTableName } from './schema/getTableName.js' export const queryDrafts: QueryDrafts = async function queryDrafts({ collection, @@ -17,7 +17,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts({ where, }) { const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config - const tableName = `_${toSnakeCase(collection)}_v` + const tableName = getTableName({ + adapter: this, + config: collectionConfig, + versions: true, + }) const fields = buildVersionCollectionFields(collectionConfig) const combinedWhere = combineQueries({ latest: { equals: true } }, where) diff --git a/packages/db-postgres/src/schema/build.ts b/packages/db-postgres/src/schema/build.ts index ab02a0e5f..e6b846790 100644 --- a/packages/db-postgres/src/schema/build.ts +++ b/packages/db-postgres/src/schema/build.ts @@ -11,10 +11,10 @@ import type { Field } from 'payload/types' import { relations } from 'drizzle-orm' import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core' import { fieldAffectsData } from 'payload/types' -import toSnakeCase from 'to-snake-case' import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js' +import { getTableName } from './getTableName.js' import { parentIDColumnMap } from './parentIDColumnMap.js' import { setColumnID } from './setColumnID.js' import { traverseFields } from './traverseFields.js' @@ -35,6 +35,7 @@ type Args = { rootTableName?: string tableName: string timestamps?: boolean + versions: boolean } type Result = { @@ -59,6 +60,7 @@ export const buildTable = ({ rootTableName: incomingRootTableName, tableName, timestamps, + versions, }: Args): Result => { const rootTableName = incomingRootTableName || tableName const columns: Record = baseColumns @@ -113,6 +115,7 @@ export const buildTable = ({ rootRelationsToBuild: rootRelationsToBuild || relationsToBuild, rootTableIDColType: rootTableIDColType || idColType, rootTableName, + versions, })) if (timestamps) { @@ -147,7 +150,7 @@ export const buildTable = ({ adapter.tables[tableName] = table if (hasLocalizedField) { - const localeTableName = `${tableName}_locales` + const localeTableName = `${tableName}${adapter.localesSuffix}` localesColumns.id = serial('id').primaryKey() localesColumns._locale = adapter.enums.enum__locales('_locale').notNull() localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id') @@ -288,11 +291,16 @@ export const buildTable = ({ } relationships.forEach((relationTo) => { - const formattedRelationTo = toSnakeCase(relationTo) + const relationshipConfig = adapter.payload.collections[relationTo].config + const formattedRelationTo = getTableName({ + adapter, + config: relationshipConfig, + throwValidationError: true, + }) let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer' - const relatedCollectionCustomID = adapter.payload.collections[ - relationTo - ].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id') + const relatedCollectionCustomID = relationshipConfig.fields.find( + (field) => fieldAffectsData(field) && field.name === 'id', + ) if (relatedCollectionCustomID?.type === 'number') colType = 'numeric' if (relatedCollectionCustomID?.type === 'text') colType = 'varchar' @@ -301,7 +309,7 @@ export const buildTable = ({ ).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' }) }) - const relationshipsTableName = `${tableName}_rels` + const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}` relationshipsTable = adapter.pgSchema.table( relationshipsTableName, @@ -333,7 +341,11 @@ export const buildTable = ({ } relationships.forEach((relationTo) => { - const relatedTableName = toSnakeCase(relationTo) + const relatedTableName = getTableName({ + adapter, + config: adapter.payload.collections[relationTo].config, + throwValidationError: true, + }) const idColumnName = `${relationTo}ID` result[idColumnName] = one(adapter.tables[relatedTableName], { fields: [relationshipsTable[idColumnName]], diff --git a/packages/db-postgres/src/schema/getTableName.ts b/packages/db-postgres/src/schema/getTableName.ts new file mode 100644 index 000000000..1119efca4 --- /dev/null +++ b/packages/db-postgres/src/schema/getTableName.ts @@ -0,0 +1,75 @@ +import type { DBIdentifierName } from 'payload/database' + +import { APIError } from 'payload/errors' +import toSnakeCase from 'to-snake-case' + +import type { PostgresAdapter } from '../types.js' + +type Args = { + adapter: PostgresAdapter + /** The collection, global or field config **/ + config: { + dbName?: DBIdentifierName + enumName?: DBIdentifierName + name?: string + slug?: string + } + /** Localized tables need to be given the locales suffix */ + locales?: boolean + /** For nested tables passed for the user custom dbName functions to handle their own iterations */ + parentTableName?: string + /** For sub tables (array for example) this needs to include the parentTableName */ + prefix?: string + /** Adds the relationships suffix */ + relationships?: boolean + /** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */ + target?: 'dbName' | 'enumName' + throwValidationError?: boolean + /** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */ + versions?: boolean +} + +/** + * Used to name database enums and tables + * Returns the table or enum name for a given entity + */ +export const getTableName = ({ + adapter, + config: { name, slug }, + config, + locales = false, + parentTableName, + prefix = '', + relationships = false, + target = 'dbName', + throwValidationError = false, + versions = false, +}: Args): string => { + let result: string + let custom = config[target] + + if (!custom && target === 'enumName') { + custom = config['dbName'] + } + + if (custom) { + result = typeof custom === 'function' ? custom({ tableName: parentTableName }) : custom + } else { + result = `${prefix}${toSnakeCase(name ?? slug)}` + } + + if (locales) result = `${result}${adapter.localesSuffix}` + if (versions) result = `_${result}${adapter.versionsSuffix}` + if (relationships) result = `${result}${adapter.relationshipsSuffix}` + + if (!throwValidationError) { + return result + } + + if (result.length > 63) { + throw new APIError( + `Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`, + ) + } + return result +} diff --git a/packages/db-postgres/src/schema/traverseFields.ts b/packages/db-postgres/src/schema/traverseFields.ts index 61fc2634d..a25872e62 100644 --- a/packages/db-postgres/src/schema/traverseFields.ts +++ b/packages/db-postgres/src/schema/traverseFields.ts @@ -27,6 +27,7 @@ import type { GenericColumns, IDType, PostgresAdapter } from '../types.js' import { hasLocalesTable } from '../utilities/hasLocalesTable.js' import { buildTable } from './build.js' import { createIndex } from './createIndex.js' +import { getTableName } from './getTableName.js' import { idToUUID } from './idToUUID.js' import { parentIDColumnMap } from './parentIDColumnMap.js' import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js' @@ -53,6 +54,7 @@ type Args = { rootRelationsToBuild?: Map rootTableIDColType: string rootTableName: string + versions: boolean } type Result = { @@ -86,7 +88,9 @@ export const traverseFields = ({ rootRelationsToBuild, rootTableIDColType, rootTableName, + versions, }: Args): Result => { + const throwValidationError = true let hasLocalizedField = false let hasLocalizedRelationshipField = false let hasManyTextField: 'index' | boolean = false @@ -217,7 +221,15 @@ export const traverseFields = ({ case 'radio': case 'select': { - const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}` + const enumName = getTableName({ + adapter, + config: field, + parentTableName: newTableName, + prefix: `enum_${newTableName}_`, + target: 'enumName', + throwValidationError, + versions, + }) adapter.enums[enumName] = pgEnum( enumName, @@ -231,7 +243,14 @@ export const traverseFields = ({ ) if (field.type === 'select' && field.hasMany) { - const selectTableName = `${newTableName}_${toSnakeCase(field.name)}` + const selectTableName = getTableName({ + adapter, + config: field, + parentTableName: newTableName, + prefix: `${newTableName}_`, + throwValidationError, + versions, + }) const baseColumns: Record = { order: integer('order').notNull(), parent: parentIDColumnMap[parentIDColType]('parent_id') @@ -266,6 +285,7 @@ export const traverseFields = ({ disableUnique, fields: [], tableName: selectTableName, + versions, }) relationsToBuild.set(fieldName, selectTableName) @@ -296,7 +316,13 @@ export const traverseFields = ({ case 'array': { const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull - const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}` + const arrayTableName = getTableName({ + adapter, + config: field, + parentTableName: newTableName, + prefix: `${newTableName}_`, + throwValidationError, + }) const baseColumns: Record = { _order: integer('_order').notNull(), _parentID: parentIDColumnMap[parentIDColType]('_parent_id') @@ -334,6 +360,7 @@ export const traverseFields = ({ rootTableIDColType, rootTableName, tableName: arrayTableName, + versions, }) if (subHasManyTextField) { @@ -356,7 +383,7 @@ export const traverseFields = ({ } if (hasLocalesTable(field.fields)) { - result._locales = many(adapter.tables[`${arrayTableName}_locales`]) + result._locales = many(adapter.tables[`${arrayTableName}${adapter.localesSuffix}`]) } subRelationsToBuild.forEach((val, key) => { @@ -375,7 +402,13 @@ export const traverseFields = ({ const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull field.blocks.forEach((block) => { - const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}` + const blockTableName = getTableName({ + adapter, + config: block, + parentTableName: rootTableName, + prefix: `${rootTableName}_blocks_`, + throwValidationError, + }) if (!adapter.tables[blockTableName]) { const baseColumns: Record = { _order: integer('_order').notNull(), @@ -416,6 +449,7 @@ export const traverseFields = ({ rootTableIDColType, rootTableName, tableName: blockTableName, + versions, }) if (subHasManyTextField) { @@ -439,7 +473,9 @@ export const traverseFields = ({ } if (hasLocalesTable(block.fields)) { - result._locales = many(adapter.tables[`${blockTableName}_locales`]) + result._locales = many( + adapter.tables[`${blockTableName}${adapter.localesSuffix}`], + ) } subRelationsToBuild.forEach((val, key) => { @@ -451,7 +487,7 @@ export const traverseFields = ({ ) adapter.relations[`relations_${blockTableName}`] = blockTableRelations - } else if (process.env.NODE_ENV !== 'production') { + } else if (process.env.NODE_ENV !== 'production' && !versions) { validateExistingBlockIsIdentical({ block, localized: field.localized, @@ -459,7 +495,7 @@ export const traverseFields = ({ table: adapter.tables[blockTableName], }) } - + adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName) }) @@ -498,6 +534,7 @@ export const traverseFields = ({ rootRelationsToBuild, rootTableIDColType, rootTableName, + versions, }) if (groupHasLocalizedField) hasLocalizedField = true @@ -540,6 +577,7 @@ export const traverseFields = ({ rootRelationsToBuild, rootTableIDColType, rootTableName, + versions, }) if (groupHasLocalizedField) hasLocalizedField = true @@ -583,6 +621,7 @@ export const traverseFields = ({ rootRelationsToBuild, rootTableIDColType, rootTableName, + versions, }) if (tabHasLocalizedField) hasLocalizedField = true @@ -626,6 +665,7 @@ export const traverseFields = ({ rootRelationsToBuild, rootTableIDColType, rootTableName, + versions, }) if (rowHasLocalizedField) hasLocalizedField = true diff --git a/packages/db-postgres/src/types.ts b/packages/db-postgres/src/types.ts index 12dad0049..7a753ed56 100644 --- a/packages/db-postgres/src/types.ts +++ b/packages/db-postgres/src/types.ts @@ -24,11 +24,14 @@ export type DrizzleDB = NodePgDatabase> export type Args = { idType?: 'serial' | 'uuid' + localesSuffix?: string logger?: DrizzleConfig['logger'] migrationDir?: string pool: PoolConfig push?: boolean + relationshipsSuffix?: string schemaName?: string + versionsSuffix?: string } export type GenericColumn = PgColumn< @@ -58,6 +61,10 @@ export type DrizzleTransaction = PgTransaction< > export type PostgresAdapter = BaseDatabaseAdapter & { + /** + * Used internally to map the block name to the table name + */ + blockTableNames: Record drizzle: DrizzleDB enums: Record /** @@ -66,12 +73,14 @@ export type PostgresAdapter = BaseDatabaseAdapter & { */ fieldConstraints: Record> idType: Args['idType'] + localesSuffix?: string logger: DrizzleConfig['logger'] pgSchema?: { table: PgTableFn } | PgSchema pool: Pool poolOptions: Args['pool'] push: boolean relations: Record + relationshipsSuffix?: string schema: Record schemaName?: Args['schemaName'] sessions: { @@ -82,6 +91,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & { } } tables: Record> + versionsSuffix?: string } export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar' @@ -98,9 +108,11 @@ declare module 'payload' { drizzle: DrizzleDB enums: Record fieldConstraints: Record> + localeSuffix?: string pool: Pool push: boolean relations: Record + relationshipsSuffix?: string schema: Record sessions: { [id: string]: { @@ -110,5 +122,6 @@ declare module 'payload' { } } tables: Record + versionsSuffix?: string } } diff --git a/packages/db-postgres/src/update.ts b/packages/db-postgres/src/update.ts index 2dce36979..ab59c59b0 100644 --- a/packages/db-postgres/src/update.ts +++ b/packages/db-postgres/src/update.ts @@ -1,11 +1,10 @@ import type { UpdateOne } from 'payload/database' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' import buildQuery from './queries/buildQuery.js' import { selectDistinct } from './queries/selectDistinct.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export const updateOne: UpdateOne = async function updateOne( @@ -14,7 +13,10 @@ export const updateOne: UpdateOne = async function updateOne( ) { const db = this.sessions[req.transactionID]?.db || this.drizzle const collection = this.payload.collections[collectionSlug].config - const tableName = toSnakeCase(collectionSlug) + const tableName = getTableName({ + adapter: this, + config: collection, + }) const whereToUse = whereArg || { id: { equals: id } } let idToUpdate = id @@ -49,7 +51,7 @@ export const updateOne: UpdateOne = async function updateOne( fields: collection.fields, operation: 'update', req, - tableName: toSnakeCase(collectionSlug), + tableName, }) return result diff --git a/packages/db-postgres/src/updateGlobal.ts b/packages/db-postgres/src/updateGlobal.ts index 3fd6bff72..b5197c700 100644 --- a/packages/db-postgres/src/updateGlobal.ts +++ b/packages/db-postgres/src/updateGlobal.ts @@ -1,10 +1,9 @@ import type { UpdateGlobalArgs } from 'payload/database' import type { PayloadRequest, TypeWithID } from 'payload/types' -import toSnakeCase from 'to-snake-case' - import type { PostgresAdapter } from './types.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export async function updateGlobal( @@ -13,7 +12,10 @@ export async function updateGlobal( ): Promise { const db = this.sessions[req.transactionID]?.db || this.drizzle const globalConfig = this.payload.globals.config.find((config) => config.slug === slug) - const tableName = toSnakeCase(slug) + const tableName = getTableName({ + adapter: this, + config: globalConfig, + }) const existingGlobal = await db.query[tableName].findFirst({}) diff --git a/packages/db-postgres/src/updateGlobalVersion.ts b/packages/db-postgres/src/updateGlobalVersion.ts index 5e1aee29a..0f0f82e28 100644 --- a/packages/db-postgres/src/updateGlobalVersion.ts +++ b/packages/db-postgres/src/updateGlobalVersion.ts @@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database' import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types' import { buildVersionGlobalFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import buildQuery from './queries/buildQuery.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export async function updateGlobalVersion( @@ -25,7 +25,11 @@ export async function updateGlobalVersion( ({ slug }) => slug === global, ) const whereToUse = whereArg || { id: { equals: id } } - const tableName = `_${toSnakeCase(global)}_v` + const tableName = getTableName({ + adapter: this, + config: globalConfig, + versions: true, + }) const fields = buildVersionGlobalFields(globalConfig) const { where } = await buildQuery({ diff --git a/packages/db-postgres/src/updateVersion.ts b/packages/db-postgres/src/updateVersion.ts index 8dcb9e997..b93e97912 100644 --- a/packages/db-postgres/src/updateVersion.ts +++ b/packages/db-postgres/src/updateVersion.ts @@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database' import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types' import { buildVersionCollectionFields } from 'payload/versions' -import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from './types.js' import buildQuery from './queries/buildQuery.js' +import { getTableName } from './schema/getTableName.js' import { upsertRow } from './upsertRow/index.js' export async function updateVersion( @@ -23,7 +23,11 @@ export async function updateVersion( const db = this.sessions[req.transactionID]?.db || this.drizzle const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const whereToUse = whereArg || { id: { equals: id } } - const tableName = `_${toSnakeCase(collection)}_v` + const tableName = getTableName({ + adapter: this, + config: collectionConfig, + versions: true, + }) const fields = buildVersionCollectionFields(collectionConfig) const { where } = await buildQuery({ diff --git a/packages/payload/src/collections/config/schema.ts b/packages/payload/src/collections/config/schema.ts index 003460166..871753cf5 100644 --- a/packages/payload/src/collections/config/schema.ts +++ b/packages/payload/src/collections/config/schema.ts @@ -108,6 +108,7 @@ const collectionSchema = joi.object().keys({ joi.boolean(), ), custom: joi.object().pattern(joi.string(), joi.any()), + dbName: joi.alternatives().try(joi.string(), joi.func()), defaultSort: joi.string(), disableDuplicate: joi.bool(), endpoints: endpointsSchema, diff --git a/packages/payload/src/database/types.ts b/packages/payload/src/database/types.ts index 85b635ad8..4f1293749 100644 --- a/packages/payload/src/database/types.ts +++ b/packages/payload/src/database/types.ts @@ -411,3 +411,10 @@ export type DatabaseAdapterResult = { defaultIDType: 'number' | 'text' init: (args: { payload: Payload }) => T } + +export type DBIdentifierName = + | ((Args: { + /** The name of the parent table when using relational DBs */ + tableName?: string + }) => string) + | string diff --git a/packages/payload/src/exports/database.ts b/packages/payload/src/exports/database.ts index 65a7e3d2a..bd8521ad8 100644 --- a/packages/payload/src/exports/database.ts +++ b/packages/payload/src/exports/database.ts @@ -12,6 +12,7 @@ export type { CreateMigration, CreateVersion, CreateVersionArgs, + DBIdentifierName, DatabaseAdapterResult as DatabaseAdapterObj, DeleteMany, DeleteManyArgs, diff --git a/packages/payload/src/exports/versions.ts b/packages/payload/src/exports/versions.ts index 4be0bbbb5..65b9a9018 100644 --- a/packages/payload/src/exports/versions.ts +++ b/packages/payload/src/exports/versions.ts @@ -4,6 +4,5 @@ export { deleteCollectionVersions } from '../versions/deleteCollectionVersions.j export { enforceMaxVersions } from '../versions/enforceMaxVersions.js' export { getLatestCollectionVersion } from '../versions/getLatestCollectionVersion.js' export { getLatestGlobalVersion } from '../versions/getLatestGlobalVersion.js' -export { getVersionsModelName } from '../versions/getVersionsModelName.js' export { saveVersion } from '../versions/saveVersion.js' export type { TypeWithVersion } from '../versions/types.js' diff --git a/packages/payload/src/fields/config/schema.ts b/packages/payload/src/fields/config/schema.ts index 22a25ec23..2381b8f1f 100644 --- a/packages/payload/src/fields/config/schema.ts +++ b/packages/payload/src/fields/config/schema.ts @@ -201,9 +201,11 @@ export const select = baseField.keys({ isClearable: joi.boolean().default(false), isSortable: joi.boolean().default(false), }), + dbName: joi.alternatives().try(joi.string(), joi.func()), defaultValue: joi .alternatives() .try(joi.string().allow(''), joi.array().items(joi.string().allow('')), joi.func()), + enumName: joi.alternatives().try(joi.string(), joi.func()), hasMany: joi.boolean().default(false), options: joi .array() @@ -233,6 +235,7 @@ export const radio = baseField.keys({ layout: joi.string().valid('vertical', 'horizontal'), }), defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()), + enumName: joi.alternatives().try(joi.string(), joi.func()), options: joi .array() .min(1) @@ -310,6 +313,7 @@ export const array = baseField.keys({ .default({}), }) .default({}), + dbName: joi.alternatives().try(joi.string(), joi.func()), defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()), fields: joi.array().items(joi.link('#field')).required(), interfaceName: joi.string(), @@ -410,6 +414,7 @@ export const blocks = baseField.keys({ joi.object({ slug: joi.string().required(), custom: joi.object().pattern(joi.string(), joi.any()), + dbName: joi.alternatives().try(joi.string(), joi.func()), fields: joi.array().items(joi.link('#field')), graphQL: joi.object().keys({ singularName: joi.string(), diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 8f61d22eb..94ad02aaa 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -17,6 +17,7 @@ import type { } from '../../admin/types.js' import type { User } from '../../auth/index.js' import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js' +import type { DBIdentifierName } from '../../database/types.js' import type { SanitizedGlobalConfig } from '../../globals/config/types.js' import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js' import type { ClientFieldConfig } from './client.js' @@ -68,9 +69,11 @@ export type FieldAccess = (args: { * The `id` of the current document being read or updated. `id` is undefined during the `create` operation. */ id?: number | string - /** The `Express` request object containing the currently authenticated `user` */ - req: PayloadRequest /** The `payload` object to interface with the payload API */ + req: PayloadRequest + /** + * Immediately adjacent data to this field. For example, if this is a `group` field, then `siblingData` will be the other fields within the group. + */ siblingData?: Partial

}) => Promise | boolean @@ -478,6 +481,14 @@ export type SelectField = FieldBase & { isClearable?: boolean isSortable?: boolean } + /** + * Customize the SQL table name + */ + dbName?: DBIdentifierName + /** + * Customize the DB enum name + */ + enumName?: DBIdentifierName hasMany?: boolean options: Option[] type: 'select' @@ -584,6 +595,10 @@ export type ArrayField = FieldBase & { } & Admin['components'] initCollapsed?: boolean } + /** + * Customize the SQL table name + */ + dbName?: DBIdentifierName fields: Field[] /** Customize generated GraphQL and Typescript schema names. * By default it is bound to the collection. @@ -606,6 +621,14 @@ export type RadioField = FieldBase & { } layout?: 'horizontal' | 'vertical' } + /** + * Customize the SQL table name + */ + dbName?: DBIdentifierName + /** + * Customize the DB enum name + */ + enumName?: DBIdentifierName options: Option[] type: 'radio' } @@ -613,6 +636,10 @@ export type RadioField = FieldBase & { export type Block = { /** Extension point to add your custom data. */ custom?: Record + /** + * Customize the SQL table name + */ + dbName?: DBIdentifierName fields: Field[] /** @deprecated - please migrate to the interfaceName property instead. */ graphQL?: { diff --git a/packages/payload/src/globals/config/schema.ts b/packages/payload/src/globals/config/schema.ts index c661cefd1..b58554f7f 100644 --- a/packages/payload/src/globals/config/schema.ts +++ b/packages/payload/src/globals/config/schema.ts @@ -49,6 +49,7 @@ const globalSchema = joi preview: joi.func(), }), custom: joi.object().pattern(joi.string(), joi.any()), + dbName: joi.alternatives().try(joi.string(), joi.func()), endpoints: endpointsSchema, fields: joi.array(), graphQL: joi.alternatives().try( diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index 92d0a5788..3587c27c4 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -16,6 +16,7 @@ import type { GeneratePreviewURL, LivePreviewConfig, } from '../../config/types.js' +import type { DBIdentifierName } from '../../database/types.js' import type { Field } from '../../fields/config/types.js' import type { PayloadRequest, RequestContext } from '../../types/index.js' import type { Where } from '../../types/index.js' @@ -141,6 +142,10 @@ export type GlobalConfig = { admin?: GlobalAdminOptions /** Extension point to add your custom data. */ custom?: Record + /** + * Customize the SQL table name + */ + dbName?: DBIdentifierName endpoints?: Omit[] | false fields: Field[] graphQL?: diff --git a/packages/payload/src/versions/getVersionsModelName.ts b/packages/payload/src/versions/getVersionsModelName.ts deleted file mode 100644 index 4177ba8ac..000000000 --- a/packages/payload/src/versions/getVersionsModelName.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { SanitizedCollectionConfig } from '../collections/config/types.ts' -import type { SanitizedGlobalConfig } from '../globals/config/types.js' - -export const getVersionsModelName = ( - entity: SanitizedCollectionConfig | SanitizedGlobalConfig, -): string => `_${entity.slug}_versions` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b367a650f..7288bb2c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -374,9 +374,6 @@ importers: mongoose: specifier: 6.12.3 version: 6.12.3 - mongoose-aggregate-paginate-v2: - specifier: 1.0.6 - version: 1.0.6 mongoose-paginate-v2: specifier: 1.7.22 version: 1.7.22 @@ -9774,7 +9771,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(@swc/core@1.4.2) + webpack: 5.90.3(@swc/core@1.4.2)(esbuild@0.19.12)(webpack-cli@5.1.4) dev: true /file-type@16.5.4: @@ -12275,7 +12272,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(@swc/core@1.4.2) + webpack: 5.90.3(@swc/core@1.4.2)(esbuild@0.19.12)(webpack-cli@5.1.4) webpack-sources: 1.4.3 dev: true @@ -12416,11 +12413,6 @@ packages: '@mongodb-js/saslprep': 1.1.4 dev: true - /mongoose-aggregate-paginate-v2@1.0.6: - resolution: {integrity: sha512-UuALu+mjhQa1K9lMQvjLL3vm3iALvNw8PQNIh2gp1b+tO5hUa0NC0Wf6/8QrT9PSJVTihXaD8hQVy3J4e0jO0Q==} - engines: {node: '>=4.0.0'} - dev: false - /mongoose-paginate-v2@1.7.22: resolution: {integrity: sha512-xW5GugkE21DJiu9e13EOxKt4ejEKQkRP/S1PkkXRjnk2rRZVKBcld1nPV+VJ/YCPfm8hb3sz9OvI7O38RmixkA==} engines: {node: '>=4.0.0'} @@ -15656,31 +15648,6 @@ packages: webpack: 5.90.3(@swc/core@1.4.2)(esbuild@0.19.12)(webpack-cli@5.1.4) dev: true - /terser-webpack-plugin@5.3.10(@swc/core@1.4.2)(webpack@5.90.3): - resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - dependencies: - '@jridgewell/trace-mapping': 0.3.23 - '@swc/core': 1.4.2 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.28.1 - webpack: 5.90.3(@swc/core@1.4.2) - dev: true - /terser@5.28.1: resolution: {integrity: sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==} engines: {node: '>=10'} @@ -16440,46 +16407,6 @@ packages: engines: {node: '>=10.13.0'} dev: true - /webpack@5.90.3(@swc/core@1.4.2): - resolution: {integrity: sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.11.3 - acorn-import-assertions: 1.9.0(acorn@8.11.3) - browserslist: 4.23.0 - chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.1 - es-module-lexer: 1.4.1 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.4.2)(webpack@5.90.3) - watchpack: 2.4.0 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - dev: true - /webpack@5.90.3(@swc/core@1.4.2)(esbuild@0.19.12)(webpack-cli@5.1.4): resolution: {integrity: sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==} engines: {node: '>=10.13.0'} diff --git a/test/database/config.ts b/test/database/config.ts index b4aec87ef..0f768de70 100644 --- a/test/database/config.ts +++ b/test/database/config.ts @@ -63,7 +63,98 @@ export default buildConfigWithDefaults({ singular: 'Relation B', }, }, + { + slug: 'custom-schema', + dbName: 'customs', + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'localizedText', + type: 'text', + localized: true, + }, + { + name: 'relationship', + type: 'relationship', + hasMany: true, + relationTo: 'relation-a', + }, + { + name: 'select', + type: 'select', + dbName: ({ tableName }) => `${tableName}_customSelect`, + enumName: 'selectEnum', + hasMany: true, + options: ['a', 'b', 'c'], + }, + { + name: 'radio', + type: 'select', + enumName: 'radioEnum', + options: ['a', 'b', 'c'], + }, + { + name: 'array', + type: 'array', + dbName: 'customArrays', + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'localizedText', + type: 'text', + localized: true, + }, + ], + }, + { + name: 'blocks', + type: 'blocks', + blocks: [ + { + slug: 'block', + dbName: 'customBlocks', + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'localizedText', + type: 'text', + localized: true, + }, + ], + }, + ], + }, + ], + versions: true, + }, ], + globals: [ + { + slug: 'global', + // @ts-expect-error + dbName: 'customGlobal', + fields: [ + { + name: 'text', + type: 'text', + }, + ], + versions: true, + }, + ], + localization: { + defaultLocale: 'en', + locales: ['en', 'es'], + }, onInit: async (payload) => { await payload.create({ collection: 'users', diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index dc0a09299..c9cc8104e 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -131,6 +131,59 @@ describe('database', () => { }) }) + describe('schema', () => { + it('should use custom dbNames', () => { + expect(payload.db).toBeDefined() + + if (payload.db.name === 'mongoose') { + // @ts-expect-error + const db: MongooseAdapter = payload.db + + expect(db.collections['custom-schema'].modelName).toStrictEqual('customs') + expect(db.versions['custom-schema'].modelName).toStrictEqual('_customs_versions') + expect(db.versions.global.modelName).toStrictEqual('_customGlobal_versions') + } else { + // @ts-expect-error + const db: PostgresAdapter = payload.db + + // collection + expect(db.tables.customs).toBeDefined() + + // collection versions + expect(db.tables._customs_v).toBeDefined() + + // collection relationships + expect(db.tables.customs_rels).toBeDefined() + + // collection localized + expect(db.tables.customs_locales).toBeDefined() + + // global + expect(db.tables.customGlobal).toBeDefined() + expect(db.tables._customGlobal_v).toBeDefined() + + // select + expect(db.tables.customs_customSelect).toBeDefined() + + // array + expect(db.tables.customArrays).toBeDefined() + + // array localized + expect(db.tables.customArrays_locales).toBeDefined() + + // blocks + expect(db.tables.customBlocks).toBeDefined() + + // localized blocks + expect(db.tables.customBlocks_locales).toBeDefined() + + // enum names + expect(db.enums.selectEnum).toBeDefined() + expect(db.enums.radioEnum).toBeDefined() + } + }) + }) + describe('transactions', () => { describe('local api', () => { it('should commit multiple operations in isolation', async () => {