i have table file and product, now its one to many, but i want to change realtionship to many-to-one. And i use mirgation in NestJS.
My code
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class RelationshipInImageCategory1671354171023 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE file_entity DROP CONSTRAINT ' +
'public.categories_entity.FK_23cf240bcad452131ad38135723');
await queryRunner.query('ALTER TABLE product_entity ADD CONSTRAINT ' +
'FK.file_entity FOREIGN KEY (file_entity.id) REFERENCES file_entity (id)');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE file_entity ADD CONSTRAINT ' +
'public.categories_entity.FK_23cf240bcad452131ad38135723 ' +
'FOREIGN KEY (product_entity.id) REFERENCES categories_entity (id)');
await queryRunner.query('ALTER TABLE product_entity DROP CONSTRAINT ' +
'FK.file_entity');
}
}
I have error {
query: 'ALTER TABLE file_entity DROP CONSTRAINT public.product_entity.FK_23cf240bcad452131ad38135723',
parameters: undefined,
driverError: error: syntax error at or near "."
Seems like work.
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE IF EXISTS product_entity DROP' +
' CONSTRAINT IF EXISTS "FK_e2c62cb8eaec0d909de8264e5ca"');
await queryRunner.query('ALTER TABLE file_entity ADD COLUMN IF NOT EXISTS product_id INT');
await queryRunner.query('ALTER TABLE file_entity ADD CONSTRAINT ' +
'FK_product FOREIGN KEY(product_id) REFERENCES product_entity(id)');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE product_entity ADD CONSTRAINT ' +
'FK_e2c62cb8eaec0d909de8264e5ca FOREING KEY(ImageId) REFERENCES file_entity(id)');
await queryRunner.query('ALTER TABLE IF EXISTS file_entity DROP CONSTRAINT ' +
'IF EXIST "FK_categories"');
}
Related
Table is created with:
exports.up = function (knex) {
return knex.schema.createTable('organisations_users', (table) => {
table.uuid('organisation_id').notNullable().references('id').inTable('organisations').onDelete('SET NULL').index();
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('organisations_users');
};
In another migration file I would like to alter the onDelete command to "CASCADE".
I tried (among other things):
exports.up = function (knex) {
return knex.schema.alterTable('organisations_users', (table) => {
table.uuid('organisation_id').alter().notNullable().references('id').inTable('organisations').onDelete('CASCADE').index();
});
};
But then knex states that the contstraint already exist (which is true, thats why i want to alter it)
What would be the command for this? I'm also fine with a knex.raw string.
Thank you
Solved it by:
exports.up = function (knex) {
return knex.schema.alterTable('organisations_users', async (table) => {
// First drop delete references
await knex.raw('ALTER TABLE organisations_users DROP CONSTRAINT IF EXISTS organisations_users_organisation_id_foreign')
// Add the correct delete command (was SET NULL, now CASCADE)
table.uuid('organisation_id').alter().notNullable().references('id').inTable('organisations').onDelete('CASCADE');
});
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('organisations_users');
};
I have an entity called TaskNotification where I have an enum field called type. I use enumName option to give a specific name TaskNotificationType to it inside the database.
import { Entity, Column } from 'typeorm';
export enum TaskNotificationType {
ASSIGNED
}
#Entity('taskNotifications')
export class TaskNotification {
#Column({
type: 'enum',
enum: TaskNotificationType,
enumName: 'TaskNotificationType',
default: TaskNotificationType.ASSIGNED,
})
type: TaskNotificationType;
/* Some more code */
}
When I create the new migration for this entity class, I get the following migration. This is correct and what is expected.
import {MigrationInterface, QueryRunner} from "typeorm";
export class addNotifications1620795716886 implements MigrationInterface {
name = 'addNotifications1620795716886'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "TaskNotificationType" AS ENUM('0')`);
await queryRunner.query(`CREATE TABLE "taskNotifications" ("id" character varying(21) NOT NULL, "senderID" character varying(21) NOT NULL, "taskID" character varying(21) NOT NULL, "type" "TaskNotificationType" NOT NULL DEFAULT '0', CONSTRAINT "PK_bf03149248aee7c64532028321e" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "notificationStatuses" ("id" character varying(21) NOT NULL, "receiverID" character varying(21) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "sentAt" TIMESTAMP WITH TIME ZONE, "readAt" TIMESTAMP WITH TIME ZONE, "taskNotificationID" character varying(21) NOT NULL, CONSTRAINT "PK_735fedb2f492dc91b0adf8233b0" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "taskNotifications" ADD CONSTRAINT "FK_9b92958e250c1f46393e0e88066" FOREIGN KEY ("taskID") REFERENCES "tasks"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "notificationStatuses" ADD CONSTRAINT "FK_14dbeaea4a320e7375cb22e7e7a" FOREIGN KEY ("taskNotificationID") REFERENCES "taskNotifications"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "notificationStatuses" DROP CONSTRAINT "FK_14dbeaea4a320e7375cb22e7e7a"`);
await queryRunner.query(`ALTER TABLE "taskNotifications" DROP CONSTRAINT "FK_9b92958e250c1f46393e0e88066"`);
await queryRunner.query(`DROP TABLE "notificationStatuses"`);
await queryRunner.query(`DROP TABLE "taskNotifications"`);
await queryRunner.query(`DROP TYPE "TaskNotificationType"`);
}
}
When I rebuild the app and run the migrations, the database gets updated as I wanted. So there is no issue upto now. 🍾
But, if I try to create another migration without changing any of the entities, I am getting another migration like below. Notice the name difference in the enum between up and down methods. (What is happening here? 🤒)
import {MigrationInterface, QueryRunner} from "typeorm";
export class addNotificationsDuplicate1620795984398 implements MigrationInterface {
name = 'addNotificationsDuplicate1620795984398'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "taskNotifications" DROP COLUMN "type"`);
await queryRunner.query(`ALTER TABLE "taskNotifications" ADD "type" "TaskNotificationType" NOT NULL DEFAULT '0'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "taskNotifications" DROP COLUMN "type"`);
await queryRunner.query(`ALTER TABLE "taskNotifications" ADD "type" "tasknotificationtype" NOT NULL DEFAULT '0'`);
}
}
If I didn't use enumName in column options, typeorm would assign a default name taskNotifications_type_enum.
Here's the migration for that:
import {MigrationInterface, QueryRunner} from "typeorm";
export class addNotifications1620796454176 implements MigrationInterface {
name = 'addNotifications1620796454176'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "taskNotifications_type_enum" AS ENUM('0')`);
await queryRunner.query(`CREATE TABLE "taskNotifications" ("id" character varying(21) NOT NULL, "senderID" character varying(21) NOT NULL, "taskID" character varying(21) NOT NULL, "type" "taskNotifications_type_enum" NOT NULL DEFAULT '0', CONSTRAINT "PK_bf03149248aee7c64532028321e" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "notificationStatuses" ("id" character varying(21) NOT NULL, "receiverID" character varying(21) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "sentAt" TIMESTAMP WITH TIME ZONE, "readAt" TIMESTAMP WITH TIME ZONE, "taskNotificationID" character varying(21) NOT NULL, CONSTRAINT "PK_735fedb2f492dc91b0adf8233b0" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "taskNotifications" ADD CONSTRAINT "FK_9b92958e250c1f46393e0e88066" FOREIGN KEY ("taskID") REFERENCES "tasks"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "notificationStatuses" ADD CONSTRAINT "FK_14dbeaea4a320e7375cb22e7e7a" FOREIGN KEY ("taskNotificationID") REFERENCES "taskNotifications"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "notificationStatuses" DROP CONSTRAINT "FK_14dbeaea4a320e7375cb22e7e7a"`);
await queryRunner.query(`ALTER TABLE "taskNotifications" DROP CONSTRAINT "FK_9b92958e250c1f46393e0e88066"`);
await queryRunner.query(`DROP TABLE "notificationStatuses"`);
await queryRunner.query(`DROP TABLE "taskNotifications"`);
await queryRunner.query(`DROP TYPE "taskNotifications_type_enum"`);
}
}
Then if I try to generate a migration without changing entities, it would not generate new migrations. (This is correct and expected behavior)
However, if I add enumName after that, it would not generate a new migration for the name change. It will throw the usual error we see when we try to run migrations without changing any entity. (Busted again 😩)
╰>>> npm run typeorm:novadelite migration:generate -- -n addNotificationsDuplicate
> novade-lite-backend#0.1.4 typeorm:novadelite /Users/eranga/Documents/Projects/Novade/NovadeLiteBackend
> ts-node ./node_modules/.bin/typeorm --config src/modules/Database/OrmConfigs/novadeLiteOrmConfig "migration:generate" "-n" "addNotificationsDuplicate"
No changes in database schema were found - cannot generate a migration. To create a new empty migration use "typeorm migration:create" command
What is wrong here and how can I give a specific name to my enum type using typeorm?
Any help is much appreciated! 🙏
(In the rare occasion of this being an issue in typeorm, I have already created an issue in Github)
This indeed was an issue in Typeorm and it was fixed by AlexMesser in this PR. 🙏
At the time of this writing the version of Typeorm is 0.2.32 and this fix will be released on the next release! 🎉🎉🎉
I used some boilerplate code (below) that creates a normalized tsvector _search column of all columns I specify (in searchObjects) that I'd like full-text search on.
For the most part, this is fine. I'm using this in conjunction with Sequelize, so my query looks like:
const articles = await Article.findAndCountAll({
where: {
[Sequelize.Op.and]: Sequelize.fn(
'article._search ## plainto_tsquery',
'english',
Sequelize.literal(':query')
),
[Sequelize.Op.and]: { status: STATUS_TYPE_ACTIVE }
},
replacements: { query: q }
});
Search index setup:
const vectorName = '_search';
const searchObjects = {
articles: ['headline', 'cleaned_body', 'summary'],
brands: ['name', 'cleaned_about'],
products: ['name', 'cleaned_description']
};
module.exports = {
up: async queryInterface =>
await queryInterface.sequelize.transaction(t =>
Promise.all(
Object.keys(searchObjects).map(table =>
queryInterface.sequelize
.query(
`
ALTER TABLE ${table} ADD COLUMN ${vectorName} TSVECTOR;
`,
{ transaction: t }
)
.then(() =>
queryInterface.sequelize.query(
`
UPDATE ${table} SET ${vectorName} = to_tsvector('english', ${searchObjects[
table
].join(" || ' ' || ")});
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
CREATE INDEX ${table}_search ON ${table} USING gin(${vectorName});
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
CREATE TRIGGER ${table}_vector_update
BEFORE INSERT OR UPDATE ON ${table}
FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(${vectorName}, 'pg_catalog.english', ${searchObjects[
table
].join(', ')});
`,
{ transaction: t }
)
)
.error(console.log)
)
)
),
down: async queryInterface =>
await queryInterface.sequelize.transaction(t =>
Promise.all(
Object.keys(searchObjects).map(table =>
queryInterface.sequelize
.query(
`
DROP TRIGGER ${table}_vector_update ON ${table};
`,
{ transaction: t }
)
.then(() =>
queryInterface.sequelize.query(
`
DROP INDEX ${table}_search;
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
ALTER TABLE ${table} DROP COLUMN ${vectorName};
`,
{ transaction: t }
)
)
)
)
)
};
The problem is that because the code concats both columns within each array of searchObjects, what is getting stored is a combined index of all columns in each array.
For example on the articles table: 'headline', 'cleaned_body', 'summary' are all part of that single generated _search vector.
Because of this, I can't really search by ONLY headline or ONLY cleaned_body, etc. I'd like to be able to search each column separately and also together.
The use case is in my search typeahead I only want to search on headline. But on my search results page, I want to search on all columns specified in searchObjects.
Can someone give me a hint on what I need to change? Should I create a new tsvector for each column?
If anyone is curious, here's how you can create a tsvector for each column:
try {
for (const table in searchObjects) {
for (const col of searchObjects[table]) {
await queryInterface.sequelize.query(
`ALTER TABLE ${table} ADD COLUMN ${col + vectorName} TSVECTOR;`,
{ transaction }
);
await queryInterface.sequelize.query(
`UPDATE ${table} SET ${col + vectorName} = to_tsvector('english', ${col});`,
{ transaction }
);
await queryInterface.sequelize.query(
`CREATE INDEX ${table}_${col}_search ON ${table} USING gin(${col +
vectorName});`,
{ transaction }
);
await queryInterface.sequelize.query(
`CREATE TRIGGER ${table}_${col}_vector_update
BEFORE INSERT OR UPDATE ON ${table}
FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(${col +
vectorName}, 'pg_catalog.english', ${col});`,
{ transaction }
);
}
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
I'm using the express generated template with postgresql and I have 2 rest route methods for creating consignments and tracking.
However i want tracking to be updated on each consignment insert, but i require the serial primary key to do it. So from the createCon function i require it to return the id after the insert, to use for the cid field in the createConTracking.
routes/index.js file
var db = require('../queries');
router.post('/api/cons', db.createCon);
router.post('/api/cons/:id/tracking', db.createConTracking);
queries.js
var promise = require('bluebird');
var options = {
promiseLib: promise
};
var pgp = require('pg-promise')(options);
var db = pgp(connectionString);
function createCon(req, res, next) {
var conid = parseInt(req.body.conid);
db.none('insert into consignments(conid, payterm,........)'+
'values($1, $2, ......)',
[conid, req.body.payterm,........])
.then(function () {
res.status(200)
.json({
status: 'success',
message: 'Inserted one con'
});
})
.catch(function (err) {
return next(err);
});
}
function createConTracking(req, res, next) {
var cid = parseInt(req.params.id);
var userid = req.user.email;
var conid = parseInt(req.body.conid);
db.none('insert into tracking(status, remarks, depot, userid, date, cid, conid)'+
'values($1, $2, $3, $4,$5, $6, $7)',
[req.body.status, req.body.remarks, req.body.depot, userid, req.body.date, cid, conid])
.then(function (data) {
res.status(200)
.json({
data: data,
status: 'success',
message: 'Updated Tracking'
});
})
.catch(function (err) {
return next(err);
});
}
DB
CREATE TABLE consignments (
ID SERIAL PRIMARY KEY,
conId INTEGER,
payTerm VARCHAR,
CREATE TABLE tracking (
ID SERIAL PRIMARY KEY,
status VARCHAR,
remarks VARCHAR,
cid INTEGER
);
I'm the author of pg-promise.
You should execute multiple queries within a task (method task) when not changing data, or transaction (method tx) when changing data. And in case of making two changes to the database, like in your example, it should be a transaction.
You would append RETURNING id to your first insert query and then use method one to indicate that you expect one row back.
function myRequestHandler(req, res, next) {
db.tx(async t => {
const id = await t.one('INSERT INTO consignments(...) VALUES(...) RETURNING id', [param1, etc], c => +c.id);
return t.none('INSERT INTO tracking(...) VALUES(...)', [id, etc]);
})
.then(() => {
res.status(200)
.json({
status: 'success',
message: 'Inserted a consignment + tracking'
});
})
.catch(error => {
return next(error);
});
}
In the example above we execute the two queries inside a transaction. And for the first query we use the third parameter for an easy return value transformation, plus conversion (in case it is a 64-bit like BIGSERIAL).
Simply add a RETURNING clause to your INSERT statement. This clause allows you to return data concerning the actual values in the inserted record.
insert into consignments(conid, payterm,........)
values($1, $2, ......)
returning id;
I am using the method below to change the column type from string to enum. Is there an alternative way to do this?
Is it possible to use it as a knex.raw to form such query?
CREATE TYPE type AS ENUM ('disabled', 'include', 'exclude');
ALTER TABLE test_table ALTER COLUMN test_col DROP DEFAULT;
ALTER TABLE test_table ALTER COLUMN test_col TYPE logic USING(test_col::type), ALTER COLUMN test_col SET DEFAULT 'disabled'::logic;
return schema
.table('offers', function (table) {
cols.forEach(function (column) {
table.renameColumn(column, column + '_old');
});
}).then(function () {
var schema = knex.schema;
return schema.table('offers', function (table) {
cols.forEach(function (column) {
table.enum(column, ['disabled', 'include', 'exclude']).defaultTo('disabled');
});
});
}).then(function () {
return knex.select('*').from('offers');
}).then(function (rows) {
return Promise.map(rows, function (row) {
var data = {};
cols.forEach(function (column) {
data[column] = row[column+'_old'];
}, data);
return knex('offers').where('id', '=', row.id).update(data);
})
}).then(function () {
var schema = knex.schema;
return schema.table('offers',function (table) {
cols.forEach(function (column) {
table.dropColumn(column+'_old');
});
});
});