Sequelize primaryKey as UUID in MySql database - not working - mysql

I'm facing the problem with implementation model in Sequalize with primary key as uuid. I follow step by step all instruction, but still I cannot solve it.
This is how my model looks like:
'use strict';
module.exports = (sequelize, DataTypes) => {
var Todo = sequelize.define('Todo', {
title: DataTypes.STRING,
description: DataTypes.STRING,
test: DataTypes.UUID
}, {});
Todo.associate = function(models) {
// associations can be defined here
};
return Todo;
};
And migration file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Todos', {
id: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1
},
title: {
type: Sequelize.STRING
},
description: {
type: Sequelize.STRING
},
test: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Todos');
}
};
After calling post method to create element, I habe got an empty value in ID column, and NULL in test column...
The Datebase is MySql.
Is there any other way to do it?

1) Setting Custom Keys in Sequelize
I'm facing the problem with implementation model in Sequalize with
primary key as uuid.
By default, if your model does not contain a key with primaryKey: true, then sequelize assumes a PK of type INTEGER named id.
In your case, it seems like you wish to make your own custom PK.
Use the following in your model:
var Todo = sequelize.define('Todo', {
id: {
primaryKey: true,
type: DataTypes.UUID
}
// rest of properties
});
2) Validations
After calling post method to create element, I habe got an empty value in ID column, and NULL in test column...
Without much information regarding not only your query, but how you seeded the database, it's hard to answer specifically.
However, it doesn't surprise me that test column was null, because you have not listed any validations. Thus if you seed/create rows that do not set a test value, it will be null.
To create validations do the following
model:
var Todo = sequelize.define('Todo', {
// rest of properties
test: {
allowNull: false
type: DataTypes.UUID,
validate: {
notNull: true
}
}
});
migration:
queryInterface.createTable('Todos', {
// rest of properties
test: {
allowNull: false,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
}
});
3) Keeping Models and Migrations Synchronized
(see: https://stackoverflow.com/a/49796801/8954866) In sequelize, models and migrations are not automatically in-sync with one another, except when initially generated using sequelize-cli model:generate. I'm unsure if you ran your migrations or if you were running your query in a unit test against the model. But you have to make sure they are synchronized. A primary example is that in the above case, your migration says id is of type UUID, but your model will think it's of type INTEGER.
References
Setting Custom Primary Keys
Validations and allowNull

Make sure your package.json has updated uuid module, this was the problem in my case.
accessSecret: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
unique: true,
},
This works fine,
just make sure package.json has
"uuid": "^8.2.0",

Looks like this issue is the same for both MySQL and Postgres: Sequelize needs to use the defaultValue setting of the model to point to its uuid dependency.
For example, for an entity called Product, create your migration like this:
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Products", {
id: {
type: Sequelize.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV4,
},
productName: {
type: Sequelize.STRING,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Products");
},
};
Then, create your model like this:
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Product extends Model {
static associate(models) {
// associations
}
}
Product.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
productName: DataTypes.STRING,
},
{
sequelize,
modelName: "Product",
}
);
return Product;
};
Note that in your model, you're defining defaultValue: DataTypes.UUIDV4.
When you create a new instance of Product, you only need the name field. The UUID is auto-generated by the Sequelize model.
const {
Product
} = require("./models");
const createProduct = async (name) => {
const newProductData = { productName: "First product" };
const creationResult = await Product.create(newProductData);
};
The id field in the model will trace the defaultValue setting back to the v4() function of the uuid package that Sequelize relies on to auto-generate the UUID as part of the model operation, not as part of native Postgres.

Related

Sequelize migration 'Can't create table (errno: 150 "Foreign key constraint is incorrectly formed")'

