I am trying to set up sequelize in my node project and for now I have
//sequelize init
const { DataTypes } = Sequelize;
const sequelize = new Sequelize({
database: database,
username: user,
host: server,
password: password,
dialect: 'mssql',
dialectOptions: {
options: {
useUTC: true,
dateFirst: 1,
}
},
define:{
timestamps:false,
paranoid:false,
freezeTableName: true
}
});
//and my Model
const User= sequelize.define('User', {
// attributes
id: {
field:'Id',
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
} ,
startTime: {
field:'startTime',
type: Sequelize.DATE
}
});
I try to setup version:true to enable Optimistic Locking
I put it in model
const Vessel = sequelize.define('FDMData', {
// attributes
id: {
field:'vesselId',
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
} ,
startTime: {
field:'startTime',
type: Sequelize.DATE
}
},{
version:true
}
);
and I get Unhandled rejection SequelizeDatabaseError: Invalid column name 'version'.
I also tried to set it as global while init
const { DataTypes } = Sequelize;
const sequelize = new Sequelize({
database: database,
username: user,
host: server,
password: password,
dialect: 'mssql',
dialectOptions: {
options: {
useUTC: true,
dateFirst: 1,
}
},
define:{
timestamps:false,
paranoid:false,
freezeTableName: true,
version: true
}
});
and again, I get Unhandled rejection SequelizeDatabaseError: Invalid column name 'version'.
What am I missing? How can I fix this?
Thanks
When you set version: true and you are creating your database structure manually, sequelize expect to find a column
named version on the table : so add a column version INTEGER NOT NULL DEFAULT 0 to your tables.
You can also name the versioning column what ever you want, just passe a string version: "myVersionColumn"
If you let sequelize handle the creation of the DB structure, it generate a DDL for the FDMData table that look like
CREATE TABLE IF NOT EXISTS FDMData (
vesselId INTEGER NOT NULL ,
startTime DATETIME,
version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (vesselId)
)
and your code work juste fine. For example
Vessel.sync().then(model=> {
// or sequelize.sync()
console.log(model)
}).catch(error=> {
console.log(error)
})
you are almost there, based on the docs, setting version to true or set it to whatever name you want do the trick
Enable optimistic locking. When enabled, sequelize will add a version count attribute
to the model and throw an OptimisticLockingError error when stale instances are saved.
Set to true or a string with the attribute name you want to use to enable.
however, just in the next section to optimistic locking -Database synchronization- it says
When starting a new project you won't have a database structure and using Sequelize you won't need to
meaning, sequelize doesn't depend on a sql structure already set in your database for this purpose if you sync your models after you define them except for the database definition, it will automatically create it for you including the version field, here is an example
const Sequelize = require('sequelize');
const config = {
username: "root",
password: "123",
tableName: "test",
options: {
host: '127.0.0.1',
dialect: 'mysql',
pool: {
max: 3,
min: 1,
acquire: 30000,
idle: 10000
},
define: {
timestamps:false,
paranoid:false,
freezeTableName: true,
version: true
}
}
};
const sequelize = new Sequelize(config.tableName, config.username, config.password, config.options);
//and my Model
const User= sequelize.define('User', {
// attributes
id: {
field:'Id',
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
} ,
startTime: {
field:'startTime',
type: Sequelize.DATE
}
});
User.sync();
if you run this script you will see the following sql statements executed
Executing (default): CREATE TABLE IF NOT EXISTS User (Id INTEGER
NOT NULL , startTime DATETIME, version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (Id)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM User
however, if you don't want sequelize to sync your models, you have to explicitly have that field in your already established tables, but not explicitly defined in sequelize model as it will automatically know its there.
Adding an example, a User model (using sequelize v6):
const { Sequelize, Model, DataTypes, Deferrable, DatabaseError } = require('sequelize');
const sequelize = require('../lib/db.sequelize').db;
class User extends Model {
}
User.init({
// The following specification of the 'id' attribute could be omitted
// since it is the default.
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
version: { // Optimistic Locking
allowNull: false,
type: DataTypes.INTEGER,
defaultValue: 0
},
name: {
allowNull: false,
type: DataTypes.STRING,
},
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isEmail: true
}
}
}, {
sequelize,
modelName: 'user',
version: true, // Optimistic Locking
indexes: [
{
name: 'user_id_index',
method: 'BTREE',
fields: ['id'],
},
{
name: 'user_email_index',
method: 'BTREE',
fields: ['email'],
}
],
freezeTableName: true
});
module.exports = User;
Note that a version attribute was added to the model as well as passing the "version: true" to the options.
Related
So I set up a Sequelize client to work with my MariaDB instance. The connection is fine, the tables exist, and everything is working expectedly, except for when I try to add a User with the User.create method. I expect that the validation would work as expected, and a user would be created when non-null values are passed.
Here I'm initializing the User class:
class User extends Model {
declare id: number;
declare username: string;
declare password: string;
declare role: string;
declare disabled: boolean;
declare email?: string;
declare phone_number?: string;
declare interests?: string;
declare first_name?: string;
declare last_name?: string;
constructor() {
super();
}
public comparePassword = async (password: string): Promise<boolean> => {
return await bc.compare(password, this.password);
};
public getPublic = (): User => {
const { password, ...publicUser } = this.get();
return publicUser as User;
};
}
Then here I'm using an initUsers function to actually initialize the users table:
export const initUsers = async () => {
User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
username: {
type: new DataTypes.STRING(128),
allowNull: false,
},
password: {
type: new DataTypes.STRING(128),
allowNull: false,
},
role: {
type: new DataTypes.STRING(128),
allowNull: false,
},
disabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
},
email: {
type: new DataTypes.STRING(128),
allowNull: true,
},
phone_number: {
type: new DataTypes.STRING(128),
allowNull: true,
},
interests: {
type: new DataTypes.STRING(128),
allowNull: true,
},
first_name: {
type: new DataTypes.STRING(128),
allowNull: true,
},
last_name: {
type: new DataTypes.STRING(128),
allowNull: true,
},
},
{
tableName: "users",
sequelize: db.sequelize,
modelName: "User",
timestamps: true,
freezeTableName: true,
}
);
User.create({
username: "admin",
password: await bc.hash("admin", 10),
role: "superuser",
disabled: false,
});
};
You can see that I've also added a manual User.create call, that, if working as expected, would create a user with the username "admin", a hashed password of the same value, etc. These 4 fields are marked as non-nullable, however, as you can see, the values I pass are not null.
I still, however, receive the following error when this User.create method runs:
SequelizeValidationError: notNull Violation: User.username cannot be null,
notNull Violation: User.password cannot be null,
notNull Violation: User.role cannot be null,
notNull Violation: User.disabled cannot be null
at InstanceValidator._validate (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\instance-validator.js:78:13)
at InstanceValidator._validateAndRunHooks (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\instance-validator.js:111:7)
at InstanceValidator.validate (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\instance-validator.js:93:12)
at User.save (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\model.js:3996:7)
at Function.create (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\model.js:2280:12)
[ERROR] 01:26:21 SequelizeValidationError: notNull Violation: User.username cannot be null,
notNull Violation: User.password cannot be null,
notNull Violation: User.role cannot be null,
notNull Violation: User.disabled cannot be null
When I console.log the value of the User.create method. It returns the async function I'd expect:
console.log("User", User.create);
// output: User [AsyncFunction: create]
console.log("User", User === db.sequelize.models.User);
// output: true
Here is some more context that might be helpful. Here is how I'm initializing the sequelize instance in a separate db.ts file:
export const db: {
sequelize: Sequelize;
} = {
sequelize: new Sequelize({
dialect: "mariadb",
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || "3306"),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
logging: false,
sync: { force: true },
}),
};
export const initDB = async () => {
await db.sequelize.authenticate();
await initUsers();
await initTokens();
await User.sync({ force: true });
await Token.sync({ force: true });
};
Like I said, the sequelize instance itself seems to be working as expected. The tables show up in the database, they have all the expected columns, although there are no entries.
So I found a fix. It turns out the culprit was adding the
constructor() {
super();
}
to the User class. Not sure why this is the case, but removing that fixed the problem. Still happy if anyone can provide an explanation for why this breaks it, but this solves my issue for now.
I am creating a test for my models, now my scenario is I want to insert to a table with a column/field that is not defined on my Sequelize schema.
Here is my sample for my schema
class ResploginSchema extends Sequelize.Model { }
ResploginSchema.init(
{
resploginID: {
type: Sequelize.BIGINT(20).UNSIGNED,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
clientID: {
type: Sequelize.INTEGER(10).UNSIGNED,
allowNull: false,
validate: {
isNumeric: true
}
},
tablelimitID: {
type: Sequelize.TINYINT(2).UNSIGNED,
allowNull: false
},
errMsgID: {
type: Sequelize.INTEGER(6).UNSIGNED,
allowNull: false
}
},
{
sequelize: dbConnection,
modelName: "resplogin",
freezeTableName: true,
timestamps: false
}
);
Here is the sample data I want to insert
let data = {
clientID: 1,
sessionID: "not defined field",
tablelimitID: 1,
errMsgID: 0
};
ResploginSchema.create(data)
I was expecting that it should return an error saying that the sessionID is not defined on the model definition.
Maybe I missed some configuration on the model. Thanks in advance
Sequelize version : 5.21.3
Node Version : 10.14.2
This is expected behavior. Sequelize will only try and find the fields in the schema it does not care if there are other fields. In fact there are many cases where you may need virtual fields that are not stored in the database, but you may use in your business logic.
I have 2 models users and tags, both are associated through another model called usersTags and all 3 models have paranoid set with custom timestamps. I understand that associating models will add additional methods to work on the associations to all associated models, so i am wanting to making a simple setTags call for users, the docs shows that if in the array in the method does not contain the element that is stored in the database it should be removed, otherwise it should be created/restored.
So i try to restore a previously removed tag but for some reason it fails. The models are defined as following:
Users
module.exports = function(sequelize, DataTypes) {
const Users = sequelize.define("users", {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(100),
allowNull: false,
validate: {
len: {
args: [3, 100],
msg: "String length is not in this range"
}
}
},
password: {
type: DataTypes.STRING(100),
allowNull: false,
field: "password_hash"
}
}, {
tableName: "users",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true
});
Users.associate = function(models) {
// Add this association to include tag records
this.belongsToMany(models.tags, {
through: {
model: models.usersTags,
unique: true
},
foreignKey: "users_id",
constraints: false
});
};
return Users;
};
Tags
module.exports = function(sequelize, DataTypes) {
const Tags = sequelize.define("tags", {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(45),
allowNull: false
}
}, {
tableName: "tags",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true
});
Tags.associate = function(models) {
this.belongsToMany(models.users, {
through: {
model: models.usersTags,
unique: true
},
foreignKey: "tags_id",
constraints: false
});
};
return Tags;
};
usersTags
module.exports = function(sequelize, DataTypes) {
const UsersTags = sequelize.define("usersTags", {
users_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: "users",
key: "id"
}
},
tags_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: "tags",
key: "id"
}
}
}, {
tableName: "users_tags",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true,
indexes: [
{
unique: true,
fields: ["users_id", "tags_id"]
}
]
});
return UsersTags;
};
Test
let _user;
models.users.findOne({where: {id: 100}})
.then(user => {
_user = user;
return _user.setTags([1]); // Successfully create association tag with id 1
})
.then(() => _user.setTags([])) // Successfully remove all associated tags
.then(() => _user.setTags([1])); // Should restore association tag with id 1 but fails
Executed query
app:database Executing (default): SELECT `id`, `username`, `first_name`, `last_name`, `birthday`, `description`, `location`, `email`, `type`, `image_path` FROM `users` AS `users` WHERE ((`users`.`delete_time` > '2018-08-28 19:40:15' OR `users`.`delete_time` IS NULL) AND `users`.`id` = 100); +0ms
app:database Executing (default): SELECT `users_id`, `tags_id`, `create_time`, `update_time`, `delete_time` FROM `users_tags` AS `usersTags` WHERE ((`usersTags`.`delete_time` > '2018-08-28 19:40:15' OR `usersTags`.`delete_time` IS NULL) AND `usersTags`.`users_id` = 100); +6ms
app:database Executing (default): INSERT INTO `users_tags` (`users_id`,`tags_id`,`create_time`,`update_time`) VALUES (100,1,'2018-08-28 19:40:15','2018-08-28 19:40:15'); +7ms
For some reason the tag search query is failing to retrieve the tag that contains the delete_time set and therefore the last query is insert instead of update, i know the workaround would be to set paranoid to false but i have to keep track of all activities, i know another workaround would be to create a custom model method to handle this but i still want to know if there is a way to achieve this without having to create an additional custom method
your code in not in a correct async order so your _user global variable is not initiated,I think this is the correct order :
let _user;
models.users.findOne({where: {id: 100}})
.then(user => {
_user = user;
_user.setTags([]).then(()=>{
_user.setTags([1])
})
})
The names of my tables in the .create() method function and model definitions are swinging from singular to plural and vice-versa. Why does Sequelize have this functionality at all? And why is disabling it so unstable?
The table names in my database are (as in the code) "user", "email", "settings". But when doing the INSERT and SELECT SQL statements Sequelize singularizes the names as if I there was need for a library to choose the best name for my database tables! Because of that, some INSERTs fail.
Here is my code:
// DEPENDENCIES
const Sequelize = require('sequelize');
// Connection set up:
const sequelize = new Sequelize(
'sql_database_1',
'sqlusername1',
'dbpassw0rd',
{ // Sequelize options:
host: 'localhost',
port: 3306,
dialect: 'mysql',
operatorsAliases: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
logging: console.log,
define: {
freezeTableName: true, // Do not change my table names.
timestamps: false // I will do this individually, thanks.
},
});
// Set up models:
const User = sequelize.define('user',
{ // Database columns:
user_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
column1: Sequelize.STRING,
});
const Settings = sequelize.define('settings',
{ // Database columns:
entry_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
owner_id: Sequelize.INTEGER,
column1: Sequelize.STRING
});
const Email = sequelize.define('email',
{ // Database columns:
entry_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
owner_id: Sequelize.INTEGER,
column1: Sequelize.STRING
});
// Set up associations:
User.hasOne(Settings,
{ // Options:
foreignKey: 'owner_id'
});
User.hasMany(Email,
{ // Options:
foreignKey: 'owner_id'
});
// Actions:
sequelize
.sync({
force: true
})
.then(function() {
User
.create({
column1: 'test123',
settings: { // STACK OVERFLOW: Not working because of Sequelize singularizing the name.
column1: 'This is dummy address'
},
emails: [ // STACK OVERFLOW: I need to write this table name in plural to work because Sequelize is changing MY names...
{ column1: 'Some data here' },
{ column1: 'Other data there' }
],
},
{
include: [Settings, Email]
})
})
.then(function() {
User
.findOne({
include: [Settings, Email],
})
.then(function(result) {
console.log('FindAll results:\n', JSON.stringify(result));
});
});
As you can see, I am using "define: { freezeTableName: true }" in the object dedicated to set up Sequelize options. It is only working when creating the new table names: it does not pluralize them. The INSERT and SELECT statements still have a similar same problem: they are being singularized.
Can this be a bug?
It has to do with the association you define. For settings, we have hasOne relationship. Hence the name is singular. For emails, we have hasMany, and henve the plural.
Lets look at an example below.
const User = sequelize.define('user', {
username: Sequelize.STRING,
});
const Email = sequelize.define('emails', {
text: Sequelize.STRING,
});
User.hasMany(Email);
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test1234',
emails: {
text: 'this is dummy Email123'
},
}, { include: [Email] }))
.then(user => {
console.log(user.dataValues);
});
I have used emails because User have hasMany relationship with Email.
If I change the relationship type to hasOne, I will have to use singular name.
const User = sequelize.define('user', {
username: Sequelize.STRING,
});
const Email = sequelize.define('emails', {
text: Sequelize.STRING,
});
User.hasOne(Email);
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test1234',
email: {
text: 'this is dummy Email123'
},
}, { include: [Email] }))
.then(user => {
console.log(user.dataValues);
});
I try to create database model by using sequelize but I'm facing a problem with model's primary key.
Setting
I'm using Postgres (v10) in docker container and sequalize (Node.js v10.1.0
) for models and GraphQL (0.13.2) + GraphQL-Sequalize (8.1.0) for request processing.
Problem
After creating models by sequelize-cli I've manually tried to replace id column with uuid. Here's my model migration that I'm using.
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Currencies', {
uuid: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false
},
name: {
type: Sequelize.STRING
},
ticker: {
type: Sequelize.STRING
},
alt_tickers: {
type: Sequelize.ARRAY(Sequelize.STRING)
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Currencies');
}
};
Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Currency = sequelize.define('Currency', {
uuid: DataTypes.UUID,
name: DataTypes.STRING,
ticker: DataTypes.STRING,
alt_tickers: DataTypes.ARRAY(DataTypes.STRING)
}, {});
Currency.associate = function(models) {
// associations can be defined here
};
return Currency;
};
Due to some problem sequalize executes next expression:
Executing (default): SELECT "id", "uuid", "name", "ticker", "alt_tickers", "createdAt", "updatedAt" FROM "Currencies" AS "Currency" ORDER BY "Currency"."id" ASC;
That leads to "column 'id' doesn't exist" error.
Alternatively, I've tried to fix it by renaming uuid column to id at migration:
...
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4()
},
...
And at the model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Currency = sequelize.define('Currency', {
id: DataTypes.INTEGER,
name: DataTypes.STRING,
ticker: DataTypes.STRING,
alt_tickers: DataTypes.ARRAY(DataTypes.STRING)
}, {});
Currency.associate = function(models) {
// associations can be defined here
};
return Currency;
};
but the result was the following error at the start of the program:
Error: A column called 'id' was added to the attributes of 'Currencies' but not marked with 'primaryKey: true'
Questions
So, is there a way to force sequelize to use UUID as the tables primary key without defining id column?
Is there a way to create columns without id columns?
What possibly caused this errors and how should fix it?
Thanks in advance!
What you have not posted here is your model code. This is what I think has happened
The database has been manually changed from id to uuid.
Your model does not reflect this change.
Hence the query is searching for both id and uuid.
You can fix this my defining uuid in your model like below and making it a primary key
const User = sequelize.define('user', {
uuid: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1,
primaryKey: true
},
username: Sequelize.STRING,
});
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test123'
}).then((user) => {
console.log(user);
}));
This is just about the only resource I've found online that explains what it takes to set up a UUID column that the database provides defaults for, without relying on the third-party uuid npm package: https://krmannix.com/2017/05/23/postgres-autogenerated-uuids-with-sequelize/
Short version:
You'll need to install the "uuid-ossp" postgres extension, using a sqlz migration
When defining the table, use this defaultValue: Sequelize.literal( 'uuid_generate_v4()' )