Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(db-postgres): add point field support #9078

Merged
merged 20 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,19 @@ jobs:
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1

services:
postgres:
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'postgis/postgis:16-3.4' || '' }}
env:
# must specify password for PG Docker container image, see: https://registry.hub.docker.com/_/postgres?tab=description&page=1&name=10
POSTGRES_USER: ${{ env.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
POSTGRES_DB: ${{ env.POSTGRES_DB }}
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- uses: actions/checkout@v4
with:
Expand All @@ -232,15 +245,6 @@ jobs:
- name: Start LocalStack
run: pnpm docker:start

- name: Start PostgreSQL
uses: CasperWA/[email protected]
with:
postgresql version: '14' # See https://hub.docker.com/_/postgres for available versions
postgresql db: ${{ env.POSTGRES_DB }}
postgresql user: ${{ env.POSTGRES_USER }}
postgresql password: ${{ env.POSTGRES_PASSWORD }}
if: startsWith(matrix.database, 'postgres')

- name: Install Supabase CLI
uses: supabase/setup-cli@v1
with:
Expand All @@ -253,10 +257,6 @@ jobs:
supabase start
if: matrix.database == 'supabase'

- name: Wait for PostgreSQL
run: sleep 30
if: startsWith(matrix.database, 'postgres')

- name: Configure PostgreSQL
run: |
psql "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" -c "CREATE ROLE runner SUPERUSER LOGIN;"
Expand Down
2 changes: 1 addition & 1 deletion docs/database/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ You should prefer a relational DB like Postgres or SQLite if:

## Payload Differences

It's important to note that nearly every Payload feature is available in all of our officially supported Database Adapters, including [Localization](../configuration/localization), [Arrays](../fields/array), [Blocks](../fields/blocks), etc. The only thing that is not supported in Postgres yet is the [Point Field](/docs/fields/point), but that should be added soon.
DanRibbens marked this conversation as resolved.
Show resolved Hide resolved
It's important to note that nearly every Payload feature is available in all of our officially supported Database Adapters, including [Localization](../configuration/localization), [Arrays](../fields/array), [Blocks](../fields/blocks), etc. The only thing that is not supported in SQLite yet is the [Point Field](/docs/fields/point), but that should be added soon.

It's up to you to choose which database you would like to use based on the requirements of your project. Payload has no opinion on which database you should ultimately choose.
2 changes: 1 addition & 1 deletion docs/fields/point.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const MyPointField: Field = {

<Banner type="warning">
<strong>Important:</strong>
The Point Field is currently only supported in MongoDB.
The Point Field currently is not supported in SQLite.
</Banner>

## Config
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"drizzle-kit": "0.28.0",
"drizzle-orm": "0.36.1",
"escape-html": "^1.0.3",
"execa": "5.1.1",
"form-data": "3.0.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/db-mongodb/src/models/buildSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
type: {
type: String,
enum: ['Point'],
...(typeof field.defaultValue !== 'undefined' && {
default: 'Point',
}),
},
coordinates: {
type: [Number],
Expand Down
4 changes: 2 additions & 2 deletions packages/db-postgres/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"@payloadcms/drizzle": "workspace:*",
"@types/pg": "8.10.2",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"drizzle-kit": "0.28.0",
"drizzle-orm": "0.36.1",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/db-postgres/src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export const connect: Connect = async function connect(
process.exit(1)
}

await this.createExtensions()

// Only push schema if not in production
if (
process.env.NODE_ENV !== 'production' &&
Expand Down
9 changes: 9 additions & 0 deletions packages/db-postgres/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
convertPathToJSONTraversal,
countDistinct,
createDatabase,
createExtensions,
createJSONQuery,
createMigration,
defaultDrizzleSnapshot,
Expand Down Expand Up @@ -75,15 +76,22 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
adapterSchema = { enum: pgEnum, table: pgTable }
}

const extensions = (args.extensions ?? []).reduce((acc, name) => {
acc[name] = true
return acc
}, {})

return createDatabaseAdapter<PostgresAdapter>({
name: 'postgres',
afterSchemaInit: args.afterSchemaInit ?? [],
beforeSchemaInit: args.beforeSchemaInit ?? [],
createDatabase,
createExtensions,
defaultDrizzleSnapshot,
disableCreateDatabase: args.disableCreateDatabase ?? false,
drizzle: undefined,
enums: {},
extensions,
features: {
json: true,
},
Expand All @@ -106,6 +114,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
sessions: {},
tableNameMap: new Map<string, string>(),
tables: {},
tablesFilter: args.tablesFilter,
transactionOptions: args.transactionOptions || undefined,
versionsSuffix: args.versionsSuffix || '_v',

Expand Down
5 changes: 5 additions & 0 deletions packages/db-postgres/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type Args = {
* @default false
*/
disableCreateDatabase?: boolean
extensions?: string[]
idType?: 'serial' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger']
Expand All @@ -46,6 +47,7 @@ export type Args = {
* @experimental This only works when there are not other tables or enums of the same name in the database under a different schema. Awaiting fix from Drizzle.
*/
schemaName?: string
tablesFilter?: string[]
transactionOptions?: false | PgTransactionConfig
versionsSuffix?: string
}
Expand All @@ -60,10 +62,12 @@ declare module 'payload' {
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
afterSchemaInit: PostgresSchemaHook[]

beforeSchemaInit: PostgresSchemaHook[]
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
drizzle: PostgresDB
enums: Record<string, GenericEnum>
extensions: Record<string, boolean>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
Expand All @@ -88,6 +92,7 @@ declare module 'payload' {
schema: Record<string, unknown>
schemaName?: Args['schemaName']
tableNameMap: Map<string, string>
tablesFilter?: string[]
versionsSuffix?: string
}
}
4 changes: 2 additions & 2 deletions packages/db-sqlite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"@libsql/client": "0.14.0",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"drizzle-kit": "0.28.0",
"drizzle-orm": "0.36.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/db-vercel-postgres/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"@payloadcms/drizzle": "workspace:*",
"@vercel/postgres": "^0.9.0",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.26.2",
"drizzle-orm": "0.35.1",
"drizzle-kit": "0.28.0",
"drizzle-orm": "0.36.1",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/db-vercel-postgres/src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export const connect: Connect = async function connect(
process.exit(1)
}

await this.createExtensions()

// Only push schema if not in production
if (
process.env.NODE_ENV !== 'production' &&
Expand Down
9 changes: 9 additions & 0 deletions packages/db-vercel-postgres/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
convertPathToJSONTraversal,
countDistinct,
createDatabase,
createExtensions,
createJSONQuery,
createMigration,
defaultDrizzleSnapshot,
Expand Down Expand Up @@ -75,15 +76,22 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
adapterSchema = { enum: pgEnum, table: pgTable }
}

const extensions = (args.extensions ?? []).reduce((acc, name) => {
acc[name] = true
return acc
}, {})

return createDatabaseAdapter<VercelPostgresAdapter>({
name: 'postgres',
afterSchemaInit: args.afterSchemaInit ?? [],
beforeSchemaInit: args.beforeSchemaInit ?? [],
createDatabase,
createExtensions,
defaultDrizzleSnapshot,
disableCreateDatabase: args.disableCreateDatabase ?? false,
drizzle: undefined,
enums: {},
extensions,
features: {
json: true,
},
Expand All @@ -107,6 +115,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
sessions: {},
tableNameMap: new Map<string, string>(),
tables: {},
tablesFilter: args.tablesFilter,
transactionOptions: args.transactionOptions || undefined,
versionsSuffix: args.versionsSuffix || '_v',

Expand Down
5 changes: 5 additions & 0 deletions packages/db-vercel-postgres/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type Args = {
* @default false
*/
disableCreateDatabase?: boolean
extensions?: string[]
idType?: 'serial' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger']
Expand All @@ -51,6 +52,7 @@ export type Args = {
* @experimental This only works when there are not other tables or enums of the same name in the database under a different schema. Awaiting fix from Drizzle.
*/
schemaName?: string
tablesFilter?: string[]
transactionOptions?: false | PgTransactionConfig
versionsSuffix?: string
}
Expand All @@ -69,6 +71,8 @@ declare module 'payload' {
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
drizzle: PostgresDB
enums: Record<string, GenericEnum>
extensions: Record<string, boolean>
extensionsFilter: Set<string>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
Expand All @@ -93,6 +97,7 @@ declare module 'payload' {
schema: Record<string, unknown>
schemaName?: Args['schemaName']
tableNameMap: Map<string, string>
tablesFilter?: string[]
versionsSuffix?: string
}
}
2 changes: 1 addition & 1 deletion packages/drizzle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"dependencies": {
"console-table-printer": "2.12.1",
"drizzle-orm": "0.35.1",
"drizzle-orm": "0.36.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"
Expand Down
1 change: 1 addition & 0 deletions packages/drizzle/src/exports/postgres.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { countDistinct } from '../postgres/countDistinct.js'
export { createDatabase } from '../postgres/createDatabase.js'
export { createExtensions } from '../postgres/createExtensions.js'
export { convertPathToJSONTraversal } from '../postgres/createJSONQuery/convertPathToJSONTraversal.js'
export { createJSONQuery } from '../postgres/createJSONQuery/index.js'
export { createMigration } from '../postgres/createMigration.js'
Expand Down
40 changes: 40 additions & 0 deletions packages/drizzle/src/find/traverseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,46 @@ export const traverseFields = ({
break
}

case 'point': {
if (adapter.name === 'sqlite') {
break
}

const args = field.localized ? _locales : currentArgs
if (!args.columns) {
args.columns = {}
}

if (!args.extras) {
args.extras = {}
}

const name = `${path}${field.name}`

// Drizzle handles that poorly. See https://github.com/drizzle-team/drizzle-orm/issues/2526
// Additionally, this way we format the column value straight in the database using ST_AsGeoJSON
args.columns[name] = false

let shouldSelect = false

if (select || selectAllOnCurrentLevel) {
if (
selectAllOnCurrentLevel ||
(selectMode === 'include' && select[field.name] === true) ||
(selectMode === 'exclude' && typeof select[field.name] === 'undefined')
) {
shouldSelect = true
}
} else {
shouldSelect = true
}

if (shouldSelect) {
args.extras[name] = sql.raw(`ST_AsGeoJSON(${toSnakeCase(name)})::jsonb`).as(name)
}
break
}

case 'join': {
// when `joinsQuery` is false, do not join
if (joinQuery === false) {
Expand Down
4 changes: 4 additions & 0 deletions packages/drizzle/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export const migrate: DrizzleAdapter['migrate'] = async function migrate(
return
}

if ('createExtensions' in this && typeof this.createExtensions === 'function') {
await this.createExtensions()
}

let latestBatch = 0
let migrationsInDB = []

Expand Down
7 changes: 6 additions & 1 deletion packages/drizzle/src/migrateFresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { PayloadRequest } from 'payload'
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
import prompts from 'prompts'

import type { DrizzleAdapter, Migration } from './types.js'
import type { DrizzleAdapter } from './types.js'

import { parseError } from './utilities/parseError.js'

Expand Down Expand Up @@ -48,6 +48,11 @@ export async function migrateFresh(
})

const req = { payload } as PayloadRequest

if ('createExtensions' in this && typeof this.createExtensions === 'function') {
await this.createExtensions()
}

// Run all migrate up
for (const migration of migrationFiles) {
payload.logger.info({ msg: `Migrating: ${migration.name}` })
Expand Down
13 changes: 13 additions & 0 deletions packages/drizzle/src/postgres/createExtensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { BasePostgresAdapter } from './types.js'

export const createExtensions = async function (this: BasePostgresAdapter): Promise<void> {
for (const extension in this.extensions) {
if (this.extensions[extension]) {
try {
await this.drizzle.execute(`CREATE EXTENSION IF NOT EXISTS "${extension}"`)
} catch (err) {
this.payload.logger.error({ err, msg: `Failed to create extension ${extension}` })
}
}
}
}
Loading