Sequelize migration changing column to be FK and then undo failing - node.js

I'm trying to create migration file to changeColumn to be string (from enum) and to reference other table.
const enumStatus = ['PENDING', 'SUCCESSFUL', 'FAILED', 'FAILED_LAST_VALUE_MISSING', 'FAILED_LAST_DATE_MISSING', 'FAILED_OLD_LAST_DATE'];
module.exports = {
up: async(queryInterface, Sequelize) => {
return Promise.all([
queryInterface.changeColumn('MeasurementTestResults', 'status', {
type: Sequelize.STRING,
allowNull: false
}), queryInterface.addConstraint('MeasurementTestResults', {
fields: ['status'],
type: 'foreign key',
name: 'statusFk',
references: {
table: 'MeasurementTestResultStatusEnums',
field: 'code'
},
onDelete: 'cascade',
onUpdate: 'cascade'
})
]);
},
down: async(queryInterface, Sequelize) => {
return Promise.all([
queryInterface.removeConstraint('MeasurementTestResults', 'statusFk'),
queryInterface.changeColumn('MeasurementTestResults', 'status', {
type: Sequelize.ENUM(...enumStatus),
allowNull: false
})
]);
}
};
db:migrate run flawlesly, but when i try to call db:migrate:undo it will execute commands out of order?
== 20201217194748-measurementTestResult_changeColumn_status: reverting =======
Executing (default): SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRA
INTS WHERE table_name='MeasurementTestResults' AND constraint_name = 'statusFk' AND TABLE_SCHEMA = 'iot_crcm_application_data1';
Executing (default): ALTER TABLE `MeasurementTestResults` CHANGE `status` `status` ENUM('PENDING', 'SUCCESSFUL', 'FAILED', 'FAILED_LAST_VALUE_MISSING', 'FAILED_LAST_DATE_MISSING', 'FAILED_OLD_LAST_DATE') NOT NULL;
Executing (default): ALTER TABLE `MeasurementTestResults`
DROP FOREIGN KEY `statusFk`;
ERROR: (conn=143438, no: 1832, SQLState: HY000) Cannot change column 'status': used in a foreign key constraint 'statusFk'
sql: ALTER TABLE `MeasurementTestResults` CHANGE `status` `status` ENUM('PENDING', 'SUCCESSFUL', 'FAILED', 'FAILED_LAST_VALUE_MISSING', 'FAILED_LAST_DATE_MISSING', 'FAILED_OLD_LAST_DATE') NOT NULL; - parameters:[]
then when i try to remove queryInterface.removeConstraint('MeasurementTestResults', 'statusFk'), this line from down script, it works, but no before i try it with error...

Don't try to execute structure changes that depends on each other in parallel using Promise.all. You better call them one by one:
await queryInterface.removeConstraint('MeasurementTestResults', 'statusFk')
await queryInterface.changeColumn('MeasurementTestResults', 'status', {
type: Sequelize.ENUM(...enumStatus),
allowNull: false
})
The same goes for up method too.

Related

Type Orm where relation is a specific value or relation is null

I want to return all user specific products and general products ( products without any have any mapping with users) in one query ..
I have tried
const query = this.productRepo
.createQueryBuilder('products')
.innerJoinAndSelect('products.users',
'users',
'users.id = 24 OR users.id IS NULL'
)....more
But it's not working the OR is working fine because i have tried 'users.id = 24 OR users.id = some other value which is working fine..
what I have done wrong here??
My relation
#ManyToMany(() => User, {
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
nullable: true,
})
#JoinTable({
name: 'product_user_mappings',
joinColumn: {
name: 'productId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'userId',
},
})
users: User[];
In general, I find it easiest to reason about SQL joins by thinking in terms of Venn Diagrams. What I gathered from your question is that we likely want something like a left inner join.
const query = this.productRepo
.createQueryBuilder('products')
.select()
.leftJoin('products.users', 'user')
.where('user.id = :id', {id: 24})
.orWhere('user.id IS NULL')
.getMany();
I have implemented this by referring this
const where = {
isActive: true,
volume: MoreThan(0),
priceValidity: MoreThan(new Date()),
};
const query = this.productRepo
.createQueryBuilder('products')
.select()
.leftJoin('products.users', 'user')
.where(where)
.andWhere(new Brackets(qb => {
qb.where('users.id = :userId', { userId: user.id })
qb.orWhere('users.id is null')
}))
.getMany()

Sequelize Update with Object

