From 0efa7fab44c65ef3f925f47db4006d4c8a60bd87 Mon Sep 17 00:00:00 2001 From: JimmyDaddy Date: Tue, 25 Jan 2022 15:27:21 +0800 Subject: [PATCH 1/2] fix: fix #263 upsert attributes should use defaultValue while there is no value assigned --- src/bone.js | 22 ++++++++++++++++++---- src/contants.js | 19 ++++++++++++++++++- src/realm.js | 7 +------ test/integration/suite/basics.test.js | 7 +++++++ test/unit/adapters/sequelize.test.js | 13 +++++++++++-- test/unit/bone.test.js | 15 +++++++++++++++ 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/bone.js b/src/bone.js index 07a771f0..7520443d 100644 --- a/src/bone.js +++ b/src/bone.js @@ -15,6 +15,7 @@ const Raw = require('./raw'); const { capitalize, camelCase, snakeCase } = require('./utils/string'); const { hookNames, setupSingleHook } = require('./setup_hooks'); const { logger } = require('./utils/index'); +const { TIMESTAMPS_ATTRNAME, LEGACY_TIMESTAMP_CLOUMN_MAP } = require('./contants'); function looseReadonly(props) { return Object.keys(props).reduce((result, name) => { @@ -846,9 +847,13 @@ class Bone { const data = {}; const Model = this; const { attributes } = Model; - for (const name in values) { - if (this.hasAttribute(name)) { - data[name] = values[name]; + for (const key in attributes) { + const attribute = attributes[key]; + if (attribute.primaryKey) continue; + if (!values[key] && attribute.defaultValue != null) { + data[key] = attribute.defaultValue; + } else if (values[key] !== undefined){ + data[key] = values[key]; } } @@ -931,6 +936,15 @@ class Bone { const attribute = new Attribute(name, attributes[name], options.define); attributeMap[attribute.columnName] = attribute; attributes[name] = attribute; + if (TIMESTAMPS_ATTRNAME.includes(name)) { + const { columnName } = attribute; + const legacyColumnName = LEGACY_TIMESTAMP_CLOUMN_MAP[columnName]; + if (!columnMap[columnName] && legacyColumnName && columnMap[legacyColumnName]) { + // correct columname + attribute.columnName = legacyColumnName; + attributeMap[attribute.columnName] = attribute; + } + } const columnInfo = columnMap[attribute.columnName]; // if datetime or timestamp precision not defined, default to column info if (columnInfo && attribute.type instanceof DataTypes.DATE && attribute.type.precision == null) { @@ -940,7 +954,7 @@ class Bone { const primaryKey = Object.keys(attributes).find(key => attributes[key].primaryKey); const timestamps = {}; - for (const key of [ 'createdAt', 'updatedAt', 'deletedAt' ]) { + for (const key of TIMESTAMPS_ATTRNAME) { const name = attributes.hasOwnProperty(key) ? key : snakeCase(key); const attribute = attributes[name]; diff --git a/src/contants.js b/src/contants.js index 24ac1a9d..7caa5a87 100644 --- a/src/contants.js +++ b/src/contants.js @@ -8,6 +8,23 @@ const AGGREGATOR_MAP = { sum: 'sum' }; +const LEGACY_TIMESTAMP_MAP = { + gmtCreate: 'createdAt', + gmtModified: 'updatedAt', + gmtDeleted: 'deletedAt', +}; + +const LEGACY_TIMESTAMP_CLOUMN_MAP = { + created_at: 'gmt_create', + updated_at: 'gmt_modified', + deleted_at: 'gmt_deleted', +}; + +const TIMESTAMPS_ATTRNAME = [ 'createdAt', 'updatedAt', 'deletedAt' ]; + module.exports = { - AGGREGATOR_MAP + AGGREGATOR_MAP, + LEGACY_TIMESTAMP_MAP, + TIMESTAMPS_ATTRNAME, + LEGACY_TIMESTAMP_CLOUMN_MAP }; diff --git a/src/realm.js b/src/realm.js index 4b49182b..f9b93b2a 100644 --- a/src/realm.js +++ b/src/realm.js @@ -8,6 +8,7 @@ const { findDriver } = require('./drivers'); const { camelCase } = require('./utils/string'); const sequelize = require('./adapters/sequelize'); const Raw = require('./raw'); +const { LEGACY_TIMESTAMP_MAP } = require('./contants'); /** * @@ -39,12 +40,6 @@ async function findModels(dir) { return models; } -const LEGACY_TIMESTAMP_MAP = { - gmtCreate: 'createdAt', - gmtModified: 'updatedAt', - gmtDeleted: 'deletedAt', -}; - /** * construct model attributes entirely from column definitions * @param {Bone} model diff --git a/test/integration/suite/basics.test.js b/test/integration/suite/basics.test.js index ec10b381..34aba6cb 100644 --- a/test/integration/suite/basics.test.js +++ b/test/integration/suite/basics.test.js @@ -912,6 +912,13 @@ describe('=> Basic', () => { count = await Tag.count(); assert.equal(count, 1); }); + + it('Bone.upsert should assigne defaultValue automatically', async () => { + await User.upsert({ email: 'yes@yes', nickname: 'halo' }); + const user = await User.findOne({ nickname: 'halo' }); + assert.equal(user.attribute('status'), 1); + assert.equal(user.level, 1); + }); }); }); diff --git a/test/unit/adapters/sequelize.test.js b/test/unit/adapters/sequelize.test.js index 73e06b0d..36e5b7d9 100644 --- a/test/unit/adapters/sequelize.test.js +++ b/test/unit/adapters/sequelize.test.js @@ -1495,8 +1495,6 @@ describe('validator should work', () => { const attributes = { id: DataTypes.BIGINT, - gmt_create: DataTypes.DATE, - gmt_deleted: DataTypes.DATE, email: { type: DataTypes.STRING(256), allowNull: false, @@ -1743,11 +1741,22 @@ describe('validator should work', () => { status: 1, }, { validate: false }); + await new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 1000); + }); + assert(user); const users = await User.findAll(); + assert.equal(users.length, 1); assert.equal(users[0].id, user.id); + + // created should not be updated + assert.equal(users[0].createdAt.getTime(), user.createdAt.getTime()); + assert.notEqual(users[0].level, user.level); await user.reload(); assert.equal(users[0].level, user.level); }); diff --git a/test/unit/bone.test.js b/test/unit/bone.test.js index 013cfb99..4dd24b2d 100644 --- a/test/unit/bone.test.js +++ b/test/unit/bone.test.js @@ -201,6 +201,21 @@ describe('=> Bone', function() { // should not override existing one assert.equal(User.attributes.updatedAt.type.precision, 0); }); + + it('should adapt legacy timestamps', async () => { + class User extends Bone { + static attributes = { + createdAt: DATE, + } + } + User.load([ + { columnName: 'id', columnType: 'bigint', dataType: 'bigint', primaryKey: true }, + { columnName: 'gmt_create', columnType: 'timestamp', dataType: 'timestamp' }, + ]); + + assert.ok(User.timestamps.createdAt); + assert.equal(User.attributes.createdAt.columnName, 'gmt_create'); + }); }); describe('=> Bone.loadAttribute()', function() { From 6b90d0cf22d883792171dbdc0b20a36e619ceb79 Mon Sep 17 00:00:00 2001 From: JimmyDaddy Date: Wed, 26 Jan 2022 13:07:25 +0800 Subject: [PATCH 2/2] fix: typo and upsert value validating --- src/bone.js | 10 +++++----- src/collection.js | 2 +- src/{contants.js => constants.js} | 8 ++++---- src/realm.js | 2 +- src/spell.js | 2 +- test/integration/suite/basics.test.js | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) rename src/{contants.js => constants.js} (71%) diff --git a/src/bone.js b/src/bone.js index 7520443d..a19ab038 100644 --- a/src/bone.js +++ b/src/bone.js @@ -15,7 +15,7 @@ const Raw = require('./raw'); const { capitalize, camelCase, snakeCase } = require('./utils/string'); const { hookNames, setupSingleHook } = require('./setup_hooks'); const { logger } = require('./utils/index'); -const { TIMESTAMPS_ATTRNAME, LEGACY_TIMESTAMP_CLOUMN_MAP } = require('./contants'); +const { TIMESTAMP_NAMES, LEGACY_TIMESTAMP_COLUMN_MAP } = require('./constants'); function looseReadonly(props) { return Object.keys(props).reduce((result, name) => { @@ -850,7 +850,7 @@ class Bone { for (const key in attributes) { const attribute = attributes[key]; if (attribute.primaryKey) continue; - if (!values[key] && attribute.defaultValue != null) { + if (values[key] == null && attribute.defaultValue != null) { data[key] = attribute.defaultValue; } else if (values[key] !== undefined){ data[key] = values[key]; @@ -936,9 +936,9 @@ class Bone { const attribute = new Attribute(name, attributes[name], options.define); attributeMap[attribute.columnName] = attribute; attributes[name] = attribute; - if (TIMESTAMPS_ATTRNAME.includes(name)) { + if (TIMESTAMP_NAMES.includes(name)) { const { columnName } = attribute; - const legacyColumnName = LEGACY_TIMESTAMP_CLOUMN_MAP[columnName]; + const legacyColumnName = LEGACY_TIMESTAMP_COLUMN_MAP[columnName]; if (!columnMap[columnName] && legacyColumnName && columnMap[legacyColumnName]) { // correct columname attribute.columnName = legacyColumnName; @@ -954,7 +954,7 @@ class Bone { const primaryKey = Object.keys(attributes).find(key => attributes[key].primaryKey); const timestamps = {}; - for (const key of TIMESTAMPS_ATTRNAME) { + for (const key of TIMESTAMP_NAMES) { const name = attributes.hasOwnProperty(key) ? key : snakeCase(key); const attribute = attributes[name]; diff --git a/src/collection.js b/src/collection.js index 3468d2d6..0cb7699a 100644 --- a/src/collection.js +++ b/src/collection.js @@ -1,6 +1,6 @@ 'use strict'; -const { AGGREGATOR_MAP } = require('./contants'); +const { AGGREGATOR_MAP } = require('./constants'); const AGGREGATORS = Object.values(AGGREGATOR_MAP); /** diff --git a/src/contants.js b/src/constants.js similarity index 71% rename from src/contants.js rename to src/constants.js index 7caa5a87..8863d505 100644 --- a/src/contants.js +++ b/src/constants.js @@ -14,17 +14,17 @@ const LEGACY_TIMESTAMP_MAP = { gmtDeleted: 'deletedAt', }; -const LEGACY_TIMESTAMP_CLOUMN_MAP = { +const LEGACY_TIMESTAMP_COLUMN_MAP = { created_at: 'gmt_create', updated_at: 'gmt_modified', deleted_at: 'gmt_deleted', }; -const TIMESTAMPS_ATTRNAME = [ 'createdAt', 'updatedAt', 'deletedAt' ]; +const TIMESTAMP_NAMES = [ 'createdAt', 'updatedAt', 'deletedAt' ]; module.exports = { AGGREGATOR_MAP, LEGACY_TIMESTAMP_MAP, - TIMESTAMPS_ATTRNAME, - LEGACY_TIMESTAMP_CLOUMN_MAP + TIMESTAMP_NAMES, + LEGACY_TIMESTAMP_COLUMN_MAP }; diff --git a/src/realm.js b/src/realm.js index f9b93b2a..fbe3551d 100644 --- a/src/realm.js +++ b/src/realm.js @@ -8,7 +8,7 @@ const { findDriver } = require('./drivers'); const { camelCase } = require('./utils/string'); const sequelize = require('./adapters/sequelize'); const Raw = require('./raw'); -const { LEGACY_TIMESTAMP_MAP } = require('./contants'); +const { LEGACY_TIMESTAMP_MAP } = require('./constants'); /** * diff --git a/src/spell.js b/src/spell.js index 12f7482d..089ae0f6 100644 --- a/src/spell.js +++ b/src/spell.js @@ -11,7 +11,7 @@ const { isPlainObject } = require('./utils'); const { IndexHint, INDEX_HINT_TYPE, Hint } = require('./hint'); const { parseObject } = require('./query_object'); const Raw = require('./raw'); -const { AGGREGATOR_MAP } = require('./contants'); +const { AGGREGATOR_MAP } = require('./constants'); /** * Parse condition expressions diff --git a/test/integration/suite/basics.test.js b/test/integration/suite/basics.test.js index 34aba6cb..8684aa37 100644 --- a/test/integration/suite/basics.test.js +++ b/test/integration/suite/basics.test.js @@ -913,7 +913,7 @@ describe('=> Basic', () => { assert.equal(count, 1); }); - it('Bone.upsert should assigne defaultValue automatically', async () => { + it('Bone.upsert should assign defaultValue automatically', async () => { await User.upsert({ email: 'yes@yes', nickname: 'halo' }); const user = await User.findOne({ nickname: 'halo' }); assert.equal(user.attribute('status'), 1);