When adding Sequelize to my project, I've had quite a struggle getting started with the migration and adding Foreign Key contraints.
Documentation is... well, there's room for improvement!
According to my online search, it seems others are struggling too.
Wanting to associate two tables in a one-to-many relation:
Model 1: Userstatus (values: 'init', 'active', 'inactive' ...)
Model 2: User (username, email, pwdhash, ...)
First of all, create the migration files:
$ sequelize model:generate --name Userstatus --attributes name:string,value:tinyint,comment:string
$ sequelize model:generate --name User --attributes username:string,pwdhash:string,email:string,statusId:tinyint
ending up with these four files (including modifications):
migrations/xxxxxxxxxxxxx1-create-userstatus.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Userstatus", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.TINYINT,
},
name: {
type: Sequelize.STRING,
},
comment: {
type: Sequelize.STRING,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Userstatus");
},
};
migrations/xxxxxxxxxxxxx2-create-users.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.BIGINT,
},
username: {
type: Sequelize.STRING,
},
pwdhash: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
statusId: {
type: Sequelize.TINYINT,
references: {
model: "Userstatus",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "SET DEFAULT",
},
statusUntil: {
allowNull: false,
type: Sequelize.DATE,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Users");
},
};
models/userstatus.js (runtime, not relevant for the migration)
models/user.js (runtime, not relevant for the migration)
Executing the command:
$ sequelize db:migrate
Sequelize CLI [Node: 14.17.0, CLI: 6.2.0, ORM: 6.9.0]
Loaded configuration file "db\config\config.js".
Using environment "development".
== xxxxxxxxxxxxx1-create-userstatus: migrating =======
== xxxxxxxxxxxxx1-create-userstatus: migrated (0.101s)
== xxxxxxxxxxxxx2-create-users: migrating =======
ERROR: Can't create table myDb.Users (errno: 150 "Foreign key constraint is incorrectly formed")
Adding detailed logging to config shows that this is a MySQL error caused by onDelete (not accepting SET DEFAULT):
Can't create foreign key with ON DELETE SET DEFAULT
Somewhere down the line, I also had the exact same error response caused by a type mismatch (TINYINT vs. INTEGER):
migrations/xxxxxxxxxxxxx1-create-userstatus.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Userstatus", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.TINYINT,
},
:
};
migrations/xxxxxxxxxxxxx2-create-users.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
:
statusId: {
type: Sequelize.INTEGER,
references: {
model: "Userstatus",
key: "id",
},
onUpdate: "CASCADE",
},
:
};
Lesson learned: When migration errors occur, focus on DB and SQL first - make sure that the resulting SQL actually works when executed manually.
This is where I personally ended up spending more time than need be - I tried to 'fix' the Sequelize migration before I knew what was going on ... the real fix was turning on logging in order to acces the SQL directly.
UPDATE: Defining Foreign Key Constraints directly where table is created, spawns another issue to take care of - in order to undo such migration, the 'undo'-part of the migration needs to hold an extra queryInterface.removeConstraint(....) command, introducing the need to use Promise.all([...]) (example here)
Maybe it's just a matter of taste... I now choose to define Foreign Key Constraints in separate migration files like this:
migrations/xxxxxxxxxxxxxx-fk-userstatus-associate.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
return await queryInterface.addConstraint("Users", {
type: "FOREIGN KEY",
fields: ["statusId"], // field name of the foreign key
name: "fk_users_statusId",
references: {
table: "Userstatus", // Target model
field: "id", // key in Target model
},
onUpdate: "CASCADE",
onDelete: "RESTRICT",
});
},
down: async (queryInterface, Sequelize) => {
return await queryInterface.removeConstraint(
"Users", // Source model
"fk_users_statusId" // key to remove
);
},
};
Wins:
Smooth undo handling: $ sequelize db:migrate:undo
I get to name my constraints as I please

Running Sql migrations throws syntax error