I have a code using sequelize to update one record and then return this updated record:
const companyId = req.params.id
const companyNewDetail = req.body
console.log("companyNewDetail", companyNewDetail)
await Company.update(companyNewDetail, {
where: {
companyId,
},
})
const company = await Company.findOne({
where: {
companyId,
},
})
res.status(200).send({ status: 0, data: company })
It is working but I have to seperate into two query statements. Is there a way I can mix them together? I want to pass the update parameter with a object rather than destructuring the object and assign to the instance one by one.
The returning option of Model.update still only supports PostgreSQL for sequelize v6.
For example:
import { sequelize } from '../../db';
import { Model, DataTypes, Sequelize } from 'sequelize';
// const sequelize = new Sequelize('sqlite::memory:');
class Company extends Model {}
Company.init(
{
companyId: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
name: DataTypes.STRING,
},
{ sequelize, tableName: 'companies' },
);
(async function test() {
try {
await sequelize.sync({ force: true });
// seed
await Company.create({ name: 'google' });
// test
const companyId = 1;
const companyNewDetail = { name: 'reddit' };
const result = await Company.update(companyNewDetail, {
where: {
companyId,
},
returning: true,
});
console.log(result);
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
If you use postgres dialect, you will get the following result:
Executing (default): DROP TABLE IF EXISTS "companies" CASCADE;
Executing (default): DROP TABLE IF EXISTS "companies" CASCADE;
Executing (default): CREATE TABLE IF NOT EXISTS "companies" ("companyId" SERIAL , "name" VARCHAR(255), PRIMARY KEY ("companyId"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'companies' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Executing (default): INSERT INTO "companies" ("companyId","name") VALUES (DEFAULT,$1) RETURNING *;
Executing (default): UPDATE "companies" SET "name"=$1 WHERE "companyId" = $2 RETURNING *
[
1,
[
Company {
dataValues: [Object],
_previousDataValues: [Object],
_changed: {},
_modelOptions: [Object],
_options: [Object],
isNewRecord: false
}
]
]
The first element of the result array is the number of affected rows for the updating operation. The second element is the sequelize model instances of updated rows.
For sqlite3, you will get:
Executing (default): DROP TABLE IF EXISTS `companies`;
Executing (default): DROP TABLE IF EXISTS `companies`;
Executing (default): CREATE TABLE IF NOT EXISTS `companies` (`companyId` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
Executing (default): PRAGMA INDEX_LIST(`companies`)
Executing (default): INSERT INTO `companies` (`companyId`,`name`,`createdAt`,`updatedAt`) VALUES (NULL,$1,$2,$3);
Executing (default): UPDATE `companies` SET `name`=$1,`updatedAt`=$2 WHERE `companyId` = $3
[ undefined, 1 ]
So, you need to do an additional query for other databases after updating the rows in the table.

Sequelize findAll() try to get unregistered field

i have problem with sequelize, i try to use findAll() to get data from my table, but there is an error which says unknown column jobs.userId in field list which i never register in my model or in my table
i have tried to use findAndCountAll() but still have same error, i also tried to remove all associatoin at get same result, the only work solution so far is put userId on exclude array
here's my controller
const jobData = await jobs.findAll({
attributes:{exclude:['updatedAt']},
include:[{
model:jobOwners,
as:'owner',
attributes:['id','profilePicture'],
include:[{
model:users,
as:'user',
attributes:['id','name'],
},{
model:districts,
as:'district',
attributes:['id','name']
}]
}]
});
here's my jobs model
'use strict';
module.exports = (sequelize, DataTypes) => {
const jobs = sequelize.define('jobs', {
jobOwnerId:{
type:DataTypes.INTEGER,
allowNull:false
},
caption: {
type:DataTypes.STRING,
allowNull:false
},
description: {
type:DataTypes.TEXT,
allowNull:false
},
districtId: {
type:DataTypes.INTEGER,
allowNull:false
},
address: {
type:DataTypes.TEXT,
allowNull:false
},
poster: {
type:DataTypes.TEXT,
allowNull:true
},
contactPerson: {
type:DataTypes.STRING,
allowNull:false
}
}, {});
jobs.associate = function(models) {
jobs.belongsTo(models.jobOwners,{as:'owner',foreignKey:'jobOwnerId'})
jobs.belongsTo(models.districts,{as:'districts', foreignKey:'districtId'})
};
return jobs;
};
here's my table
CREATE TABLE jobs (
id int(11) NOT NULL,
jobOwnerId int(11) NOT NULL,
caption varchar(255) NOT NULL,
description text,
districtId int(11) NOT NULL,
address text NOT NULL,
poster text,
contactPerson varchar(15) NOT NULL,
createdAt datetime NOT NULL,
updatedAt datetime NOT NULL
)
here's is the query result shown on console
SELECT jobs.id, jobs.jobOwnerId, jobs.caption, jobs.description, jobs.districtId, jobs.address,
jobs.poster, jobs.contactPerson, jobs.createdAt, jobs.userId, owner.id AS owner.id, owner.profilePicture AS owner.profilePicture,
owner->user.id AS owner.user.id, owner->user.name AS owner.user.name, owner->district.id
AS owner.district.id, owner->district.name AS owner.district.name FROM jobs AS jobs
LEFT OUTER JOIN jobOwners AS owner ON jobs.jobOwnerId = owner.id LEFT OUTER JOIN users AS owner->user
ON owner.userId = owner->user.id LEFT OUTER JOIN districts AS owner->district ON owner.districtId = owner->district.id;
there should be no jobs.userId
Problem solved, the problem not from jobs model or jobs controller, but it comes from users model, i create association hasMany jobs and it couses the problem..

Unable to execute the raw query in Sequelize migrations

I am trying to update my database using Sequelize migrations so I have tried to write a Sequelize migrations like this
'use strict';
module.exports = {
up: (queryInterface, Sequelize, migration) => {
queryInterface.addColumn('coaching_class_entries', 'bill_cycle', {
type: Sequelize.INTEGER(4),
allowNull: false,
defaultValue: '0',
field: 'bill_cycle',
after: 'fees'
})
.then(() => queryInterface.addColumn('coaching_classes', 'bill_plans', {
type: Sequelize.JSON,
allowNull: false,
defaultValue: 'NULL',
field: 'bill_plans',
after: 'bill_cycle'
}))
.then(() =>
migration.migrator.Sequelize.query('UPDATE coaching_classes SET bill_plans = JSON_ARRAY(JSON_OBJECT("cycle", bill_cycle, "fee", fees));'));
},
down: (queryInterface, Sequelize) => {
let migrations = [];
migrations.push(queryInterface.removeColumn('coaching_class_entries', 'bill_cycle'))
migrations.push(queryInterface.removeColumn('coaching_classes', 'bill_plans'))
return Promise.all(migrations);
}
};
But it is always giving me error in raw query line
Cannot read property 'Sequelize' of undefined
What is the correct syntax for this?
I found it myself only we have to use simple queryInterface.sequelize.query

Writing Migrations with Foreign Keys Using SequelizeJS

The Background
I'm building a project with SequelizeJS, a popular ORM for NodeJS. When designing a schema, there appears to be two tactics:
Create model code and use the .sync() function to automatically generate tables for your models.
Create model code and write manual migrations using QueryInterface and umzug.
My understanding is that #1 is better for rapid prototyping, but that #2 is a best practice for projects that are expected to evolve over time and where production data needs to be able to survive migrations.
This question pertains to tactic #2.
The Question(s)
My tables have relationships which must be reflected through foreign keys.
How do I create tables with foreign key relationships with one another through the Sequelize QueryInterface?
What columns and helper tables are required by Sequelize? For example, it appears that specific columns such as createdAt or updatedAt are expected.
How do I create tables with foreign key relationships with one another through the Sequelize QueryInterface?
The .createTable() method takes in a dictionary of columns. You can see the list of valid attributes in the documentation for .define(), specifically by looking at the [attributes.column.*] rows within the params table.
To create an attribute with a foreign key relationship, use the "references" and "referencesKey" fields:
For example, the following would create a users table, and a user_emails table which references the users table.
queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
}
}).then(function() {
queryInterface.createTable('user_emails', {
userId: {
type: Sequelize.INTEGER,
references: { model: 'users', key: 'id' }
}
})
});
What columns and helper tables are required by sequelize? For example, it appears that specific columns such as createdAt or updatedAt are expected.
It appears that a standard model will expect an id, updatedAt, and createdAt column for each table.
queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: Sequelize.DATE
},
updatedAt: {
type: Sequelize.DATE
}
}
If you set paranoid to true on your model, you also need a deletedAt timestamp.
I want to offer another more manual alternative because when using manual migrations and queryInterface I ran across the following problem: I had 2 files in the migration folder like so
migrations/create-project.js
migrations/create-projectType.js
because project had column projectTypeId it referenced projectType, which wasnt created yet due to the order of the files and this was causing an error.
I solved it by adding a foreign key constraint after creating both tables. In my case I decided to write it inside create-projectType.js:
queryInterface.createTable('project_type', {
// table attributes ...
})
.then(() => queryInterface.addConstraint('project', ['projectTypeId'], {
type: 'FOREIGN KEY',
name: 'FK_projectType_project', // useful if using queryInterface.removeConstraint
references: {
table: 'project_type',
field: 'id',
},
onDelete: 'no action',
onUpdate: 'no action',
}))
This is to create migration file for adding a column.
Here I want to add a column area_id in users table. Run command:
sequelize migration:create --name add-area_id-in-users
Once it gets executed creates a migration file timestamp-add-region_id-in-users in the migrations folder.
In the created migration file paste the below code:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.addColumn('users', 'region_id',
{
type: Sequelize.UUID,
references: {
model: 'regions',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
defaultValue: null, after: 'can_maintain_system'
}),
]);
},
down: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.removeColumn('users', 'region_id'),
]);
}
};
Here in the users table I am going to create a column named region_id along with type and relation/foreign key/references. That's it.
So first one goes as the below solution and the second question: you need not explicitly mention createdAt and updatedAt because they are generated by the Sequelize for you.
The solution is:
queryInterface.createTable('Images', {
//...
}).then(
return queryInterface.addConstraint('Images', ['postId'], {
type: 'foreign key',
name: 'custom_fkey_images',
references: { //Required field
table: 'Posts',
field: 'id'
},
onDelete: 'cascade',
onUpdate: 'cascade'
})
)

Resources