diff --git a/src/orm/base_model/index.ts b/src/orm/base_model/index.ts index 1efbce2d..12299a74 100644 --- a/src/orm/base_model/index.ts +++ b/src/orm/base_model/index.ts @@ -8,7 +8,6 @@ */ import { DateTime } from 'luxon' -import equal from 'fast-deep-equal' import Hooks from '@poppinss/hooks' import lodash from '@poppinss/utils/lodash' import { Exception, defineStaticProperty } from '@poppinss/utils' @@ -63,6 +62,8 @@ import { ensureRelation, managedTransaction, normalizeCherryPickObject, + transformDateValue, + compareValues, } from '../../utils/index.js' const MANY_RELATIONS = ['hasMany', 'manyToMany', 'hasManyThrough'] @@ -207,9 +208,13 @@ class BaseModelImpl implements LucidRow { * array */ return rowObjects.map((rowObject: any) => { - const existingRow = existingRows.find((one: any) => { - /* eslint-disable-next-line eqeqeq */ - return keys.every((key) => one[key] == rowObject[key]) + const existingRow = existingRows.find((row: any) => { + return keys.every((key) => { + const objectValue = rowObject[key] + const rowValue = row[key] + + return compareValues(rowValue, objectValue) + }) }) /** @@ -852,6 +857,8 @@ class BaseModelImpl implements LucidRow { payload: any, options?: ModelAssignOptions ): Promise { + const client = this.$adapter.modelConstructorClient(this as LucidModel, options) + uniqueKeys = Array.isArray(uniqueKeys) ? uniqueKeys : [uniqueKeys] const uniquenessPair: { key: string; value: string[] }[] = uniqueKeys.map( (uniqueKey: string) => { @@ -861,7 +868,7 @@ class BaseModelImpl implements LucidRow { throw new Exception( `Value for the "${uniqueKey}" is null or undefined inside "fetchOrNewUpMany" payload` ) - }), + }).map((value) => transformDateValue(value, client.dialect)), } } ) @@ -896,6 +903,8 @@ class BaseModelImpl implements LucidRow { payload: any, options?: ModelAssignOptions ): Promise { + const client = this.$adapter.modelConstructorClient(this as LucidModel, options) + uniqueKeys = Array.isArray(uniqueKeys) ? uniqueKeys : [uniqueKeys] const uniquenessPair: { key: string; value: string[] }[] = uniqueKeys.map( (uniqueKey: string) => { @@ -905,7 +914,7 @@ class BaseModelImpl implements LucidRow { throw new Exception( `Value for the "${uniqueKey}" is null or undefined inside "fetchOrCreateMany" payload` ) - }), + }).map((value) => transformDateValue(value, client.dialect)), } } ) @@ -960,6 +969,8 @@ class BaseModelImpl implements LucidRow { payload: any, options?: ModelAssignOptions ): Promise { + const client = this.$adapter.modelConstructorClient(this as LucidModel, options) + uniqueKeys = Array.isArray(uniqueKeys) ? uniqueKeys : [uniqueKeys] const uniquenessPair: { key: string; value: string[] }[] = uniqueKeys.map( (uniqueKey: string) => { @@ -969,13 +980,11 @@ class BaseModelImpl implements LucidRow { throw new Exception( `Value for the "${uniqueKey}" is null or undefined inside "updateOrCreateMany" payload` ) - }), + }).map((value) => transformDateValue(value, client.dialect)), } } ) - const client = this.$adapter.modelConstructorClient(this as LucidModel, options) - return managedTransaction(client, async (trx) => { /** * Find existing rows @@ -1287,15 +1296,10 @@ class BaseModelImpl implements LucidRow { const originalValue = this.$original[key] let isEqual = true - if (DateTime.isDateTime(value) || DateTime.isDateTime(originalValue)) { - isEqual = - DateTime.isDateTime(value) && DateTime.isDateTime(originalValue) - ? value.equals(originalValue) - : value === originalValue - } else if (isObject(value) && 'isDirty' in value) { + if (isObject(value) && 'isDirty' in value) { isEqual = !value.isDirty } else { - isEqual = equal(originalValue, value) + isEqual = compareValues(originalValue, value) } if (!isEqual) { diff --git a/src/utils/index.ts b/src/utils/index.ts index 2af3ab10..d239aa79 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,9 +12,16 @@ import { join, extname } from 'node:path' import { Exception, fsReadAll, isScriptFile } from '@poppinss/utils' import { RelationshipsContract } from '../types/relations.js' import { LucidRow, ModelObject, CherryPickFields } from '../types/model.js' -import { FileNode, QueryClientContract, TransactionClientContract } from '../types/database.js' +import { + DialectContract, + FileNode, + QueryClientContract, + TransactionClientContract, +} from '../types/database.js' import { fileURLToPath, pathToFileURL } from 'node:url' import * as errors from '../errors.js' +import { DateTime } from 'luxon' +import equal from 'fast-deep-equal' /** * Ensure that relation is defined @@ -53,6 +60,30 @@ export function collectValues(payload: any[], key: string, missingCallback: () = }) } +/** + * Transform value if it is an instance of DateTime, so it can be processed by query builder + */ +export function transformDateValue(value: unknown, dialect: DialectContract) { + if (DateTime.isDateTime(value)) { + return value.toFormat(dialect.dateTimeFormat) + } + + return value +} + +/** + * Compare two values deeply whether they are equal or not + */ +export function compareValues(valueA: unknown, valueB: unknown) { + if (DateTime.isDateTime(valueA) || DateTime.isDateTime(valueB)) { + return DateTime.isDateTime(valueA) && DateTime.isDateTime(valueB) + ? valueA.equals(valueB) + : valueA === valueB + } else { + return equal(valueA, valueB) + } +} + /** * Raises exception when a relationship `booted` property is false. */ diff --git a/test/orm/base_model.spec.ts b/test/orm/base_model.spec.ts index 205c4b1b..3ceaf54c 100644 --- a/test/orm/base_model.spec.ts +++ b/test/orm/base_model.spec.ts @@ -4888,6 +4888,68 @@ test.group('Base Model | fetch', (group) => { assert.lengthOf(usersList, 1) assert.equal(usersList[0].points, 2) }) + + test('updateOrCreateMany should work with DateTime', async ({ fs, assert }) => { + const app = new AppFactory().create(fs.baseUrl, () => {}) + await app.init() + const db = getDb() + const adapter = ormAdapter(db) + + const BaseModel = getBaseModel(adapter) + + class User extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column() + declare username: string + + @column() + declare email: string + + @column.dateTime() + declare createdAt: DateTime + } + + const createdAt1 = DateTime.now().minus({ days: 2 }).startOf('second') + const createdAt2 = DateTime.now().minus({ days: 1 }).startOf('second') + + await User.createMany([ + { + username: 'virk1', + email: 'virk+1@adonisjs.com', + createdAt: createdAt1, + }, + { + username: 'virk2', + email: 'virk+2@adonisjs.com', + createdAt: createdAt2, + }, + ]) + + const users = await User.updateOrCreateMany('createdAt', [ + { + username: 'virk3', + email: 'virk+3@adonisjs.com', + createdAt: createdAt1, + }, + { + username: 'nikk', + email: 'nikk@adonisjs.com', + createdAt: DateTime.now(), + }, + ]) + + assert.lengthOf(users, 2) + assert.isTrue(users[0].$isPersisted) + assert.isFalse(users[0].$isLocal) + + assert.isTrue(users[1].$isPersisted) + assert.isTrue(users[1].$isLocal) + + const usersList = await db.query().from('users') + assert.lengthOf(usersList, 3) + }) }) test.group('Base Model | hooks', (group) => {