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

meta: make Utils.now() and Utils.toDefaultValue() accept a dialect #15072

Merged
merged 1 commit into from Oct 1, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/dialects/abstract/index.ts
Expand Up @@ -126,6 +126,11 @@ export type DialectSupports = {
* enables the ability to use backslash escapes inside of the string.
*/
escapeStringConstants: boolean,

/**
* Whether this dialect supports date & time values with a precision down to at least the millisecond.
*/
milliseconds: boolean,
};

export abstract class AbstractDialect {
Expand Down Expand Up @@ -217,6 +222,7 @@ export abstract class AbstractDialect {
indexHints: false,
searchPath: false,
escapeStringConstants: false,
milliseconds: true,
};

declare readonly defaultVersion: string;
Expand Down
3 changes: 2 additions & 1 deletion src/dialects/db2/query-generator.js
Expand Up @@ -912,7 +912,8 @@ export class Db2QueryGenerator extends AbstractQueryGenerator {
for (const key in rawAttributes) {
if (rawAttributes[key].unique && dataValues[key] === undefined) {
if (rawAttributes[key].type instanceof DataTypes.DATE) {
dataValues[key] = Utils.now('db2');
// TODO: refactor this to this.dialect when https://github.com/sequelize/sequelize/pull/15069 has been merged
dataValues[key] = Utils.now(this._dialect);
} else if (rawAttributes[key].type instanceof DataTypes.STRING) {
dataValues[key] = `unique${uniqno++}`;
} else if (rawAttributes[key].type instanceof DataTypes.INTEGER) {
Expand Down
1 change: 1 addition & 0 deletions src/dialects/snowflake/index.js
Expand Up @@ -43,6 +43,7 @@ export class SnowflakeDialect extends AbstractDialect {
REGEXP: true,
schemas: true,
databases: true,
milliseconds: false,
});

constructor(sequelize) {
Expand Down
24 changes: 12 additions & 12 deletions src/model.js
Expand Up @@ -169,23 +169,23 @@ export class Model {
}

if (this.constructor._timestampAttributes.createdAt && defaults[this.constructor._timestampAttributes.createdAt]) {
this.dataValues[this.constructor._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.createdAt], this.sequelize.options.dialect);
this.dataValues[this.constructor._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.createdAt], this.sequelize.dialect);
delete defaults[this.constructor._timestampAttributes.createdAt];
}

if (this.constructor._timestampAttributes.updatedAt && defaults[this.constructor._timestampAttributes.updatedAt]) {
this.dataValues[this.constructor._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.updatedAt], this.sequelize.options.dialect);
this.dataValues[this.constructor._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.updatedAt], this.sequelize.dialect);
delete defaults[this.constructor._timestampAttributes.updatedAt];
}

if (this.constructor._timestampAttributes.deletedAt && defaults[this.constructor._timestampAttributes.deletedAt]) {
this.dataValues[this.constructor._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.deletedAt], this.sequelize.options.dialect);
this.dataValues[this.constructor._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.deletedAt], this.sequelize.dialect);
delete defaults[this.constructor._timestampAttributes.deletedAt];
}

for (key in defaults) {
if (values[key] === undefined) {
this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.options.dialect), { raw: true });
this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.dialect), { raw: true });
delete values[key];
}
}
Expand Down Expand Up @@ -1145,7 +1145,7 @@ Specify a different name for either index to resolve this issue.`);
}

if (Object.prototype.hasOwnProperty.call(definition, 'defaultValue')) {
this._defaultValues[name] = () => Utils.toDefaultValue(definition.defaultValue, this.sequelize.options.dialect);
this._defaultValues[name] = () => Utils.toDefaultValue(definition.defaultValue, this.sequelize.dialect);
}

if (Object.prototype.hasOwnProperty.call(definition, 'unique') && definition.unique) {
Expand Down Expand Up @@ -2509,7 +2509,7 @@ Specify a different name for either index to resolve this issue.`);
const updatedDataValues = _.pick(instance.dataValues, changed);
const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this);
const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this);
const now = Utils.now(this.sequelize.options.dialect);
const now = Utils.now(this.sequelize.dialect);

// Attach createdAt
if (createdAtAttr && !insertValues[createdAtAttr]) {
Expand Down Expand Up @@ -2578,7 +2578,7 @@ Specify a different name for either index to resolve this issue.`);
}

const dialect = this.sequelize.options.dialect;
const now = Utils.now(this.sequelize.options.dialect);
const now = Utils.now(this.sequelize.dialect);
options = Utils.cloneDeep(options);

// Add CLS transaction
Expand Down Expand Up @@ -2983,7 +2983,7 @@ Specify a different name for either index to resolve this issue.`);
[field]: Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null,
};

attrValueHash[field] = Utils.now(this.sequelize.options.dialect);
attrValueHash[field] = Utils.now(this.sequelize.dialect);
result = await this.queryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes);
} else {
result = await this.queryInterface.bulkDelete(this.getTableName(options), options.where, options, this);
Expand Down Expand Up @@ -3118,7 +3118,7 @@ Specify a different name for either index to resolve this issue.`);
}

if (this._timestampAttributes.updatedAt && !options.silent) {
values[this._timestampAttributes.updatedAt] = this._getDefaultTimestamp(this._timestampAttributes.updatedAt) || Utils.now(this.sequelize.options.dialect);
values[this._timestampAttributes.updatedAt] = this._getDefaultTimestamp(this._timestampAttributes.updatedAt) || Utils.now(this.sequelize.dialect);
}

options.model = this;
Expand Down Expand Up @@ -3275,7 +3275,7 @@ Specify a different name for either index to resolve this issue.`);

