Alter string to enum - postgresql

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');
});
});
});

Related

How to alter postgres sql constraint with Knex?

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');
};

How to search on a single OR multiple columns with TSVECTOR and TSQUERY

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;
}

Simple Foreign Key Relation with Knex and Bookshelf.js

I'm attempting what should be a simple example of foreign key relations. regions and subregions are separate tables. There is a 1:Many relationship between regions:subregions, where the column region_fk on table subregions is mapped to an id from table regions. Each table also has a name column. The goal is to return the name of the region and the names of all subregions.
The query works fine without the {withRelated: ...} parameter, so something must be amiss with the connection between the models.
knex migration
exports.up = function(knex, Promise) {
return Promise.all([
knex.schema.createTable("regions", function(table) {
table.integer("id").primary();
table.string("name");
}),
knex.schema.createTable("subregions", function(table) {
table.integer("id").primary();
table.string("name");
table
.integer("region_fk")
.references("id")
.inTable("regions");
}),
])
}
bookshelf.config.js
var knex = require("knex")(require("./knexfile.js").development);
var bookshelf = require("bookshelf")(knex);
bookshelf.plugin("registry");
module.exports = bookshelf;
bookshelf models
// Region
const bookshelf = require("../bookshelf.config");
const Region = bookshelf.Model.extend({
tableName: "regions",
subregions: function() {
return this.hasMany("Subregion", "region_fk");
},
});
module.exports = bookshelf.model("Region", Region);
// Subregion
const bookshelf = require("../bookshelf.config");
const Subregion = bookshelf.Model.extend({
tableName: "subregions",
region: function() {
return this.belongsTo("Region", "region_fk");
},
});
module.exports = bookshelf.model("Subregion", Subregion);
Query
const Region = require("../../models/region");
export const getRegionWithId = (req, res) => {
new Region()
.where("id", req.params.id)
.fetch({ withRelated: ["subregions"], require: true })
.then(region => {
res.status(200).json(region);
})
.catch(err => {
res.status(404).json(err);
});
};

How to return the serial primary key from a db insert to use for another db insert

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;

BookshelfJs failing to bring in nested relationship on create

Let's say we have a Join table vehicle_inspections and another join table inspection_actions, as well as basic tables for actions, vehicles, andinspections`.
Lets say I desire the following DB entries:
vehicles
----------------------------
id make
----------------------------
1 Toyota
actions
-------------------------------
id description
-------------------------------
2 Check Tire Pressue
inspections
-------------------------------
id location date
-------------------------------
3 New York tomorrow
vehicle_inspections
--------------------------------
vehicle_id inspection_id
--------------------------------
1 3
inspection_actions
--------------------------------
inspection_id action_id
--------------------------------
3 2
and the following bookshelf classes
inspection_actions.js
(function () {
'use strict';
var Repository = require('../repository');
module.exports = Repository.Model.extend({
tableName: 'inspection_actions',
});
})();
vehicle_inspections.js
(function () {
'use strict';
var Repository = require('../repository');
module.exports = Repository.Model.extend({
tableName = 'vehicle_inspections',
inspection: function () {
return this.belongsTo(require('inspection'));
},
fetchOrCreate: function(vehicleId, inspectionId, options) {
var self = this;
return self.query(function (qb) {
qb.where({
vehicle_id: vehicleId,
inspection_id: inspectionId
});
)}.fetch(options || {}).then(function (model) {
if (!model) {
model.save({
vehicle_id: vehicleId,
inspection_id: inspectionId
});
return model;
};
}
};
});
inspection.js
...
module.exports = Repository.Model.extend(_.extend({
tableName: 'inspections',
actions: function () {
return this.hasMany(require('./inspection-action'));
}
}));
And a route:
new VehicleInspection().fetchOrCreate(req.params.vehicle_id, req.params.inspection_id, {withRelated: ['inspection.actions']})
.then(function (vehicleInspection) {
var inspection = vehicleInspection.related('inspection');
console.log( inspection);
console.log(inspection.related(actions);
})
The inspection console log prints out the correct inspection, however, irrelevantly of what is in the database the second console.log prints out an empty result
{ length: 0,
models: [],
_byId: {},
...
targetIdAttribute: 'id',
foreignKey: undefined,
parentId: undefined,
parentTableName: 'tasks',
parentIdAttribute: 'id',
parentFk: undefined } }
This "bad" behaviour only occurs the first time a projectTasks entry is being created. What appears to be happening is that the inspection_action table is not being populated through the nested withRelated. How could I get this working nested create working?
I'm not completely clear what you are trying to achieve, but here is how I would generally set things up. First I'd create a base model (assuming its saved as base.js), I think you are going to have some problems with circular dependencies, so using the Bookshelf registry plugin would be good:
var config = {
client: // whatever client you are using,
connection: // url to your database
};
var db = require('knex')(config);
var Bookshelf = require('bookshelf')(db);
var Base = Bookshelf.Model.extend({
// Put anything here that will be helpful for your use case
});
Bookshelf.plugin('registry');
Base.model = Bookshelf.model.bind(Bookshelf);
module.exports = Base;
Next create your Vehicle model:
require('inspection');
require('action');
var Base = require('base');
var Vehicle = Base.Model.extend({
tableName = 'vehicles',
inspections: function () {
return this.belongsToMany('Inspection',
'inspections_vehicles', 'vehicle_id', 'inspection_id');
},
actions: function() {
return this.belongsToMany('Action',
'actions_vehicles', 'vehicle_id', 'action_id');
}
};
module.exports = Base.model('Vehicle', Vehicle);
Then an inspection model:
require('vehicle');
var Base = require('base');
var Inspection = Base.Model.extend({
tableName = 'inspection',
vehicles: function () {
return this.belongsToMany('Vehicle',
'inspections_vehicles', 'inspection_id', 'vehicle_id');
}
};
module.exports = Base.model('Inspection', Inspection);
Finally an action model:
var Base = require('base');
var Action = Base.Model.extend({
tableName = 'actions',
};
module.exports = Base.model('Action', Action);
Now assuming that the database isn't already filled in with the data you supplied, we can populate it:
var Inspection = require('inspection');
var Vehicle = require('vehicle');
var Action = require('action');
var toyota;
var newYorkInspection
Vehicle.forge().save({name: 'Toyota'})
.then(function(vehicle) {
toyota = vehicle;
return Inspection.forge().save({location: 'New York', date: 'Tomorrow'});
}).then(function(inspection){
newYorkInspection = inspection;
return toyota.inspections().attach(newYorkInspection);
}).then(function() {
return Action.forge().save({description: 'Check Tire Pressure'});
}).then(function(tirePressureAction) {
return toyota.actions().attach(tirePressureAction);
});
Now I can fetch the toyota vehicle with the related actions and inspections:
var Vehicle = require('vehicle');
return Vehicle.forge({'name': 'Toyota'}).fetch({
withRelated: ['inspections', 'actions']
}).then(function(toyota){
var toyotaInspections = toyota.related('inspections');
var toyotaActions = toyota.related('actions');
});