I have created my first migration using sequelize-cli, now when I enter npx sequelize-cli db:migrate to run migration and create table in DB, I get error
I look into documentation could not find how and what should go into migration file.
Error
ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NUMBER, `otp` INTEGER, `otp_expiration_date` DATETIME, `createdAt` DATETIME NOT ' at line 1
My migration File
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
phone_number: {
type: Sequelize.NUMBER
},
otp: {
type: Sequelize.INTEGER(4)
},
otp_expiration_date: {
type: Sequelize.DATE
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
})
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users');
}
};
My User Model:
const moment = require('moment');
'use strict';
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
};
User.init({
name: {
type: DataTypes.STRING,
allowNull: false
},
phone_number: {
type: DataTypes.NUMBER
},
otp: {
type: DataTypes.INTEGER(4)
},
otp_expiration_date: {
type: DataTypes.DATE,
set(value) {
// convert regular Date to moment Date
value = moment(value).add(5, 'minutes');
this.setDataValue('otp_expiration_date', value);
}
},
is_otp_expired: {
type: DataTypes.VIRTUAL,
get() {
// otp_expiration_date < current date
return this.getDataValue(otp_expiration_date).isAfter(moment()) ? true : false
}
}
}, {
sequelize,
modelName: 'User',
});
return User;
};
I have tried
changing datatypes
moving getters into migration
removing createdAt
[SOLVED]
Problem was with DataType of phone_number , there is no Sequelize.NUMBER type per Sequelize docs
I confused Sequelize DataTypes with MySql DataTypes
phone_number: {
type: DataTypes.NUMBER <---- bug
}
Solution
phone_number: {
type: DataTypes.INTEGER <---- solution
}

How to write object id reference of mongoose using Sequelize for MySql in Node.js

I am trying to write the category model for MySQL using Sequelize taking reference from this code of mongoose model in node(shared below) . I have gone through some articles but going through some confusion. Please help.
const mongoose = require("mongoose");
const categorySchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
);
module.exports = mongoose.model("Category", categorySchema);
Here I've tried my code in using Sequelize for MySql but got stuck in createdBy block. Need Help!!!
module.exports = (sequelize, Sequelize) => {
const Category = sequelize.define("categories", {
name:{
type: Sequelize.STRING,
required: true,
trim: true,
},
createdBy:{
}
});
return Category;
};
The createdBy value from Mongo will be an ObjectID, which you can save as a string in MySQL, so use a DataTypes.STRING type in the definition, then set it on insert/update/etc from your Mongo lookup. The Sequelize variable in your example should be DataTypes as well.
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('categories', {
name: {
type: DataTypes.STRING,
required: true,
trim: true,
},
createdBy: {
type: DataTypes.STRING,
// any other settings
},
});
return Category;
};
// get the value from mongoose
const createdBy = ...;
// set it on the create for your model to insert.
await Category.create({
name: 'Some Name',
createdBy,
});

Able to make migration with associations but not able to query with associations (association not found)

I've built a database with a couple tables that are associated with each other: Auction and Bids. Each Auction should have many Bids and each Bid should have only one Auction. I made a migration fine to add foreignKeys but when I try to look up a certain Bid on an Auction I receive a SequelizeEagerLoadingError: Bids is not associated to Auctions! error.
migrations file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'Auctions', // name of target model
'BidId',
{
type: Sequelize.INTEGER,
references:{
model: "bids",
key: "bid_id",
},
},
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Auction');
}
};
bids.js
const Sequelize = require('sequelize');
// const resolver = require('graphql-sequelize');
const sequelize = require('../config/database');
const Auction = require('./Auction');
const tableName = 'bids';
const Bids = sequelize.define('Bids', {
bid_id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
createdAt: {
type: Sequelize.DATE,
// defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
},
amount: {
type: Sequelize.INTEGER
},
bid_amount: {
type:Sequelize.STRING
},
bid_no: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
},
},
{tableName})
Bids.associate = () => {
Bids.hasOne(Auction, {foreignKey:"BidId"})
};
auction.js
const Sequelize = require('sequelize');
const sequelize = require('../config/database');
const tableName = 'Auctions';
const Auction = sequelize.define('Auctions', {
auc_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4, // generate the UUID automatically
primaryKey: true,
},
features: {
type: Sequelize.JSONB,
},
bid_amount: {
type:Sequelize.STRING
},
BidId: {
type: Sequelize.UUID,
}
}, { tableName });
Auction.hasMany(Bids, {foreignKey: 'BidId'})
module.exports = Auction
query.js
const findBidOnAuction = () => {Auction.findOne({where:{BidId:2}, include:[{model:Bids}]}).then(data => console.log("result", data))}
How do I properly associate these tables?
edit: Also on pgAdmin I can that the relationship exists, BidId is a foreignKey on Auction linked to bid_id on Bids
I do the following for add a foreignKey on migrations:
1.I create a migration for the parent model with a ref function.
const TABLE_NAME = 'parent';
function up(queryInterface, Sequelize) {
return queryInterface.createTable(
TABLE_NAME, //Bid model
{
//attributes
},
);
}
function down(queryInterface, Sequelize) {
return queryInterface.dropTable(TABLE_NAME);
}
function ref(Sequelize) {
return {type: Sequelize.STRING(2), references: {model: TABLE_NAME, key: 'id'}}; // 'id' here is your parent (Bids) primary key
}
module.exports = {up, down, ref};
2.On the child model where you are going to add the reference, you import the ref function and added like this, so your migration actually knows which model are you refering:
const {ref: REF_Model} = require('name-of-your-migration-file'); //import model
async function up(queryInterface, Sequelize){
await queryInterface.addColumn('Auctions', 'BidId', {...REF_Model(Sequelize), allowNull: true});
}
async function down(queryInterface){
await queryInterface.removeColumn('Auctions', 'BidId');
}
module.exports = {up, down};

