feat: custom db naming for postgres and mongodb (#5697)

This commit is contained in:
Dan Ribbens
2024-04-05 17:09:08 -04:00
committed by GitHub
parent 6fec2bbe1c
commit f39f95af6d
50 changed files with 634 additions and 208 deletions

View File

@@ -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. | | **`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. | | **`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. | | **`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) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._ _\* An asterisk denotes that a property is required._

View File

@@ -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. | | **`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. | | **`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) | | **`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._ _\* An asterisk denotes that a property is required._

View File

@@ -38,12 +38,18 @@ export default buildConfig({
### Options ### Options
| Option | Description | | Option | Description |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `pool` | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. | | `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. | | `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. | | `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'. | | `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 ### Access to Drizzle

View File

@@ -45,6 +45,7 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`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). | | **`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._ _\* An asterisk denotes that a property is required._

View File

@@ -80,6 +80,7 @@ Blocks are defined as separate configs of their own.
| **`imageAltText`** | Customize this block's image thumbnail alt text. | | **`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). | | **`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`. | | **`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) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
#### Auto-generated data per block #### Auto-generated data per block

View File

@@ -20,27 +20,27 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
### Config ### Config
| Option | Description | | Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`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. | | **`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. | | **`min`** | Minimum value accepted. Used in the default `validation` function. |
| **`max`** | Maximum 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. | | **`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. | | **`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. | | **`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. | | **`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. | | **`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) | | **`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. | | **`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) | | **`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) | | **`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. | | **`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) | | **`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. | | **`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. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._ _\* An asterisk denotes that a property is required._

View File

@@ -36,6 +36,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`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._ _\* An asterisk denotes that a property is required._

View File

@@ -38,6 +38,8 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
| **`required`** | Require this field to have a value. | | **`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. | | **`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) | | **`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._ _\* An asterisk denotes that a property is required._

View File

@@ -30,7 +30,6 @@
"get-port": "5.1.1", "get-port": "5.1.1",
"http-status": "1.6.2", "http-status": "1.6.2",
"mongoose": "6.12.3", "mongoose": "6.12.3",
"mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22", "mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2", "prompts": "2.4.2",
"uuid": "9.0.0" "uuid": "9.0.0"

View File

@@ -5,11 +5,7 @@ import type { SanitizedCollectionConfig } from 'payload/types'
import mongoose from 'mongoose' import mongoose from 'mongoose'
import paginate from 'mongoose-paginate-v2' import paginate from 'mongoose-paginate-v2'
import { import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
buildVersionCollectionFields,
buildVersionGlobalFields,
getVersionsModelName,
} from 'payload/versions'
import type { MongooseAdapter } from './index.js' import type { MongooseAdapter } from './index.js'
import type { CollectionModel } from './types.js' import type { CollectionModel } from './types.js'
@@ -18,13 +14,14 @@ import buildCollectionSchema from './models/buildCollectionSchema.js'
import { buildGlobalModel } from './models/buildGlobalModel.js' import { buildGlobalModel } from './models/buildGlobalModel.js'
import buildSchema from './models/buildSchema.js' import buildSchema from './models/buildSchema.js'
import getBuildQueryPlugin from './queries/buildQuery.js' import getBuildQueryPlugin from './queries/buildQuery.js'
import { getDBName } from './utilities/getDBName.js'
export const init: Init = function init(this: MongooseAdapter) { export const init: Init = function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schema = buildCollectionSchema(collection, this.payload.config) const schema = buildCollectionSchema(collection, this.payload.config)
if (collection.versions) { if (collection.versions) {
const versionModelName = getVersionsModelName(collection) const versionModelName = getDBName({ config: collection, versions: true })
const versionCollectionFields = buildVersionCollectionFields(collection) const versionCollectionFields = buildVersionCollectionFields(collection)
@@ -54,7 +51,7 @@ export const init: Init = function init(this: MongooseAdapter) {
} }
const model = mongoose.model( const model = mongoose.model(
collection.slug, getDBName({ config: collection }),
schema, schema,
this.autoPluralization === true ? undefined : collection.slug, this.autoPluralization === true ? undefined : collection.slug,
) as CollectionModel ) as CollectionModel
@@ -72,7 +69,7 @@ export const init: Init = function init(this: MongooseAdapter) {
this.payload.config.globals.forEach((global) => { this.payload.config.globals.forEach((global) => {
if (global.versions) { if (global.versions) {
const versionModelName = getVersionsModelName(global) const versionModelName = getDBName({ config: global, versions: true })
const versionGlobalFields = buildVersionGlobalFields(global) const versionGlobalFields = buildVersionGlobalFields(global)

View File

@@ -363,7 +363,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
} }
if (field.localized && config.localization) { if (field.localized && config.localization) {
config.localization.locales.forEach((locale) => { config.localization.locales.forEach((locale) => {
schema.index({ [`${field.name}.${locale}`]: '2dsphere' }, indexOptions) schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
}) })
} else { } else {
schema.index({ [field.name]: '2dsphere' }, indexOptions) schema.index({ [field.name]: '2dsphere' }, indexOptions)

View File

@@ -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
}

View File

@@ -1,9 +1,8 @@
import type { Create } from 'payload/database' import type { Create } from 'payload/database'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export const create: Create = async function create( export const create: Create = async function create(
@@ -20,7 +19,10 @@ export const create: Create = async function create(
fields: collection.fields, fields: collection.fields,
operation: 'create', operation: 'create',
req, req,
tableName: toSnakeCase(collectionSlug), tableName: getTableName({
adapter: this,
config: collection,
}),
}) })
return result return result

View File

@@ -1,10 +1,9 @@
import type { CreateGlobalArgs } from 'payload/database' import type { CreateGlobalArgs } from 'payload/database'
import type { PayloadRequest, TypeWithID } from 'payload/types' import type { PayloadRequest, TypeWithID } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export async function createGlobal<T extends TypeWithID>( export async function createGlobal<T extends TypeWithID>(
@@ -21,7 +20,10 @@ export async function createGlobal<T extends TypeWithID>(
fields: globalConfig.fields, fields: globalConfig.fields,
operation: 'create', operation: 'create',
req, req,
tableName: toSnakeCase(slug), tableName: getTableName({
adapter: this,
config: globalConfig,
}),
}) })
return result return result

View File

@@ -4,10 +4,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
import { sql } from 'drizzle-orm' import { sql } from 'drizzle-orm'
import { type CreateGlobalVersionArgs } from 'payload/database' import { type CreateGlobalVersionArgs } from 'payload/database'
import { buildVersionGlobalFields } from 'payload/versions' import { buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export async function createGlobalVersion<T extends TypeWithID>( export async function createGlobalVersion<T extends TypeWithID>(
@@ -16,8 +16,11 @@ export async function createGlobalVersion<T extends TypeWithID>(
) { ) {
const db = this.sessions[req.transactionID]?.db || this.drizzle const db = this.sessions[req.transactionID]?.db || this.drizzle
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug) const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
const globalTableName = toSnakeCase(globalSlug) const tableName = getTableName({
const tableName = `_${globalTableName}_v` adapter: this,
config: global,
versions: true,
})
const result = await upsertRow<TypeWithVersion<T>>({ const result = await upsertRow<TypeWithVersion<T>>({
adapter: this, adapter: this,

View File

@@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
import { sql } from 'drizzle-orm' import { sql } from 'drizzle-orm'
import { buildVersionCollectionFields } from 'payload/versions' import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export async function createVersion<T extends TypeWithID>( export async function createVersion<T extends TypeWithID>(
@@ -21,8 +21,11 @@ export async function createVersion<T extends TypeWithID>(
) { ) {
const db = this.sessions[req.transactionID]?.db || this.drizzle const db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config const collection = this.payload.collections[collectionSlug].config
const collectionTableName = toSnakeCase(collectionSlug) const tableName = getTableName({
const tableName = `_${collectionTableName}_v` adapter: this,
config: collection,
versions: true,
})
const result = await upsertRow<TypeWithVersion<T>>({ const result = await upsertRow<TypeWithVersion<T>>({
adapter: this, adapter: this,
@@ -40,7 +43,15 @@ export async function createVersion<T extends TypeWithID>(
}) })
const table = this.tables[tableName] 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) { if (collection.versions.drafts) {
await db.execute(sql` await db.execute(sql`

View File

@@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database'
import type { PayloadRequest } from 'payload/types' import type { PayloadRequest } from 'payload/types'
import { inArray } from 'drizzle-orm' import { inArray } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const deleteMany: DeleteMany = async function deleteMany( export const deleteMany: DeleteMany = async function deleteMany(
this: PostgresAdapter, this: PostgresAdapter,
@@ -14,7 +14,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
) { ) {
const db = this.sessions[req.transactionID]?.db || this.drizzle const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig = this.payload.collections[collection].config const collectionConfig = this.payload.collections[collection].config
const tableName = toSnakeCase(collection) const tableName = getTableName({ adapter: this, config: collectionConfig })
const result = await findMany({ const result = await findMany({
adapter: this, adapter: this,

View File

@@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database'
import type { PayloadRequest } from 'payload/types' import type { PayloadRequest } from 'payload/types'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { buildFindManyArgs } from './find/buildFindManyArgs.js' import { buildFindManyArgs } from './find/buildFindManyArgs.js'
import buildQuery from './queries/buildQuery.js' import buildQuery from './queries/buildQuery.js'
import { selectDistinct } from './queries/selectDistinct.js' import { selectDistinct } from './queries/selectDistinct.js'
import { getTableName } from './schema/getTableName.js'
import { transform } from './transform/read/index.js' import { transform } from './transform/read/index.js'
export const deleteOne: DeleteOne = async function deleteOne( 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 db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config const collection = this.payload.collections[collectionSlug].config
const tableName = toSnakeCase(collectionSlug) const tableName = getTableName({
adapter: this,
config: collection,
})
let docToDelete: Record<string, unknown> let docToDelete: Record<string, unknown>
const { joinAliases, joins, selectFields, where } = await buildQuery({ const { joinAliases, joins, selectFields, where } = await buildQuery({

View File

@@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { inArray } from 'drizzle-orm' import { inArray } from 'drizzle-orm'
import { buildVersionCollectionFields } from 'payload/versions' import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const deleteVersions: DeleteVersions = async function deleteVersion( export const deleteVersions: DeleteVersions = async function deleteVersion(
this: PostgresAdapter, this: PostgresAdapter,
@@ -16,7 +16,11 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
const db = this.sessions[req.transactionID]?.db || this.drizzle const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config 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 fields = buildVersionCollectionFields(collectionConfig)
const { docs } = await findMany({ const { docs } = await findMany({

View File

@@ -1,38 +1,41 @@
import type { Find } from 'payload/database' import type { Find } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types' import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const find: Find = async function find( export const find: Find = async function find(
this: PostgresAdapter, this: PostgresAdapter,
{ {
collection, collection,
limit: limitArg, limit,
locale, locale,
page = 1, page = 1,
pagination, pagination,
req = {} as PayloadRequest, req = {} as PayloadRequest,
sort: sortArg, sort: sortArg,
where: whereArg, where,
}, },
) { ) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const tableName = getTableName({
adapter: this,
config: collectionConfig,
})
return findMany({ return findMany({
adapter: this, adapter: this,
fields: collectionConfig.fields, fields: collectionConfig.fields,
limit: limitArg, limit,
locale, locale,
page, page,
pagination, pagination,
req, req,
sort, sort,
tableName: toSnakeCase(collection), tableName,
where: whereArg, where,
}) })
} }

View File

@@ -2,11 +2,12 @@
import type { Field } from 'payload/types' import type { Field } from 'payload/types'
import { fieldAffectsData, tabHasName } from 'payload/types' import { fieldAffectsData, tabHasName } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from '../types.js' import type { PostgresAdapter } from '../types.js'
import type { Result } from './buildFindManyArgs.js' import type { Result } from './buildFindManyArgs.js'
import { getTableName } from '../schema/getTableName.js'
type TraverseFieldArgs = { type TraverseFieldArgs = {
_locales: Record<string, unknown> _locales: Record<string, unknown>
adapter: PostgresAdapter adapter: PostgresAdapter
@@ -78,9 +79,22 @@ export const traverseFields = ({
with: {}, 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 currentArgs.with[`${path}${field.name}`] = withArray
traverseFields({ traverseFields({
@@ -128,9 +142,16 @@ export const traverseFields = ({
with: {}, 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 topLevelArgs.with[blockKey] = withBlock
traverseFields({ traverseFields({

View File

@@ -1,17 +1,19 @@
import type { FindGlobal } from 'payload/database' import type { FindGlobal } from 'payload/database'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const findGlobal: FindGlobal = async function findGlobal( export const findGlobal: FindGlobal = async function findGlobal(
this: PostgresAdapter, this: PostgresAdapter,
{ slug, locale, req, where }, { slug, locale, req, where },
) { ) {
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug) const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = toSnakeCase(slug) const tableName = getTableName({
adapter: this,
config: globalConfig,
})
const { const {
docs: [doc], docs: [doc],

View File

@@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types' import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { buildVersionGlobalFields } from 'payload/versions' import { buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions( export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
this: PostgresAdapter, this: PostgresAdapter,
@@ -27,7 +27,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
) )
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt' 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) const fields = buildVersionGlobalFields(globalConfig)
return findMany({ return findMany({

View File

@@ -1,17 +1,20 @@
import type { FindOneArgs } from 'payload/database' import type { FindOneArgs } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types' import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export async function findOne<T extends TypeWithID>( export async function findOne<T extends TypeWithID>(
this: PostgresAdapter, this: PostgresAdapter,
{ collection, locale, req = {} as PayloadRequest, where: incomingWhere }: FindOneArgs, { collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
): Promise<T> { ): Promise<T> {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = getTableName({
adapter: this,
config: collectionConfig,
})
const { docs } = await findMany({ const { docs } = await findMany({
adapter: this, adapter: this,
@@ -22,8 +25,8 @@ export async function findOne<T extends TypeWithID>(
pagination: false, pagination: false,
req, req,
sort: undefined, sort: undefined,
tableName: toSnakeCase(collection), tableName,
where: incomingWhere, where,
}) })
return docs?.[0] || null return docs?.[0] || null

View File

@@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types' import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { buildVersionCollectionFields } from 'payload/versions' import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const findVersions: FindVersions = async function findVersions( export const findVersions: FindVersions = async function findVersions(
this: PostgresAdapter, this: PostgresAdapter,
@@ -25,7 +25,11 @@ export const findVersions: FindVersions = async function findVersions(
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort 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) const fields = buildVersionCollectionFields(collectionConfig)
return findMany({ return findMany({

View File

@@ -1,6 +1,5 @@
import type { Payload } from 'payload' import type { Payload } from 'payload'
import type { DatabaseAdapterObj } from 'payload/database' import type { DatabaseAdapterObj } from 'payload/database'
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
@@ -39,6 +38,8 @@ import { updateGlobal } from './updateGlobal.js'
import { updateGlobalVersion } from './updateGlobalVersion.js' import { updateGlobalVersion } from './updateGlobalVersion.js'
import { updateVersion } from './updateVersion.js' import { updateVersion } from './updateVersion.js'
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
export { sql } from 'drizzle-orm' export { sql } from 'drizzle-orm'
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> { export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
@@ -50,20 +51,24 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
name: 'postgres', name: 'postgres',
// Postgres-specific // Postgres-specific
blockTableNames: {},
drizzle: undefined, drizzle: undefined,
enums: {}, enums: {},
fieldConstraints: {}, fieldConstraints: {},
idType, idType,
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger, logger: args.logger,
pgSchema: undefined, pgSchema: undefined,
pool: undefined, pool: undefined,
poolOptions: args.pool, poolOptions: args.pool,
push: args.push, push: args.push,
relations: {}, relations: {},
relationshipsSuffix: args.relationshipsSuffix || '_rels',
schema: {}, schema: {},
schemaName: args.schemaName, schemaName: args.schemaName,
sessions: {}, sessions: {},
tables: {}, tables: {},
versionsSuffix: args.versionsSuffix || '_v',
// DatabaseAdapter // DatabaseAdapter
beginTransaction, beginTransaction,

View File

@@ -4,11 +4,11 @@ import type { SanitizedCollectionConfig } from 'payload/types'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core' import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions' import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { buildTable } from './schema/build.js' import { buildTable } from './schema/build.js'
import { getTableName } from './schema/getTableName.js'
export const init: Init = function init(this: PostgresAdapter) { export const init: Init = function init(this: PostgresAdapter) {
if (this.schemaName) { if (this.schemaName) {
@@ -25,7 +25,10 @@ export const init: Init = function init(this: PostgresAdapter) {
} }
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const tableName = toSnakeCase(collection.slug) const tableName = getTableName({
adapter: this,
config: collection,
})
buildTable({ buildTable({
adapter: this, adapter: this,
@@ -37,10 +40,15 @@ export const init: Init = function init(this: PostgresAdapter) {
fields: collection.fields, fields: collection.fields,
tableName, tableName,
timestamps: collection.timestamps, timestamps: collection.timestamps,
versions: false,
}) })
if (collection.versions) { if (collection.versions) {
const versionsTableName = `_${tableName}_v` const versionsTableName = getTableName({
adapter: this,
config: collection,
versions: true,
})
const versionFields = buildVersionCollectionFields(collection) const versionFields = buildVersionCollectionFields(collection)
buildTable({ buildTable({
@@ -53,12 +61,13 @@ export const init: Init = function init(this: PostgresAdapter) {
fields: versionFields, fields: versionFields,
tableName: versionsTableName, tableName: versionsTableName,
timestamps: true, timestamps: true,
versions: true,
}) })
} }
}) })
this.payload.config.globals.forEach((global) => { this.payload.config.globals.forEach((global) => {
const tableName = toSnakeCase(global.slug) const tableName = getTableName({ adapter: this, config: global })
buildTable({ buildTable({
adapter: this, adapter: this,
@@ -70,10 +79,11 @@ export const init: Init = function init(this: PostgresAdapter) {
fields: global.fields, fields: global.fields,
tableName, tableName,
timestamps: false, timestamps: false,
versions: false,
}) })
if (global.versions) { if (global.versions) {
const versionsTableName = `_${tableName}_v` const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
const versionFields = buildVersionGlobalFields(global) const versionFields = buildVersionGlobalFields(global)
buildTable({ buildTable({
@@ -86,6 +96,7 @@ export const init: Init = function init(this: PostgresAdapter) {
fields: versionFields, fields: versionFields,
tableName: versionsTableName, tableName: versionsTableName,
timestamps: true, timestamps: true,
versions: true,
}) })
} }
}) })

View File

@@ -92,7 +92,7 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
payload.logger.info({ msg: `Migrating: ${migration.name}` }) payload.logger.info({ msg: `Migrating: ${migration.name}` })
const pgAdapter = payload.db as PostgresAdapter const pgAdapter = payload.db
const drizzleJSON = generateDrizzleJson(pgAdapter.schema) const drizzleJSON = generateDrizzleJson(pgAdapter.schema)
try { try {

View File

@@ -14,6 +14,8 @@ import { v4 as uuid } from 'uuid'
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types.js' import type { GenericColumn, GenericTable, PostgresAdapter } from '../types.js'
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js' import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js'
import { getTableName } from '../schema/getTableName.js'
type Constraint = { type Constraint = {
columnName: string columnName: string
table: GenericTable | PgTableWithColumns<any> table: GenericTable | PgTableWithColumns<any>
@@ -183,7 +185,13 @@ export const getTableColumnFromPath = ({
case 'group': { case 'group': {
if (locale && field.localized && adapter.payload.config.localization) { 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( joins[tableName] = eq(
adapter.tables[tableName].id, adapter.tables[tableName].id,
@@ -218,7 +226,12 @@ export const getTableColumnFromPath = ({
} }
case 'array': { case 'array': {
newTableName = `${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}` newTableName = getTableName({
adapter,
config: field,
parentTableName: `${tableName}_${tableNameSuffix}`,
prefix: `${tableName}_${tableNameSuffix}`,
})
constraintPath = `${constraintPath}${field.name}.%.` constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) { if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and( joins[newTableName] = and(
@@ -265,7 +278,12 @@ export const getTableColumnFromPath = ({
const blockTypes = Array.isArray(value) ? value : [value] const blockTypes = Array.isArray(value) ? value : [value]
blockTypes.forEach((blockType) => { blockTypes.forEach((blockType) => {
const block = field.blocks.find((block) => block.slug === 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( joins[newTableName] = eq(
adapter.tables[tableName].id, adapter.tables[tableName].id,
adapter.tables[newTableName]._parentID, adapter.tables[newTableName]._parentID,
@@ -285,7 +303,12 @@ export const getTableColumnFromPath = ({
} }
const hasBlockField = field.blocks.some((block) => { 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}.%.` constraintPath = `${constraintPath}${field.name}.%.`
let result let result
const blockConstraints = [] const blockConstraints = []
@@ -351,7 +374,7 @@ export const getTableColumnFromPath = ({
case 'relationship': case 'relationship':
case 'upload': { case 'upload': {
let relationshipFields let relationshipFields
const relationTableName = `${rootTableName}_rels` const relationTableName = `${rootTableName}${adapter.relationshipsSuffix}`
const newCollectionPath = pathSegments.slice(1).join('.') const newCollectionPath = pathSegments.slice(1).join('.')
const aliasRelationshipTableName = uuid() const aliasRelationshipTableName = uuid()
const aliasRelationshipTable = alias( const aliasRelationshipTable = alias(
@@ -392,9 +415,13 @@ export const getTableColumnFromPath = ({
let newAliasTable let newAliasTable
if (typeof field.relationTo === 'string') { 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 // parent to relationship join table
relationshipFields = adapter.payload.collections[field.relationTo].config.fields relationshipFields = relationshipConfig.fields
newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid())) newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid()))
@@ -413,7 +440,11 @@ export const getTableColumnFromPath = ({
} }
} else if (newCollectionPath === 'value') { } else if (newCollectionPath === 'value') {
const tableColumnsNames = field.relationTo.map( const tableColumnsNames = field.relationTo.map(
(relationTo) => `"${aliasRelationshipTableName}"."${toSnakeCase(relationTo)}_id"`, (relationTo) =>
`"${aliasRelationshipTableName}"."${getTableName({
adapter,
config: adapter.payload.collections[relationTo].config,
})}_id"`,
) )
return { return {
constraints, constraints,
@@ -460,7 +491,7 @@ export const getTableColumnFromPath = ({
if (field.localized && adapter.payload.config.localization) { if (field.localized && adapter.payload.config.localization) {
// If localized, we go to localized table and set aliasTable to undefined // If localized, we go to localized table and set aliasTable to undefined
// so it is not picked up below to be used as targetTable // 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] const parentTable = aliasTable || adapter.tables[tableName]

View File

@@ -2,9 +2,9 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { type QueryDrafts, combineQueries } from 'payload/database' import { type QueryDrafts, combineQueries } from 'payload/database'
import { buildVersionCollectionFields } from 'payload/versions' import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import { findMany } from './find/findMany.js' import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const queryDrafts: QueryDrafts = async function queryDrafts({ export const queryDrafts: QueryDrafts = async function queryDrafts({
collection, collection,
@@ -17,7 +17,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts({
where, where,
}) { }) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config 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 fields = buildVersionCollectionFields(collectionConfig)
const combinedWhere = combineQueries({ latest: { equals: true } }, where) const combinedWhere = combineQueries({ latest: { equals: true } }, where)

View File

@@ -11,10 +11,10 @@ import type { Field } from 'payload/types'
import { relations } from 'drizzle-orm' import { relations } from 'drizzle-orm'
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core' import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
import { fieldAffectsData } from 'payload/types' import { fieldAffectsData } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js' import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js'
import { getTableName } from './getTableName.js'
import { parentIDColumnMap } from './parentIDColumnMap.js' import { parentIDColumnMap } from './parentIDColumnMap.js'
import { setColumnID } from './setColumnID.js' import { setColumnID } from './setColumnID.js'
import { traverseFields } from './traverseFields.js' import { traverseFields } from './traverseFields.js'
@@ -35,6 +35,7 @@ type Args = {
rootTableName?: string rootTableName?: string
tableName: string tableName: string
timestamps?: boolean timestamps?: boolean
versions: boolean
} }
type Result = { type Result = {
@@ -59,6 +60,7 @@ export const buildTable = ({
rootTableName: incomingRootTableName, rootTableName: incomingRootTableName,
tableName, tableName,
timestamps, timestamps,
versions,
}: Args): Result => { }: Args): Result => {
const rootTableName = incomingRootTableName || tableName const rootTableName = incomingRootTableName || tableName
const columns: Record<string, PgColumnBuilder> = baseColumns const columns: Record<string, PgColumnBuilder> = baseColumns
@@ -113,6 +115,7 @@ export const buildTable = ({
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild, rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
rootTableIDColType: rootTableIDColType || idColType, rootTableIDColType: rootTableIDColType || idColType,
rootTableName, rootTableName,
versions,
})) }))
if (timestamps) { if (timestamps) {
@@ -147,7 +150,7 @@ export const buildTable = ({
adapter.tables[tableName] = table adapter.tables[tableName] = table
if (hasLocalizedField) { if (hasLocalizedField) {
const localeTableName = `${tableName}_locales` const localeTableName = `${tableName}${adapter.localesSuffix}`
localesColumns.id = serial('id').primaryKey() localesColumns.id = serial('id').primaryKey()
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull() localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id') localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
@@ -288,11 +291,16 @@ export const buildTable = ({
} }
relationships.forEach((relationTo) => { 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' let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
const relatedCollectionCustomID = adapter.payload.collections[ const relatedCollectionCustomID = relationshipConfig.fields.find(
relationTo (field) => fieldAffectsData(field) && field.name === 'id',
].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id') )
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric' if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar' if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
@@ -301,7 +309,7 @@ export const buildTable = ({
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' }) ).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
}) })
const relationshipsTableName = `${tableName}_rels` const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
relationshipsTable = adapter.pgSchema.table( relationshipsTable = adapter.pgSchema.table(
relationshipsTableName, relationshipsTableName,
@@ -333,7 +341,11 @@ export const buildTable = ({
} }
relationships.forEach((relationTo) => { relationships.forEach((relationTo) => {
const relatedTableName = toSnakeCase(relationTo) const relatedTableName = getTableName({
adapter,
config: adapter.payload.collections[relationTo].config,
throwValidationError: true,
})
const idColumnName = `${relationTo}ID` const idColumnName = `${relationTo}ID`
result[idColumnName] = one(adapter.tables[relatedTableName], { result[idColumnName] = one(adapter.tables[relatedTableName], {
fields: [relationshipsTable[idColumnName]], fields: [relationshipsTable[idColumnName]],

View File

@@ -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
}

View File

@@ -27,6 +27,7 @@ import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
import { hasLocalesTable } from '../utilities/hasLocalesTable.js' import { hasLocalesTable } from '../utilities/hasLocalesTable.js'
import { buildTable } from './build.js' import { buildTable } from './build.js'
import { createIndex } from './createIndex.js' import { createIndex } from './createIndex.js'
import { getTableName } from './getTableName.js'
import { idToUUID } from './idToUUID.js' import { idToUUID } from './idToUUID.js'
import { parentIDColumnMap } from './parentIDColumnMap.js' import { parentIDColumnMap } from './parentIDColumnMap.js'
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js' import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js'
@@ -53,6 +54,7 @@ type Args = {
rootRelationsToBuild?: Map<string, string> rootRelationsToBuild?: Map<string, string>
rootTableIDColType: string rootTableIDColType: string
rootTableName: string rootTableName: string
versions: boolean
} }
type Result = { type Result = {
@@ -86,7 +88,9 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
versions,
}: Args): Result => { }: Args): Result => {
const throwValidationError = true
let hasLocalizedField = false let hasLocalizedField = false
let hasLocalizedRelationshipField = false let hasLocalizedRelationshipField = false
let hasManyTextField: 'index' | boolean = false let hasManyTextField: 'index' | boolean = false
@@ -217,7 +221,15 @@ export const traverseFields = ({
case 'radio': case 'radio':
case 'select': { 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( adapter.enums[enumName] = pgEnum(
enumName, enumName,
@@ -231,7 +243,14 @@ export const traverseFields = ({
) )
if (field.type === 'select' && field.hasMany) { 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<string, PgColumnBuilder> = { const baseColumns: Record<string, PgColumnBuilder> = {
order: integer('order').notNull(), order: integer('order').notNull(),
parent: parentIDColumnMap[parentIDColType]('parent_id') parent: parentIDColumnMap[parentIDColType]('parent_id')
@@ -266,6 +285,7 @@ export const traverseFields = ({
disableUnique, disableUnique,
fields: [], fields: [],
tableName: selectTableName, tableName: selectTableName,
versions,
}) })
relationsToBuild.set(fieldName, selectTableName) relationsToBuild.set(fieldName, selectTableName)
@@ -296,7 +316,13 @@ export const traverseFields = ({
case 'array': { case 'array': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull 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<string, PgColumnBuilder> = { const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(), _order: integer('_order').notNull(),
_parentID: parentIDColumnMap[parentIDColType]('_parent_id') _parentID: parentIDColumnMap[parentIDColType]('_parent_id')
@@ -334,6 +360,7 @@ export const traverseFields = ({
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
tableName: arrayTableName, tableName: arrayTableName,
versions,
}) })
if (subHasManyTextField) { if (subHasManyTextField) {
@@ -356,7 +383,7 @@ export const traverseFields = ({
} }
if (hasLocalesTable(field.fields)) { if (hasLocalesTable(field.fields)) {
result._locales = many(adapter.tables[`${arrayTableName}_locales`]) result._locales = many(adapter.tables[`${arrayTableName}${adapter.localesSuffix}`])
} }
subRelationsToBuild.forEach((val, key) => { subRelationsToBuild.forEach((val, key) => {
@@ -375,7 +402,13 @@ export const traverseFields = ({
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
field.blocks.forEach((block) => { 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]) { if (!adapter.tables[blockTableName]) {
const baseColumns: Record<string, PgColumnBuilder> = { const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(), _order: integer('_order').notNull(),
@@ -416,6 +449,7 @@ export const traverseFields = ({
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
tableName: blockTableName, tableName: blockTableName,
versions,
}) })
if (subHasManyTextField) { if (subHasManyTextField) {
@@ -439,7 +473,9 @@ export const traverseFields = ({
} }
if (hasLocalesTable(block.fields)) { if (hasLocalesTable(block.fields)) {
result._locales = many(adapter.tables[`${blockTableName}_locales`]) result._locales = many(
adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
)
} }
subRelationsToBuild.forEach((val, key) => { subRelationsToBuild.forEach((val, key) => {
@@ -451,7 +487,7 @@ export const traverseFields = ({
) )
adapter.relations[`relations_${blockTableName}`] = blockTableRelations adapter.relations[`relations_${blockTableName}`] = blockTableRelations
} else if (process.env.NODE_ENV !== 'production') { } else if (process.env.NODE_ENV !== 'production' && !versions) {
validateExistingBlockIsIdentical({ validateExistingBlockIsIdentical({
block, block,
localized: field.localized, localized: field.localized,
@@ -459,7 +495,7 @@ export const traverseFields = ({
table: adapter.tables[blockTableName], table: adapter.tables[blockTableName],
}) })
} }
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName) rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
}) })
@@ -498,6 +534,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
versions,
}) })
if (groupHasLocalizedField) hasLocalizedField = true if (groupHasLocalizedField) hasLocalizedField = true
@@ -540,6 +577,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
versions,
}) })
if (groupHasLocalizedField) hasLocalizedField = true if (groupHasLocalizedField) hasLocalizedField = true
@@ -583,6 +621,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
versions,
}) })
if (tabHasLocalizedField) hasLocalizedField = true if (tabHasLocalizedField) hasLocalizedField = true
@@ -626,6 +665,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
versions,
}) })
if (rowHasLocalizedField) hasLocalizedField = true if (rowHasLocalizedField) hasLocalizedField = true

View File

@@ -24,11 +24,14 @@ export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
export type Args = { export type Args = {
idType?: 'serial' | 'uuid' idType?: 'serial' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger'] logger?: DrizzleConfig['logger']
migrationDir?: string migrationDir?: string
pool: PoolConfig pool: PoolConfig
push?: boolean push?: boolean
relationshipsSuffix?: string
schemaName?: string schemaName?: string
versionsSuffix?: string
} }
export type GenericColumn = PgColumn< export type GenericColumn = PgColumn<
@@ -58,6 +61,10 @@ export type DrizzleTransaction = PgTransaction<
> >
export type PostgresAdapter = BaseDatabaseAdapter & { export type PostgresAdapter = BaseDatabaseAdapter & {
/**
* Used internally to map the block name to the table name
*/
blockTableNames: Record<string, string>
drizzle: DrizzleDB drizzle: DrizzleDB
enums: Record<string, GenericEnum> enums: Record<string, GenericEnum>
/** /**
@@ -66,12 +73,14 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
*/ */
fieldConstraints: Record<string, Record<string, string>> fieldConstraints: Record<string, Record<string, string>>
idType: Args['idType'] idType: Args['idType']
localesSuffix?: string
logger: DrizzleConfig['logger'] logger: DrizzleConfig['logger']
pgSchema?: { table: PgTableFn } | PgSchema pgSchema?: { table: PgTableFn } | PgSchema
pool: Pool pool: Pool
poolOptions: Args['pool'] poolOptions: Args['pool']
push: boolean push: boolean
relations: Record<string, GenericRelation> relations: Record<string, GenericRelation>
relationshipsSuffix?: string
schema: Record<string, GenericEnum | GenericRelation | GenericTable> schema: Record<string, GenericEnum | GenericRelation | GenericTable>
schemaName?: Args['schemaName'] schemaName?: Args['schemaName']
sessions: { sessions: {
@@ -82,6 +91,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
} }
} }
tables: Record<string, GenericTable | PgTableWithColumns<any>> tables: Record<string, GenericTable | PgTableWithColumns<any>>
versionsSuffix?: string
} }
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar' export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
@@ -98,9 +108,11 @@ declare module 'payload' {
drizzle: DrizzleDB drizzle: DrizzleDB
enums: Record<string, GenericEnum> enums: Record<string, GenericEnum>
fieldConstraints: Record<string, Record<string, string>> fieldConstraints: Record<string, Record<string, string>>
localeSuffix?: string
pool: Pool pool: Pool
push: boolean push: boolean
relations: Record<string, GenericRelation> relations: Record<string, GenericRelation>
relationshipsSuffix?: string
schema: Record<string, GenericEnum | GenericRelation | GenericTable> schema: Record<string, GenericEnum | GenericRelation | GenericTable>
sessions: { sessions: {
[id: string]: { [id: string]: {
@@ -110,5 +122,6 @@ declare module 'payload' {
} }
} }
tables: Record<string, GenericTable> tables: Record<string, GenericTable>
versionsSuffix?: string
} }
} }

View File

@@ -1,11 +1,10 @@
import type { UpdateOne } from 'payload/database' import type { UpdateOne } from 'payload/database'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import buildQuery from './queries/buildQuery.js' import buildQuery from './queries/buildQuery.js'
import { selectDistinct } from './queries/selectDistinct.js' import { selectDistinct } from './queries/selectDistinct.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export const updateOne: UpdateOne = async function updateOne( 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 db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config const collection = this.payload.collections[collectionSlug].config
const tableName = toSnakeCase(collectionSlug) const tableName = getTableName({
adapter: this,
config: collection,
})
const whereToUse = whereArg || { id: { equals: id } } const whereToUse = whereArg || { id: { equals: id } }
let idToUpdate = id let idToUpdate = id
@@ -49,7 +51,7 @@ export const updateOne: UpdateOne = async function updateOne(
fields: collection.fields, fields: collection.fields,
operation: 'update', operation: 'update',
req, req,
tableName: toSnakeCase(collectionSlug), tableName,
}) })
return result return result

View File

@@ -1,10 +1,9 @@
import type { UpdateGlobalArgs } from 'payload/database' import type { UpdateGlobalArgs } from 'payload/database'
import type { PayloadRequest, TypeWithID } from 'payload/types' import type { PayloadRequest, TypeWithID } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export async function updateGlobal<T extends TypeWithID>( export async function updateGlobal<T extends TypeWithID>(
@@ -13,7 +12,10 @@ export async function updateGlobal<T extends TypeWithID>(
): Promise<T> { ): Promise<T> {
const db = this.sessions[req.transactionID]?.db || this.drizzle const db = this.sessions[req.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug) 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({}) const existingGlobal = await db.query[tableName].findFirst({})

View File

@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database'
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types' import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
import { buildVersionGlobalFields } from 'payload/versions' import { buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import buildQuery from './queries/buildQuery.js' import buildQuery from './queries/buildQuery.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export async function updateGlobalVersion<T extends TypeWithID>( export async function updateGlobalVersion<T extends TypeWithID>(
@@ -25,7 +25,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
({ slug }) => slug === global, ({ slug }) => slug === global,
) )
const whereToUse = whereArg || { id: { equals: id } } 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 fields = buildVersionGlobalFields(globalConfig)
const { where } = await buildQuery({ const { where } = await buildQuery({

View File

@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types' import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
import { buildVersionCollectionFields } from 'payload/versions' import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import buildQuery from './queries/buildQuery.js' import buildQuery from './queries/buildQuery.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js' import { upsertRow } from './upsertRow/index.js'
export async function updateVersion<T extends TypeWithID>( export async function updateVersion<T extends TypeWithID>(
@@ -23,7 +23,11 @@ export async function updateVersion<T extends TypeWithID>(
const db = this.sessions[req.transactionID]?.db || this.drizzle const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const whereToUse = whereArg || { id: { equals: id } } 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 fields = buildVersionCollectionFields(collectionConfig)
const { where } = await buildQuery({ const { where } = await buildQuery({

View File

@@ -108,6 +108,7 @@ const collectionSchema = joi.object().keys({
joi.boolean(), joi.boolean(),
), ),
custom: joi.object().pattern(joi.string(), joi.any()), custom: joi.object().pattern(joi.string(), joi.any()),
dbName: joi.alternatives().try(joi.string(), joi.func()),
defaultSort: joi.string(), defaultSort: joi.string(),
disableDuplicate: joi.bool(), disableDuplicate: joi.bool(),
endpoints: endpointsSchema, endpoints: endpointsSchema,

View File

@@ -411,3 +411,10 @@ export type DatabaseAdapterResult<T = BaseDatabaseAdapter> = {
defaultIDType: 'number' | 'text' defaultIDType: 'number' | 'text'
init: (args: { payload: Payload }) => T init: (args: { payload: Payload }) => T
} }
export type DBIdentifierName =
| ((Args: {
/** The name of the parent table when using relational DBs */
tableName?: string
}) => string)
| string

View File

@@ -12,6 +12,7 @@ export type {
CreateMigration, CreateMigration,
CreateVersion, CreateVersion,
CreateVersionArgs, CreateVersionArgs,
DBIdentifierName,
DatabaseAdapterResult as DatabaseAdapterObj, DatabaseAdapterResult as DatabaseAdapterObj,
DeleteMany, DeleteMany,
DeleteManyArgs, DeleteManyArgs,

View File

@@ -4,6 +4,5 @@ export { deleteCollectionVersions } from '../versions/deleteCollectionVersions.j
export { enforceMaxVersions } from '../versions/enforceMaxVersions.js' export { enforceMaxVersions } from '../versions/enforceMaxVersions.js'
export { getLatestCollectionVersion } from '../versions/getLatestCollectionVersion.js' export { getLatestCollectionVersion } from '../versions/getLatestCollectionVersion.js'
export { getLatestGlobalVersion } from '../versions/getLatestGlobalVersion.js' export { getLatestGlobalVersion } from '../versions/getLatestGlobalVersion.js'
export { getVersionsModelName } from '../versions/getVersionsModelName.js'
export { saveVersion } from '../versions/saveVersion.js' export { saveVersion } from '../versions/saveVersion.js'
export type { TypeWithVersion } from '../versions/types.js' export type { TypeWithVersion } from '../versions/types.js'

View File

@@ -201,9 +201,11 @@ export const select = baseField.keys({
isClearable: joi.boolean().default(false), isClearable: joi.boolean().default(false),
isSortable: joi.boolean().default(false), isSortable: joi.boolean().default(false),
}), }),
dbName: joi.alternatives().try(joi.string(), joi.func()),
defaultValue: joi defaultValue: joi
.alternatives() .alternatives()
.try(joi.string().allow(''), joi.array().items(joi.string().allow('')), joi.func()), .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), hasMany: joi.boolean().default(false),
options: joi options: joi
.array() .array()
@@ -233,6 +235,7 @@ export const radio = baseField.keys({
layout: joi.string().valid('vertical', 'horizontal'), layout: joi.string().valid('vertical', 'horizontal'),
}), }),
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()), defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
enumName: joi.alternatives().try(joi.string(), joi.func()),
options: joi options: joi
.array() .array()
.min(1) .min(1)
@@ -310,6 +313,7 @@ export const array = baseField.keys({
.default({}), .default({}),
}) })
.default({}), .default({}),
dbName: joi.alternatives().try(joi.string(), joi.func()),
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()), defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()),
fields: joi.array().items(joi.link('#field')).required(), fields: joi.array().items(joi.link('#field')).required(),
interfaceName: joi.string(), interfaceName: joi.string(),
@@ -410,6 +414,7 @@ export const blocks = baseField.keys({
joi.object({ joi.object({
slug: joi.string().required(), slug: joi.string().required(),
custom: joi.object().pattern(joi.string(), joi.any()), custom: joi.object().pattern(joi.string(), joi.any()),
dbName: joi.alternatives().try(joi.string(), joi.func()),
fields: joi.array().items(joi.link('#field')), fields: joi.array().items(joi.link('#field')),
graphQL: joi.object().keys({ graphQL: joi.object().keys({
singularName: joi.string(), singularName: joi.string(),

View File

@@ -17,6 +17,7 @@ import type {
} from '../../admin/types.js' } from '../../admin/types.js'
import type { User } from '../../auth/index.js' import type { User } from '../../auth/index.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.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 { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js' import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
import type { ClientFieldConfig } from './client.js' import type { ClientFieldConfig } from './client.js'
@@ -68,9 +69,11 @@ export type FieldAccess<T extends TypeWithID = any, P = any, U = any> = (args: {
* The `id` of the current document being read or updated. `id` is undefined during the `create` operation. * The `id` of the current document being read or updated. `id` is undefined during the `create` operation.
*/ */
id?: number | string id?: number | string
/** The `Express` request object containing the currently authenticated `user` */
req: PayloadRequest<U>
/** The `payload` object to interface with the payload API */ /** The `payload` object to interface with the payload API */
req: PayloadRequest<U>
/**
* 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<P> siblingData?: Partial<P>
}) => Promise<boolean> | boolean }) => Promise<boolean> | boolean
@@ -478,6 +481,14 @@ export type SelectField = FieldBase & {
isClearable?: boolean isClearable?: boolean
isSortable?: boolean isSortable?: boolean
} }
/**
* Customize the SQL table name
*/
dbName?: DBIdentifierName
/**
* Customize the DB enum name
*/
enumName?: DBIdentifierName
hasMany?: boolean hasMany?: boolean
options: Option[] options: Option[]
type: 'select' type: 'select'
@@ -584,6 +595,10 @@ export type ArrayField = FieldBase & {
} & Admin['components'] } & Admin['components']
initCollapsed?: boolean initCollapsed?: boolean
} }
/**
* Customize the SQL table name
*/
dbName?: DBIdentifierName
fields: Field[] fields: Field[]
/** Customize generated GraphQL and Typescript schema names. /** Customize generated GraphQL and Typescript schema names.
* By default it is bound to the collection. * By default it is bound to the collection.
@@ -606,6 +621,14 @@ export type RadioField = FieldBase & {
} }
layout?: 'horizontal' | 'vertical' layout?: 'horizontal' | 'vertical'
} }
/**
* Customize the SQL table name
*/
dbName?: DBIdentifierName
/**
* Customize the DB enum name
*/
enumName?: DBIdentifierName
options: Option[] options: Option[]
type: 'radio' type: 'radio'
} }
@@ -613,6 +636,10 @@ export type RadioField = FieldBase & {
export type Block = { export type Block = {
/** Extension point to add your custom data. */ /** Extension point to add your custom data. */
custom?: Record<string, any> custom?: Record<string, any>
/**
* Customize the SQL table name
*/
dbName?: DBIdentifierName
fields: Field[] fields: Field[]
/** @deprecated - please migrate to the interfaceName property instead. */ /** @deprecated - please migrate to the interfaceName property instead. */
graphQL?: { graphQL?: {

View File

@@ -49,6 +49,7 @@ const globalSchema = joi
preview: joi.func(), preview: joi.func(),
}), }),
custom: joi.object().pattern(joi.string(), joi.any()), custom: joi.object().pattern(joi.string(), joi.any()),
dbName: joi.alternatives().try(joi.string(), joi.func()),
endpoints: endpointsSchema, endpoints: endpointsSchema,
fields: joi.array(), fields: joi.array(),
graphQL: joi.alternatives().try( graphQL: joi.alternatives().try(

View File

@@ -16,6 +16,7 @@ import type {
GeneratePreviewURL, GeneratePreviewURL,
LivePreviewConfig, LivePreviewConfig,
} from '../../config/types.js' } from '../../config/types.js'
import type { DBIdentifierName } from '../../database/types.js'
import type { Field } from '../../fields/config/types.js' import type { Field } from '../../fields/config/types.js'
import type { PayloadRequest, RequestContext } from '../../types/index.js' import type { PayloadRequest, RequestContext } from '../../types/index.js'
import type { Where } from '../../types/index.js' import type { Where } from '../../types/index.js'
@@ -141,6 +142,10 @@ export type GlobalConfig = {
admin?: GlobalAdminOptions admin?: GlobalAdminOptions
/** Extension point to add your custom data. */ /** Extension point to add your custom data. */
custom?: Record<string, any> custom?: Record<string, any>
/**
* Customize the SQL table name
*/
dbName?: DBIdentifierName
endpoints?: Omit<Endpoint, 'root'>[] | false endpoints?: Omit<Endpoint, 'root'>[] | false
fields: Field[] fields: Field[]
graphQL?: graphQL?:

View File

@@ -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`

77
pnpm-lock.yaml generated
View File

@@ -374,9 +374,6 @@ importers:
mongoose: mongoose:
specifier: 6.12.3 specifier: 6.12.3
version: 6.12.3 version: 6.12.3
mongoose-aggregate-paginate-v2:
specifier: 1.0.6
version: 1.0.6
mongoose-paginate-v2: mongoose-paginate-v2:
specifier: 1.7.22 specifier: 1.7.22
version: 1.7.22 version: 1.7.22
@@ -9774,7 +9771,7 @@ packages:
dependencies: dependencies:
loader-utils: 2.0.4 loader-utils: 2.0.4
schema-utils: 3.3.0 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 dev: true
/file-type@16.5.4: /file-type@16.5.4:
@@ -12275,7 +12272,7 @@ packages:
dependencies: dependencies:
loader-utils: 2.0.4 loader-utils: 2.0.4
schema-utils: 3.3.0 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 webpack-sources: 1.4.3
dev: true dev: true
@@ -12416,11 +12413,6 @@ packages:
'@mongodb-js/saslprep': 1.1.4 '@mongodb-js/saslprep': 1.1.4
dev: true 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: /mongoose-paginate-v2@1.7.22:
resolution: {integrity: sha512-xW5GugkE21DJiu9e13EOxKt4ejEKQkRP/S1PkkXRjnk2rRZVKBcld1nPV+VJ/YCPfm8hb3sz9OvI7O38RmixkA==} resolution: {integrity: sha512-xW5GugkE21DJiu9e13EOxKt4ejEKQkRP/S1PkkXRjnk2rRZVKBcld1nPV+VJ/YCPfm8hb3sz9OvI7O38RmixkA==}
engines: {node: '>=4.0.0'} 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) webpack: 5.90.3(@swc/core@1.4.2)(esbuild@0.19.12)(webpack-cli@5.1.4)
dev: true 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: /terser@5.28.1:
resolution: {integrity: sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==} resolution: {integrity: sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -16440,46 +16407,6 @@ packages:
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
dev: true 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): /webpack@5.90.3(@swc/core@1.4.2)(esbuild@0.19.12)(webpack-cli@5.1.4):
resolution: {integrity: sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==} resolution: {integrity: sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}

View File

@@ -63,7 +63,98 @@ export default buildConfigWithDefaults({
singular: 'Relation B', 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) => { onInit: async (payload) => {
await payload.create({ await payload.create({
collection: 'users', collection: 'users',

View File

@@ -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('transactions', () => {
describe('local api', () => { describe('local api', () => {
it('should commit multiple operations in isolation', async () => { it('should commit multiple operations in isolation', async () => {