feat(db-sqlite): add idType: 'uuid' support (#10016)

Adds `idType: 'uuid'` to the SQLite adapter support:
```ts
sqliteAdapter({
  idType: 'uuid',
})
```

Achieved through Drizzle's `$defaultFn()`
https://orm.drizzle.team/docs/latest-releases/drizzle-orm-v0283#-added-defaultfn--default-methods-to-column-builders
as SQLite doesn't have native UUID support. Added `sqlite-uuid` to CI.
This commit is contained in:
Sasha
2024-12-19 05:44:04 +02:00
committed by GitHub
parent 0e5bda9a74
commit 03ff77544e
10 changed files with 42 additions and 16 deletions

View File

@@ -180,6 +180,7 @@ jobs:
- postgres-uuid - postgres-uuid
- supabase - supabase
- sqlite - sqlite
- sqlite-uuid
env: env:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres

View File

@@ -40,6 +40,7 @@ export default buildConfig({
| `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. | | `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
| `idType` | A string of 'number', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) | | `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. | | `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'. | | `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |

View File

@@ -61,8 +61,8 @@ export { sql } from 'drizzle-orm'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> { export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
const postgresIDType = args.idType || 'serial' const sqliteIDType = args.idType || 'number'
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text' const payloadIDType = sqliteIDType === 'uuid' ? 'text' : 'number'
function adapter({ payload }: { payload: Payload }) { function adapter({ payload }: { payload: Payload }) {
const migrationDir = findMigrationDir(args.migrationDir) const migrationDir = findMigrationDir(args.migrationDir)
@@ -93,7 +93,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
json: true, json: true,
}, },
fieldConstraints: {}, fieldConstraints: {},
idType: postgresIDType, idType: sqliteIDType,
initializing, initializing,
localesSuffix: args.localesSuffix || '_locales', localesSuffix: args.localesSuffix || '_locales',
logger: args.logger, logger: args.logger,

View File

@@ -59,9 +59,8 @@ export const buildDrizzleTable: BuildDrizzleTable = ({ adapter, locales, rawTabl
break break
} }
// Not used yet in SQLite but ready here.
case 'uuid': { case 'uuid': {
let builder = text(column.name) let builder = text(column.name, { length: 36 })
if (column.defaultRandom) { if (column.defaultRandom) {
builder = builder.$defaultFn(() => uuidv4()) builder = builder.$defaultFn(() => uuidv4())

View File

@@ -1,6 +1,6 @@
import type { SetColumnID } from '@payloadcms/drizzle/types' import type { SetColumnID } from '@payloadcms/drizzle/types'
export const setColumnID: SetColumnID = ({ columns, fields }) => { export const setColumnID: SetColumnID = ({ adapter, columns, fields }) => {
const idField = fields.find((field) => field.name === 'id') const idField = fields.find((field) => field.name === 'id')
if (idField) { if (idField) {
if (idField.type === 'number') { if (idField.type === 'number') {
@@ -22,6 +22,17 @@ export const setColumnID: SetColumnID = ({ columns, fields }) => {
} }
} }
if (adapter.idType === 'uuid') {
columns.id = {
name: 'id',
type: 'uuid',
defaultRandom: true,
primaryKey: true,
}
return 'uuid'
}
columns.id = { columns.id = {
name: 'id', name: 'id',
type: 'integer', type: 'integer',

View File

@@ -40,7 +40,7 @@ export type Args = {
client: Config client: Config
/** Generated schema from payload generate:db-schema file path */ /** Generated schema from payload generate:db-schema file path */
generateSchemaOutputFile?: string generateSchemaOutputFile?: string
idType?: 'serial' | 'uuid' idType?: 'number' | 'uuid'
localesSuffix?: string localesSuffix?: string
logger?: DrizzleConfig['logger'] logger?: DrizzleConfig['logger']
migrationDir?: string migrationDir?: string
@@ -106,6 +106,7 @@ type SQLiteDrizzleAdapter = Omit<
| 'drizzle' | 'drizzle'
| 'dropDatabase' | 'dropDatabase'
| 'execute' | 'execute'
| 'idType'
| 'insert' | 'insert'
| 'operators' | 'operators'
| 'relations' | 'relations'

View File

@@ -1,7 +1,7 @@
import prompts from 'prompts' import prompts from 'prompts'
import type { BasePostgresAdapter } from '../postgres/types.js' import type { BasePostgresAdapter } from '../postgres/types.js'
import type { DrizzleAdapter } from '../types.js' import type { DrizzleAdapter, PostgresDB } from '../types.js'
/** /**
* Pushes the development schema to the database using Drizzle. * Pushes the development schema to the database using Drizzle.
@@ -60,21 +60,24 @@ export const pushDevSchema = async (adapter: DrizzleAdapter) => {
? `"${adapter.schemaName}"."payload_migrations"` ? `"${adapter.schemaName}"."payload_migrations"`
: '"payload_migrations"' : '"payload_migrations"'
const drizzle = adapter.drizzle as PostgresDB
const result = await adapter.execute({ const result = await adapter.execute({
drizzle: adapter.drizzle, drizzle,
raw: `SELECT * FROM ${migrationsTable} WHERE batch = '-1'`, raw: `SELECT * FROM ${migrationsTable} WHERE batch = '-1'`,
}) })
const devPush = result.rows const devPush = result.rows
if (!devPush.length) { if (!devPush.length) {
await adapter.execute({ // Use drizzle for insert so $defaultFn's are called
drizzle: adapter.drizzle, await drizzle.insert(adapter.tables.payload_migrations).values({
raw: `INSERT INTO ${migrationsTable} (name, batch) VALUES ('dev', '-1')`, name: 'dev',
batch: -1,
}) })
} else { } else {
await adapter.execute({ await adapter.execute({
drizzle: adapter.drizzle, drizzle,
raw: `UPDATE ${migrationsTable} SET updated_at = CURRENT_TIMESTAMP WHERE batch = '-1'`, raw: `UPDATE ${migrationsTable} SET updated_at = CURRENT_TIMESTAMP WHERE batch = '-1'`,
}) })
} }

View File

@@ -24,7 +24,7 @@ describe('Custom GraphQL', () => {
} }
}) })
if (!['sqlite'].includes(process.env.PAYLOAD_DATABASE || '')) { if (!['sqlite', 'sqlite-uuid'].includes(process.env.PAYLOAD_DATABASE || '')) {
describe('Isolated Transaction ID', () => { describe('Isolated Transaction ID', () => {
it('should isolate transaction IDs between queries in the same request', async () => { it('should isolate transaction IDs between queries in the same request', async () => {
const query = `query { const query = `query {

View File

@@ -530,7 +530,7 @@ describe('database', () => {
describe('transactions', () => { describe('transactions', () => {
describe('local api', () => { describe('local api', () => {
// sqlite cannot handle concurrent write transactions // sqlite cannot handle concurrent write transactions
if (!['sqlite'].includes(process.env.PAYLOAD_DATABASE)) { if (!['sqlite', 'sqlite-uuid'].includes(process.env.PAYLOAD_DATABASE)) {
it('should commit multiple operations in isolation', async () => { it('should commit multiple operations in isolation', async () => {
const req = { const req = {
payload, payload,
@@ -1074,7 +1074,8 @@ describe('database', () => {
data: { title: 'invalid', relationship: 'not-real-id' }, data: { title: 'invalid', relationship: 'not-real-id' },
}) })
} catch (error) { } catch (error) {
expect(error).toBeInstanceOf(Error) // instanceof checks don't work with libsql
expect(error).toBeTruthy()
} }
expect(invalidDoc).toBeUndefined() expect(invalidDoc).toBeUndefined()

View File

@@ -53,6 +53,15 @@ export const allDatabaseAdapters = {
url: process.env.SQLITE_URL || 'file:./payloadtests.db', url: process.env.SQLITE_URL || 'file:./payloadtests.db',
}, },
})`, })`,
'sqlite-uuid': `
import { sqliteAdapter } from '@payloadcms/db-sqlite'
export const databaseAdapter = sqliteAdapter({
idType: 'uuid',
client: {
url: process.env.SQLITE_URL || 'file:./payloadtests.db',
},
})`,
supabase: ` supabase: `
import { postgresAdapter } from '@payloadcms/db-postgres' import { postgresAdapter } from '@payloadcms/db-postgres'