Creating Primary and Foreign Key relations in Sequelize

I have 2 models Project model and Task model defined in sequelize as shown below
import { INTEGER, STRING, DATE } from 'sequelize';
import sequelize from '../sequelize';
import Task from './task.model'
const ProjectModel = sequelize.define('project', {
project_id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
phabricator_project_id: {
type: STRING,
allowNull: false
},
name: {
type: STRING
},
description: {
type: STRING
},
start_date: {
type: STRING,
},
end_date: {
type: STRING
}
},
{
timestamps: false
}
);
export default ProjectModel;
and the task model
import { INTEGER, STRING, DATE } from 'sequelize';
import sequelize from '../sequelize';
const TaskModel = sequelize.define('task', {
task_id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: STRING
},
status: {
type: STRING
},
priority: {
type: STRING
},
description: {
type: STRING
},
tool_project_id: {
type: STRING
},
date_modified: {
type: STRING
}
},
{
timestamps: false
}
);
export default TaskModel;
What I want to achieve is to create a relation between tool_project_id in TaskModel and phabricator_project_id in ProjectModel (they are same values only diff column names are given) and write a query for a GET request which outputs the data in form shown below
{ {project1Details,TaskDetails-->{task1, task2, task3},
{project2Details,TaskDetails-->{task4, task5, task6},
{project3Details,TaskDetails-->{task7, task8, task9},
{project4Details,TaskDetails-->{task10, task11, task12} }
All the database design has been done accordingly and another file is called to create all these databases. This is written in typescript and I tried this as a GET method
listByProjects(req, res) {
TaskModel.belongsTo(ProjectModel, { as: 'task' , foreignKey: 'tool_project_id'});
ProjectModel.findAll({
include:[{model:TaskModel}],
where:{status:'open'}
}).then(function(projects) {
res.json(projects);
});
}
Here in this method I define the relation and try to list all 'open' tasks and send them back as response but I am getting the error
Unhandled rejection Error: task is not associated to project!
ANY HELP TO THIS PROBLEM WOULD BE WONDERFULL
The answer to this question is that when creating the table we should create the relation and then create the table such as
Create the relation also the name of the key should be same so as to create relation.
TaskModel.belongsTo(ProjectModel, {foreignKey: 'project_id' });
ProjectModel.hasMany(TaskModel, { foreignKey: 'project_id' });
Then create the table project and then tasks
ProjectModel.sync({ force: false }).then(function () {
console.log('Project table created');
TaskModel.sync({ force: false }).then(function () {
console.log('Task table created');
});
});
then in the API method, you are invoking just include the model which you want to provide to get the required data.
ProjectModel.findAll({
include: [{
model: TimeSheetModel,
where: {
status: "ACTIVE"
},
}],
}).then(function (projects) {
const responseData = {
'status': 1,
'message': 'List successfull.',
'projects': projects,
};
res.json(responseData);
}).catch(error => {
const responseData = {
'status': 1,
'message': error.message,
'projects': [],
};
res.json(responseData);
})
This uses nodemon and sequilize to manage node and relations of the table respectively