Sequelize - how to define a trigger in a model? - mysql

I am currently working with sequelize on RDS Aurora DB and I need to track which records in which tables were deleted - for this, I created a new table dbLog. Now I need to add a trigger to the database which saves the id of the record into dbLog table whenever that record in table t1 gets deleted. Basically, I need to cover two scenarios for already deployed databases and those who dont yet exist.
Database already exists. This is easy since I can create the trigger by raw query like this
CREATE TRIGGER trigger AFTER DELETE ON t1 FOR EACH ROW
INSERT INTO dbLog ( id, tableName, status, updatedAt )
VALUES (OLD.id, 't1', 'D', NOW())`
Database doesn't exist. This is problematic since it gets created by initiation of the model and then sequelize.sync(). So I cant just call a raw query, instead, I need to define this trigger in the model for t1 table. This is how I initiate the table (simplified)
t1.init(
{
id: {
type: new DataTypes.BIGINT,
autoIncrement: true,
primaryKey: true
},
name: {
type: new DataTypes.STRING,
allowNull: false
}
},
{
sequelize,
tableName: 't1',
}
);
The problem is that I dont know how to create that trigger. I tried putting something like this into the attributes section of t1.Init. But there is some problem, when I check the database for triggers, none was created. How can I fix it? And are even triggers created by this way 1:1 equivalent of triggers created by raw query? Thanks a lot.
hooks: {
afterDestroy: function(t1) {
DbLog.create({
id: t1.id,
tableName: 't1',
status: 'D',
updatedAt: '2015-10-02'
})
}
}

You should create triggers in a DB manually by executing a raw SQL query. If you are using migrations then just create a migration with a trigger creation (also a raw SQL query).

Related

ORM: Sequelize: Add Not Equal Constraint for Two Columns

I have a following table. I don't want user to follow himself so I want to add a CHECK constraint.
For example, if this is inserted, I want database to throw an error.
{
userID: 1,
followingID: 1,
}
I can check with Javascript if userID and followingID are equal but I want database to check it.
My MYSQL version is 8.0.17 so I think it is possible to create this constraint with SQL. How can I add this constraint with Sequelize?
There is two solution
1. Using Model wise validation and adding check constraint in database manually:
Model wise validation:
const FollowingModel = sequelize.define("following", {
userId: {
type: Sequelize.INTEGER,
// .. other configuration like `allowNull`
},
followingId: {
type: Sequelize.INTEGER,
// .. other configuration like `allowNull`
}
}, {
validate: {
userShouldNotFollowSelf : function() {
if(this.userId === this.followingId) {
throw Error("User should not follow self") // Use any custom error class if your application has such class.
}
}
}
}
Beware this will allow you create entry in database which does not maintain this constraint.
It is just ORM's application layer check that, this application won't allow any entry where userId and followingId is not same.
Mysql database layer check constraint.
CREATE TABLE `following`
(
`userId` INT NOT NULL,
`followingId` INT NOT NULL,
CONSTRAINT `no_self_following` CHECK (`userId` <> `followingId`)
-- other properties and foreign key constraints.
);
It will ensure that, no such entry inserted where userId and followingId is same.
2. Declaring constraint in sequelize query interface.
This require to declare your model using query interfaces addConstraint as follows
sequelize.getQueryInterface().addConstraint("following", ['userId'], {
type: 'check',
name: "no_self_following"
where: {
userId: {
[Sequelize.Op.ne]: Sequelize.col("followingId")
}
}
});
Run this while all database model is been synced correctly. It will add database level constraint.
Which one to use ?
Approach #1 is more efficient. It is checking within the application without going into the database call, Makes your database less busy.

How do you insert / find rows related by foreign keys from different tables using Sequelize?

I think I've done enough research on this subject and I've only got a headache.
Here is what I have done and understood: I have restructured my MySQL database so that I will keep my user's data in different tables, I am using foreign keys. Until now I only concluded that foreign keys are only used for consistency and control and they do not automatize or do anything else (for example, to insert data about the same user in two tables I need to use two separate insert statements and the foreign key will not help to make this different or automatic in some way).
Fine. Here is what I want to do: I want to use Sequelize to insert, update and retrieve data altogether from all the related tables at once and I have absolutely no idea on how to do that. For example, if a user registers, I want to be able to insert the data in the table "A" containing some user information and in the same task insert in the table B some other data (like the user's settings in the dedicated table or whatever). Same with retrievals, I want to be able to get an object (or array) with all the related data from different tables fitting in the criteria I want to find by.
Sequelize documentation covers the things in a way that every thing depends on the previous one, and Sequelize is pretty bloated with a lot of stuff I do not need. I do not want to use .sync(). I do not want to use migrations. I have the structure of my database created already and I want Sequelize to attach to it.
Is it possible insert and retrieve several rows related at the same time and getting / using a single Sequelize command / object? How?
Again, by "related data" I mean data "linked" by sharing the same foreign key.
Is it possible insert and retrieve several rows related at the same
time and getting / using a single Sequelize command / object? How?
Yes. What you need is eager loading.
Look at the following example
const User = sequelize.define('user', {
username: Sequelize.STRING,
});
const Address = sequelize.define('add', {
address: Sequelize.STRING,
});
const Designation = sequelize.define('designation', {
designation: Sequelize.STRING,
});
User.hasOne(Address);
User.hasMany(Designation);
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test123',
add: {
address: 'this is dummy address'
},
designations: [
{ designation: 'designation1' },
{ designation: 'designation2' },
],
}, { include: [Address, Designation] }))
.then(user => {
User.findAll({
include: [Address, Designation],
}).then((result) => {
console.log(result);
});
});
In console.log, you will get all the data with all its associated models that you want to include in the query

Defining Sequelize model for existing MySQL database

Please advise on the following issue. I'd like to use Sequelize to work with existing database.
Stack used:
- Node.js v6.10.3
- MySQL 2nd Gen 5.7 running on Google Cloud
- Sequelize 4.7.5
MySQL contains 1 table - 'test_table' with columns 'id', 'content', 'status' and holds some data.
I want to define testTable model which would "explain" my MySQL 'test_table' for sequelize, so I run:
const connection = new Sequelize("dbName", "user", "pass", {
host: xxx.xxx.xxx.xxx,
dialect: mysql
});
const testTable = connection.define('test_table', {
id: {type: Sequelize.INTEGER,
allowNull: false, unique: true,
primaryKey: true, autoIncrement:
true},
content: {type: Sequelize.STRING},
status: {type: Sequelize.STRING},
});
First surprise I get is that from this moment onwards Node completely ignores existence of 'test_table' and creates its 'analogue' (???).
C:\Users\dmitri\Desktop\app>node query.js
Executing (default): CREATE TABLE IF NOT EXISTS `test_tables` (`id` INTEGER NOT NULL auto_increment UNIQUE , `content` VARCHAR(255), `status` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DAT
ETIME NOT NULL, UNIQUE `test_tables_id_unique` (`id`), PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SELECT `id`, `content`, `status`, `createdAt`, `updatedAt` FROM `test_tables` AS `test_table`;
Executing (default): SHOW INDEX FROM `test_tables`
Now MySQL contains two tables:
- 'test_table' (singular)
- 'test_tables' (plural) identical to "test_table" but empty.
I am trying to SELECTid,contentFROMtest_table` by executing the following Sequelize code:
testTable
.findAll({attributes: ['id', 'content']})
.then(result=>{
console.log(result)
})
Instead of returning contents of 'test_table', node keeps ignoring it:
C:\Users\dmitri\Desktop\app>node query.js
Executing (default): SELECT id, content FROM test_tables AS test_table;
and returns [].
What may be the reason it voluntarily jumps to query 'test_tables' instead of 'test_table'? Am I completely missing something here?
Many thanks!
You should have specified the table name in the options. Delete this new table, add the table name option and re-lift the app.
connection.define('test_table', {
//.... your fields
}, {
tableName: 'test_table'
});
On a side note, I like to have my models named with camelcase. TestTable. Same goes for connection.define('TestTable'...
EDIT:
As your comments I clarify a couple of things:
You can/have to specify the table name when defining the model. Look at my example. Specifying the tableName property guarantees the pluralization doesnt screw you over.
You shouldnt do sequelize.sync() (unless you know what it does and you want it). Otherwise you might get problems (like sequelize creating another table because it cant find the one it looks for).
Defining the models is something you have to do if you want to use sequelize. Otherwise it doesnt make sense and you should use just mysql2. You have to define the models to match the tables in your database.

sequelize returns null for column 'id' in the dataValues of a query result.

I am using the sequelize in combination with my nodejs typescript application and am facing the following issue. I created a class called OrmMapper which is responsible for creating my tables. I created a table called "PCS" and initialized it with the following code:
PCS.init({
id: {
primaryKey : true,
autoIncrement: true,
type: DataTypes.INTEGER
},
owner : DataTypes.INTEGER,
created: DataTypes.DATE,
year : DataTypes.STRING
}, {sequelize});
The generated DDL is fine and the problem occurs whenever I run this piece of code:
handleGetByOwner(request: TrackerRequest, res:Response):void{
this.pcsModel.findAll({where:{owner: request.queryParams.ownerId}}).then((dataPackage:any)=>{
let PCSS = [];
console.log(dataPackage);
for(let pcs of dataPackage){
PCSS.push(pcs.dataValues);
}
res.status(200).send(new TrackerResponse({success: true, payload: PCSS, timeStamp: new Date()}));
});
}
which generates the following SQL:
SELECT `id`, `owner`, `created`, `year`, `createdAt`, `updatedAt` FROM `PCs` AS `PCS` WHERE `PCS`.`owner` = '1';
When I look in my database, all the data inserted is correct, when i execute the above SQL statement everything works. However when I run the server code above the result of my query is:
All of the datavalues are correct, expect for id which is null.

Sequelize transaction executes a rollback but database not

Here is a snapshot of the database Tables and constraints in PostgreSQL:
CREATE TABLE garage (
garage_id integer NOT NULL,
garage_name text,
garage_description text
);
CREATE TABLE auto (
auto_id serial PRIMARY KEY,
auto_name text,
auto_description text,
auto_price numeric(20,2),
auto_category text,
garage_id integer
);
ALTER TABLE ONLY auto
ADD CONSTRAINT auto_garage_id_fkey FOREIGN KEY (gerage_id)
REFERENCES gerage(gerage_id);
Here I am defining the database objects in nodejs using Sequelize:
var Auto = sequelize.define('auto', {
auto_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
auto_name: Sequelize.STRING,
auto_description: Sequelize.STRING,
auto_price: Sequelize.NUMERIC,
auto_category: Sequelize.STRING,
garage_id: Sequelize.INTEGER
}, {freezeTableName: true,
tableName: "auto",
timestamps: false,
paranoid: false,
underscored: true});
function createAutos(auto_1,auto_2){
return sequelize.transaction().then(function(t){
return Auto.create(auto_1,
{fields: ['auto_name', 'auto_description', 'auto_price', 'auto_category', 'garage_id']},
{transaction: t}
).then(function(){
return Auto.create(auto_2,
{fields: ['auto_name', 'auto_description', 'auto_price', 'auto_category', 'garage_id']},
{transaction: t});
}).then(function(){
t.commit();
}).catch(function(err){
t.rollback();
});
});
}
Here I am executing the following method to test the transactional createAutos():
createAutos({
"auto_name": 'bmw',
"auto_description": 'sport',
"auto_price":4.95,
"auto_category": 'luxes',
"garage_id": 1 // Exists in the database
},{
"auto_name": 'SSSS',
"auto_description": 'sport',
"auto_price":4.95,
"auto_category": 'luxes',
"garage_id": 200 // Doesn't exist in the database.
});
When executed, I can see the following output log on the console:
Executing (bf8cb998-657b-49b7-b29e-957bcf770b40): START TRANSACTION;
Executing (bf8cb998-657b-49b7-b29e-957bcf770b40): SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Executing (bf8cb998-657b-49b7-b29e-957bcf770b40): SET autocommit = 1;
Executing (default): INSERT INTO "auto" ("auto_name","auto_description","auto_price","auto_category","garage_id") VALUES ('bmw','sport',4.95,'luxes',1) RETURNING *;
Executing (default): INSERT INTO "auto" ("auto_name","auto_description","auto_price","auto_category","garage_id") VALUES ('SSSS','sport',4.95,'luxes',200) RETURNING *;
Executing (bf8cb998-657b-49b7-b29e-957bcf770b40): ROLLBACK;
But in the database the first auto "bmw" gets written despite the ROLLBACK of the whole transaction.
I tested the program with PostgreSQL 9.3.10, Ubuntu, MySQL 5.5.46, sequelize 3.13.0 & 3.0.0
Does anyone notice here a mistake in the code or is it a bug...?
In your output log, we can see two transactions, one bf8cb998-657b-49b7-b29e-957bcf770b40 and the other Default. The first one is rollbacked, the second one is not, and that's where you insert.
You try to pass the transaction to the Create function but it looks like Sequelize doesnt get it. Some releases ago, the syntax for transactions was changed, can you try to put the 'transaction: t' property in the second object and not the third ? Something like this:
Auto.create(auto_1,
{fields: ['auto_name', 'auto_description', 'auto_price', 'auto_category', 'garage_id'],
transaction: t}