Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e84f43f
mongo, int tests
AlessioGr Aug 11, 2025
f61e7d0
postgres support
AlessioGr Aug 13, 2025
d2ca782
Merge remote-tracking branch 'origin/main' into feat/$push
AlessioGr Aug 13, 2025
774ccee
fix tests
AlessioGr Aug 13, 2025
ef28b42
Merge remote-tracking branch 'origin/main' into feat/$push
AlessioGr Aug 21, 2025
2ab2b7b
add tests for localized field within unlocalized array
AlessioGr Aug 21, 2025
d78c047
mongodb localized push support
AlessioGr Aug 21, 2025
52c4adf
improve test coverage
AlessioGr Aug 21, 2025
b3f23e8
postgres localized arrays $push support
AlessioGr Aug 21, 2025
1860f33
import
AlessioGr Aug 21, 2025
b9f6cb7
organize new tests
AlessioGr Aug 21, 2025
a3bbf38
add docs
AlessioGr Aug 21, 2025
bb8e10b
docs
AlessioGr Aug 21, 2025
663c154
Merge remote-tracking branch 'origin/main' into feat/$push
AlessioGr Aug 22, 2025
3a955f6
do not automatically set updatedAt if it's explicitly set to null
AlessioGr Aug 22, 2025
9c98fb3
perf(drizzle): skip updating main row if there is no data to update. …
AlessioGr Aug 22, 2025
d041687
add tests for new updatedAt behavior
AlessioGr Aug 22, 2025
ace523b
Merge remote-tracking branch 'origin/main' into feat/$push
AlessioGr Aug 22, 2025
b1836f9
document performance improvement from setting updatedAt to null
AlessioGr Aug 22, 2025
263029e
fix
AlessioGr Aug 22, 2025
73ffa10
fix mongo test
AlessioGr Aug 23, 2025
24cc9b1
Merge remote-tracking branch 'origin/main' into feat/$push
AlessioGr Aug 26, 2025
be86eac
remove atomic operations docs for now
AlessioGr Aug 26, 2025
e408586
should allow atomic array updates and $inc
AlessioGr Aug 26, 2025
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
19 changes: 16 additions & 3 deletions packages/db-mongodb/src/updateOne.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,24 @@ export const updateOne: UpdateOne = async function updateOne(

let result

const $inc: Record<string, number> = {}
let updateData: UpdateQuery<any> = data
transform({ $inc, adapter: this, data, fields, operation: 'write' })

const $inc: Record<string, number> = {}
const $push: Record<string, { $each: any[] } | any> = {}

transform({ $inc, $push, adapter: this, data, fields, operation: 'write' })

const updateOps: UpdateQuery<any> = {}

if (Object.keys($inc).length) {
updateData = { $inc, $set: updateData }
updateOps.$inc = $inc
}
if (Object.keys($push).length) {
updateOps.$push = $push
}
if (Object.keys(updateOps).length) {
updateOps.$set = updateData
updateData = updateOps
}

try {
Expand Down
53 changes: 51 additions & 2 deletions packages/db-mongodb/src/utilities/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ const sanitizeDate = ({

type Args = {
$inc?: Record<string, number>
$push?: Record<string, { $each: any[] } | any>
/** instance of the adapter */
adapter: MongooseAdapter
/** data to transform, can be an array of documents or a single document */
Expand Down Expand Up @@ -398,6 +399,7 @@ const stripFields = ({

export const transform = ({
$inc,
$push,
adapter,
data,
fields,
Expand All @@ -412,7 +414,16 @@ export const transform = ({

if (Array.isArray(data)) {
for (const item of data) {
transform({ $inc, adapter, data: item, fields, globalSlug, operation, validateRelationships })
transform({
$inc,
$push,
adapter,
data: item,
fields,
globalSlug,
operation,
validateRelationships,
})
}
return
}
Expand Down Expand Up @@ -470,6 +481,39 @@ export const transform = ({
}
}

if (
$push &&
field.type === 'array' &&
operation === 'write' &&
field.name in ref &&
ref[field.name]
) {
const value = ref[field.name]
if (value && typeof value === 'object' && '$push' in value) {
const push = value.$push

if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
if (typeof push === 'object' && push !== null) {
Object.entries(push).forEach(([localeKey, localeData]) => {
if (Array.isArray(localeData)) {
$push[`${parentPath}${field.name}.${localeKey}`] = { $each: localeData }
} else if (typeof localeData === 'object') {
$push[`${parentPath}${field.name}.${localeKey}`] = localeData
}
})
}
} else {
if (Array.isArray(push)) {
$push[`${parentPath}${field.name}`] = { $each: push }
} else if (typeof push === 'object') {
$push[`${parentPath}${field.name}`] = push
}
}

delete ref[field.name]
}
}

if (field.type === 'date' && operation === 'read' && field.name in ref && ref[field.name]) {
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
const fieldRef = ref[field.name] as Record<string, unknown>
Expand Down Expand Up @@ -550,8 +594,13 @@ export const transform = ({
})

if (operation === 'write') {
if (!data.updatedAt) {
if (typeof data.updatedAt === 'undefined') {
// If data.updatedAt is explicitly set to `null` we should not set it - this means we don't want to change the value of updatedAt.
data.updatedAt = new Date().toISOString()
} else if (data.updatedAt === null) {
// `updatedAt` may be explicitly set to null to disable updating it - if that is the case, we need to delete the property. Keeping it as null will
// cause the database to think we want to set it to null, which we don't.
delete data.updatedAt
}
}
}
2 changes: 2 additions & 0 deletions packages/drizzle/src/transform/write/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const transformArray = ({
data.forEach((arrayRow, i) => {
const newRow: ArrayRowToInsert = {
arrays: {},
arraysToPush: {},
locales: {},
row: {
_order: i + 1,
Expand Down Expand Up @@ -104,6 +105,7 @@ export const transformArray = ({
traverseFields({
adapter,
arrays: newRow.arrays,
arraysToPush: newRow.arraysToPush,
baseTableName,
blocks,
blocksToDelete,
Expand Down
2 changes: 2 additions & 0 deletions packages/drizzle/src/transform/write/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const transformBlocks = ({

const newRow: BlockRowToInsert = {
arrays: {},
arraysToPush: {},
locales: {},
row: {
_order: i + 1,
Expand Down Expand Up @@ -116,6 +117,7 @@ export const transformBlocks = ({
traverseFields({
adapter,
arrays: newRow.arrays,
arraysToPush: newRow.arraysToPush,
baseTableName,
blocks,
blocksToDelete,
Expand Down
2 changes: 2 additions & 0 deletions packages/drizzle/src/transform/write/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const transformForWrite = ({
// Split out the incoming data into rows to insert / delete
const rowToInsert: RowToInsert = {
arrays: {},
arraysToPush: {},
blocks: {},
blocksToDelete: new Set(),
locales: {},
Expand All @@ -45,6 +46,7 @@ export const transformForWrite = ({
traverseFields({
adapter,
arrays: rowToInsert.arrays,
arraysToPush: rowToInsert.arraysToPush,
baseTableName: tableName,
blocks: rowToInsert.blocks,
blocksToDelete: rowToInsert.blocksToDelete,
Expand Down
87 changes: 63 additions & 24 deletions packages/drizzle/src/transform/write/traverseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
import toSnakeCase from 'to-snake-case'

import type { DrizzleAdapter } from '../../types.js'
import type {
ArrayRowToInsert,
BlockRowToInsert,
NumberToDelete,
RelationshipToDelete,
TextToDelete,
} from './types.js'
import type { NumberToDelete, RelationshipToDelete, RowToInsert, TextToDelete } from './types.js'

import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
Expand All @@ -23,16 +17,20 @@ import { transformTexts } from './texts.js'

type Args = {
adapter: DrizzleAdapter
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
/**
* This will delete the array table and then re-insert all the new array rows.
*/
arrays: RowToInsert['arrays']
/**
* Array rows to push to the existing array. This will simply create
* a new row in the array table.
*/
arraysToPush: RowToInsert['arraysToPush']
/**
* This is the name of the base table
*/
baseTableName: string
blocks: {
[blockType: string]: BlockRowToInsert[]
}
blocks: RowToInsert['blocks']
blocksToDelete: Set<string>
/**
* A snake-case field prefix, representing prior fields
Expand Down Expand Up @@ -82,6 +80,7 @@ type Args = {
export const traverseFields = ({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
Expand Down Expand Up @@ -129,13 +128,24 @@ export const traverseFields = ({
if (field.type === 'array') {
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)

if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}

if (isLocalized) {
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
let value: {
[locale: string]: unknown[]
} = data[field.name] as any

let push = false
if (typeof value === 'object' && '$push' in value) {
value = value.$push as any
push = true
}

if (typeof value === 'object' && value !== null) {
Object.entries(value).forEach(([localeKey, _localeData]) => {
let localeData = _localeData
if (push && !Array.isArray(localeData)) {
localeData = [localeData]
}

if (Array.isArray(localeData)) {
const newRows = transformArray({
adapter,
Expand All @@ -158,18 +168,35 @@ export const traverseFields = ({
withinArrayOrBlockLocale: localeKey,
})

arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = []
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows)
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
}
}
})
}
} else {
let value = data[field.name]
let push = false
if (typeof value === 'object' && '$push' in value) {
value = Array.isArray(value.$push) ? value.$push : [value.$push]
push = true
}

const newRows = transformArray({
adapter,
arrayTableName,
baseTableName,
blocks,
blocksToDelete,
data: data[field.name],
data: value,
field,
numbers,
numbersToDelete,
Expand All @@ -183,7 +210,17 @@ export const traverseFields = ({
withinArrayOrBlockLocale,
})

arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = []
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows)
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
}
}

return
Expand Down Expand Up @@ -264,6 +301,7 @@ export const traverseFields = ({
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
Expand Down Expand Up @@ -298,6 +336,7 @@ export const traverseFields = ({
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
Expand Down Expand Up @@ -547,8 +586,8 @@ export const traverseFields = ({
let formattedValue = value

if (field.type === 'date') {
if (fieldName === 'updatedAt' && !formattedValue) {
// let the db handle this
if (fieldName === 'updatedAt' && typeof formattedValue === 'undefined') {
// let the db handle this. If formattedValue is explicitly set to `null` we should not set it - this means we don't want to change the value of updatedAt.
formattedValue = new Date().toISOString()
} else {
if (typeof value === 'number' && !Number.isNaN(value)) {
Expand Down
9 changes: 9 additions & 0 deletions packages/drizzle/src/transform/write/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ export type ArrayRowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
arraysToPush: {
[tableName: string]: ArrayRowToInsert[]
}
locales: {
[locale: string]: Record<string, unknown>
}
Expand All @@ -12,6 +15,9 @@ export type BlockRowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
arraysToPush: {
[tableName: string]: ArrayRowToInsert[]
}
locales: {
[locale: string]: Record<string, unknown>
}
Expand All @@ -37,6 +43,9 @@ export type RowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
arraysToPush: {
[tableName: string]: ArrayRowToInsert[]
}
blocks: {
[tableName: string]: BlockRowToInsert[]
}
Expand Down
Loading
Loading