static _getDefaultTimestamp(attr) {
if (Boolean(this.rawAttributes[attr]) && Boolean(this.rawAttributes[attr].defaultValue)) {
return Utils.toDefaultValue(this.rawAttributes[attr].defaultValue, this.sequelize.options.dialect);
return Utils.toDefaultValue(this.rawAttributes[attr].defaultValue, this.sequelize.dialect);
}

}
Expand Down Expand Up @@ -3443,7 +3443,7 @@ Instead of specifying a Model, either:
const updatedAtAttr = this._timestampAttributes.updatedAt;
if (!options.silent && updatedAtAttr && !incrementAmountsByField[updatedAtAttr]) {
const attrName = this.rawAttributes[updatedAtAttr].field || updatedAtAttr;
extraAttributesToBeUpdated[attrName] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect);
extraAttributesToBeUpdated[attrName] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.dialect);
}

const tableName = this.getTableName(options);
Expand Down Expand Up @@ -3979,7 +3979,7 @@ Instead of specifying a Model, either:
const versionAttr = this.constructor._versionAttribute;
const hook = this.isNewRecord ? 'Create' : 'Update';
const wasNewRecord = this.isNewRecord;
const now = Utils.now(this.sequelize.options.dialect);
const now = Utils.now(this.sequelize.dialect);
let updatedAtAttr = this.constructor._timestampAttributes.updatedAt;

if (updatedAtAttr && options.fields.length > 0 && !options.fields.includes(updatedAtAttr)) {
Expand Down
23 changes: 7 additions & 16 deletions src/utils/dialect.ts
@@ -1,31 +1,22 @@
import isPlainObject from 'lodash/isPlainObject';
// eslint-disable-next-line import/order -- caused by temporarily mixing require with import
/* eslint-disable import/order -- caused by temporarily mixing require with import */
import { v1 as uuidv1, v4 as uuidv4 } from 'uuid';

const DataTypes = require('../data-types');
import type { AbstractDialect } from '../dialects/abstract';
/* eslint-enable import/order */

const dialectsSupportingMilliseconds = new Set([
'mariadb',
'mysql',
'postgres',
'sqlite',
'mssql',
'db2',
'ibmi',
]);
const DataTypes = require('../data-types');

// TODO: instead of receiving a dialect *name* here, require the actual AbstractDialect subclass
// and add a flag on AbstractDialect.supports to determine if the date should include milliseconds.
export function now(dialect: string): Date {
export function now(dialect: AbstractDialect): Date {
const d = new Date();
if (!dialectsSupportingMilliseconds.has(dialect)) {
if (!dialect.supports.milliseconds) {
d.setMilliseconds(0);
}

return d;
}

export function toDefaultValue(value: unknown, dialect: string): unknown {
export function toDefaultValue(value: unknown, dialect: AbstractDialect): unknown {
if (typeof value === 'function') {
const tmp = value();
if (tmp instanceof DataTypes.ABSTRACT) {
Expand Down
14 changes: 8 additions & 6 deletions test/unit/utils.test.js
Expand Up @@ -6,6 +6,8 @@ const expect = chai.expect;
const Support = require('./support');
const { DataTypes, Op, Utils } = require('@sequelize/core');

const dialect = Support.sequelize.dialect;

describe(Support.getTestDialectTeaser('Utils'), () => {
describe('merge', () => {
it('does not clone sequelize models', () => {
Expand Down Expand Up @@ -34,22 +36,22 @@ describe(Support.getTestDialectTeaser('Utils'), () => {

describe('toDefaultValue', () => {
it('return plain data types', () => {
expect(Utils.toDefaultValue(DataTypes.UUIDV4)).to.equal('UUIDV4');
expect(Utils.toDefaultValue(DataTypes.UUIDV4, dialect)).to.equal('UUIDV4');
});
it('return uuid v1', () => {
expect(/^[\da-z-]{36}$/.test(Utils.toDefaultValue(DataTypes.UUIDV1()))).to.be.equal(true);
expect(/^[\da-z-]{36}$/.test(Utils.toDefaultValue(DataTypes.UUIDV1(), dialect))).to.be.equal(true);
});
it('return uuid v4', () => {
expect(/^[\da-z-]{36}/.test(Utils.toDefaultValue(DataTypes.UUIDV4()))).to.be.equal(true);
expect(/^[\da-z-]{36}/.test(Utils.toDefaultValue(DataTypes.UUIDV4(), dialect))).to.be.equal(true);
});
it('return now', () => {
expect(Object.prototype.toString.call(Utils.toDefaultValue(DataTypes.NOW()))).to.be.equal('[object Date]');
expect(Object.prototype.toString.call(Utils.toDefaultValue(DataTypes.NOW(), dialect))).to.be.equal('[object Date]');
});
it('return plain string', () => {
expect(Utils.toDefaultValue('Test')).to.equal('Test');
expect(Utils.toDefaultValue('Test', dialect)).to.equal('Test');
});
it('return plain object', () => {
chai.assert.deepEqual({}, Utils.toDefaultValue({}));
chai.assert.deepEqual({}, Utils.toDefaultValue({}, dialect));
});
});

Expand Down