operator does not exist: character varying = integer in sequelize node js - node.js

I am trying to connect two tables with sequelize, but the primary key and foreign key have different data types,
I am getting errors when I triggered the query. it is not possible to change the schema, it will affect the whole data.
Can you provide some possible solutions to fix this error?

The only way to join them in findAll/findOne calls is to use the on option in the include option:
const items = Items.findAll({
include: [{
model: ChildItem,
on: {
// assuming that childId is an integer and parentId is a string
childId: Sequelize.cast('parentId', 'integer')
}
}]
})

I stumbled on the same issue here is how i resolved it:
The cause of the issue is that Sequlize doing typecasting from integer to string when you create in one schema primaryKey as id and give it type of integer and in another schema you use it as secondaryKey with alias for instance userId so when you reference to connected schema from main schema you receive error operator does not exist: character varying = integer
Code examples (i am using Sequilze-typescript):
main schema where id is id
#Table({ tableName: 'users' })
export class User extends Model<User,> {
#Column({
type: DataType.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
})
id: number
#HasMany(() => Post)
posts:Post[]
}
secondary schema that uses User id as secondaryKey
#Table({ tableName: 'posts' })
export class Task extends Model<Task, TaskCreationAttributes> {
#Column({
type: DataType.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
})
id: number
#BelongsTo(() => User)
creator: User
#ForeignKey(() => User)
#Column({ type: DataType.INTEGER, allowNull: false })
userId: number
}
so here even-through we explicitly telling that our secondaryKey is number when we query User schema, Sequlize casts id -> userId, integer -> string
so in order to prevent the typecasting from id -> userId we can change
main schema(User) to
#Column({
type: DataType.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
})
userId: number
and secondary(Post) schema to:
#Column({
type: DataType.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
})
postId: number
#BelongsTo(() => User)
creator: User
#ForeignKey(() => User)
#Column({ type: DataType.INTEGER, allowNull: false })
userId: number
so no collision and typecasting will be done

Related

How to define array of objects in Sequelize.js in NestJS for Postgres?

How can I define an array of objects field in Sequelize.js model in NestJS for Postgres?
I need something like this
{
userId: { type: String, required: true},
products: [
{
productId: {
type: String
},
quantity: {
type: Number,
default: 1
}
}
]
}
I tried to translate the above code to NestJS using Sequelize.js, but using Postman I sent post requests and got an untyped object in the products array. And I don't understand how to solve this problem.
import {Column, DataType, Model, Table} from "sequelize-typescript";
export interface IProductItem {
productId: string
quantity: number
}
interface ICartsCreationAttrs {
userId: number
products: Array<IProductItem>
}
#Table({tableName: 'carts'})
export class CartsModel extends Model<CartsModel, ICartsCreationAttrs> {
#Column({type: DataType.INTEGER, unique: true, autoIncrement: true, primaryKey: true})
id: number
#Column({type: DataType.INTEGER, unique: true, allowNull: false})
userId: number
#Column({type: DataType.ARRAY(DataType.JSON), allowNull: false})
products: Array<IProductItem>
}
For sequelize doesn't needs a type definition in an array or JSON object. You can define only column types.
(JSONB allows only for Postgress)
products: { type: JSONB, required: true}

Creating a foreignKey in sequelize, that is not a integer association

I want to translate this psql table creation query to sequelize:
PSQL:
CREATE TABLE categories
(
id INTEGER NOT NULL PRIMARY KEY,
name CHARACTER VARYING(50) NOT NULL UNIQUE,
description CHARACTER VARYING(100) NOT NULL
);
CREATE TABLE posts
(
id INTEGER NOT NULL PRIMARY KEY,
title CHARACTER VARYING(50) NOT NULL,
content CHARACTER VARYING(100) NOT NULL,
from_category CHARACTER VARYING(50) NOT NULL,
CONSTRAINT fk_from_category
FOREIGN KEY(from_category)
REFERENCES categories(name)
)
Its a simple fk association, with varchar type.
I have read sequelize docs, but i still don't know how to change the relation from primary keys to varchar.
From what i read, this is what you can do with associations:
Post.belongsTo(Category, {
foreignKey: {
onDelete: ...,
onUpdate: ...,
validate: {...},
},
});
and thats all i could find about on youtube too..
I would be really happy if you can help me. I have spent too much time on this already, but i want it to work!
Follow this example using belongsTo:
class Category extends Model { }
Category.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
description: {
type: DataTypes.STRING,
allowNull: false
}
}, { sequelize, modelName: 'categories' });
class Post extends Model { }
Post.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.STRING,
allowNull: false
},
from_category: {
type: DataTypes.STRING,
allowNull: false
}
}, { sequelize, modelName: 'posts' });
Post.belongsTo(Category, {
foreignKey: 'from_category',
targetKey: 'name'
});
Read more about to understand with more details in the official docs.

TS2345 sequelize create function

I am creating a sequelize repository and I have an object which extends sequelize model.
I was able to do the following to save the model to the db.
repository.create(myModel) // myModel being an instance of MyModel which extends sequelize model
Now I am getting the following error in typescript:
Argument of type 'MyModel' is not assignable to parameter of type 'CreationAttributes<MyModel>'.
Type 'MyModel' is not assignable to type 'Omit<any, string>'.
Index signature for type 'number' is missing in type 'MyModel'.ts(2345)
I was doing some searching and a suggestion was to add to MyModel:
[key: number]: number;
When I do that the error is changed to the following:
Argument of type 'MyModel' is not assignable to parameter of type 'CreationAttributes<MyModel>'.
Type 'MyModel' is not assignable to type 'Omit<any, string>'.
Index signature for type 'symbol' is missing in type 'MyModel'.ts(2345)
I can get around the error by changing the call to create:
repository.create({...myModel})
Can anyone point me to documentation about ts(2345) what it is, perhaps how to ignore it. Or even solve the issue the correct way?
Using the spread operator, to me seems a bit messy, but if that is the correct solution that is fine.
Model definition:
import {
Column, DataType, ForeignKey, Model, Sequelize, Table,
} from 'sequelize-typescript';
import MediaResource from './media_resource';
import User from './users';
#Table({
tableName: 'videos',
timestamps: true,
version: true,
})
export default class Video extends Model {
#Column({
primaryKey: true,
autoIncrement: true,
type: DataType.INTEGER,
defaultValue: Sequelize.literal("nextval('videos_id_seq'::regclass)"),
})
id?: number;
#ForeignKey(() => MediaResource)
#Column({
field: 'media_resource_id',
allowNull: false,
type: DataType.INTEGER,
})
mediaResourceId?: number;
#Column({
allowNull: false,
type: DataType.STRING(191),
})
title?: string;
#Column({
allowNull: false,
type: DataType.STRING(2000),
})
url?: string;
#Column({
allowNull: true,
type: DataType.STRING(512),
})
description?: string;
#Column({
allowNull: false,
type: DataType.BOOLEAN,
})
is_3d?: boolean;
#Column({
allowNull: false,
type: DataType.BOOLEAN,
})
is_360?: boolean;
#ForeignKey(() => User)
#Column({
field: 'user_id',
allowNull: false,
type: DataType.INTEGER,
})
userId?: number;
#Column({
field: 'created_at',
allowNull: false,
type: DataType.DATE,
})
createdAt?: Date;
#Column({
field: 'updated_at',
allowNull: false,
type: DataType.DATE,
})
updatedAt?: Date;
}
Repository create signiture:
const db = DB.getInstance();
const videosRepository = db.getRepository(Video);
const transaction = await db.transaction();
try {
const saved = await videosRepository.create({ ...video }, { transaction });
await transaction.commit();
return saved;
} catch (err) {
await transaction.rollback();
if (err instanceof Error) logger.error(err.message);
throw new InternalServerError();
}
We have a sequelize singleton which is what db is, the video object is created from an express request body.
Also this used to work, I could add a ts-ignore comment and it will work.
sequelize: 6.15.1
sequelize-typescript: 2.1.2
typescript: 4.5.5
So I've come across 2 solutions to this problem, the first is as outlined above:
repository.create({ ...myModel })
And the second is:
repository.create(myModel as any)
This was my solution ...
export default class Video extends Model<InferAttributes<Video>, InferCreationAttributes<Video>> {
https://sequelize.org/master/manual/typescript.html

Sequelize create through association

I'm working on a create method for an association between two classes. The sequelize documentation indicates that this can be done in one step using includes
IntramuralAthlete.create(intramuralAthlete,{
include: [Person]
}).then((data,err)=>{
if(data)res.json(data);
else res.status(422).json(err);
}).catch(function(error) {
res.status(422).json({message: "failed to create athlete", error: error.message});
});
My model association looks like this
var Person = require('../models').person;
var IntramuralAthlete = require('../models').intramuralAthlete;
IntramuralAthlete.belongsTo(Person);
And the value of intramural athlete when I log it is
{
person:
{ firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
organizationId: 1
}
But I get the error notNull Violation: personId cannot be null. This error makes it sound like something is wrong with the way I'm indicating to Sequelize that I'm intending to create the personId in that same call.
Is there something wrong in the way I indicate to the create statement what associated tables to create with the IntramuralAthlete?
Thanks!
EDIT:
I have also tried with the following structure with the same result
{
Person: {
firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
organizationId: 1
}
My model is as follows:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('intramuralAthlete', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')
},
grade: {
type: DataTypes.STRING,
allowNull: true
},
age: {
type: DataTypes.INTEGER(11),
allowNull: true
},
school: {
type: DataTypes.STRING,
allowNull: true
},
notes: {
type: DataTypes.STRING,
allowNull: true
},
guardianId: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'contact',
key: 'id'
}
},
personId: {
type: DataTypes.INTEGER(11),
allowNull: false,
references: {
model: 'person',
key: 'id'
}
},
mobileAthleteId: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'mobileAthlete',
key: 'id'
}
},
organizationId: {
type: DataTypes.INTEGER(11),
allowNull: false,
references: {
model: 'organization',
key: 'id'
}
}
}, {
tableName: 'intramuralAthlete'
});
};
I suppose that your models are named Person and IntramuralAthlete (first arguments of sequelize.define method). In this case, when you create an association like yours, and do not define the as attribute, your create data object should look as follows
{
Person: {
firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
organizationId: 1
}
If you want to use person instead (just as in your code), you should define the association a little bit differently
IntramuralAthlete.belongsTo(Person, { as: 'person' });
Then, you would have to perform some changes in the create query in the include attribute of the options like this
IntramuralAthlete.create(data, {
include: [
{ model: Person, as: 'person' }
]
}).then((result) => {
// both instances should be created now
});
EDIT: Trick the save() method with empty value of personId
You can maintain the allowNull: false if you do something like that
{
person: {
// person data
},
personId: '', // whatever value - empty string, empty object etc.
grade: '12th',
organizationId: 1
}
EDIT 2: Disable validation when creating.
This case assumes that the validation is turned off. It seems like a bad idea to omit model validation, however there still maintains the database table level validation - defined in migrations, where it can still check if personId value was set
IntramuralAthlete.create(data, {
include: [
{ model: Person, as: 'person' }
],
validate: false
}).then((result) => {
// both instances should be created now
});
In this case the data object can be as in your example - without the personId attribute. We omit the model level validation which allows to pass null value, however if during the save() method it would still be null value - database level validation would throw an error.
first of all, when you associatea a model with belongsTo, sequelize will add automatically the target model primary key as a foreign key in the source model. in most of cases you don't need to define it by yourself, so in your case when you define IntramuralAthlete.belongsTo(Person) sequelize adds PersonId as a foreign key in IntramuralAthlete. your IntramuralAthlete model should looks like:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('intramuralAthlete', {
grade: {
type: DataTypes.STRING,
allowNull: true
},
age: {
type: DataTypes.INTEGER(11),
allowNull: true
},
school: {
type: DataTypes.STRING,
allowNull: true
},
notes: {
type: DataTypes.STRING,
allowNull: true
}
});
};
now you can create an intramuralAthlete like your code above. for example:
let data = {
Person: {
firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
notes: 'test notes'
}
IntramuralAthlete.create(data, {include: [Person]}).then((result) => {
// both instances should be created now
});
be carefull with the model name.
second I suppose that your IntramuralAthlete model has more than one belongsTo association. just you need to define them as the previous one association and sequelize will add their primary keys as foreign keys in the IntramuralAthlete model.
third, when you define a model, sequelize adds automatically an id datafield as a primary key and autoincrement and also adds createdAt and updatedAt datafields with a default CURRENT_TIMESTAMP value, so you don't need to define them in your model

Avoid created_at and updated_at being auto generated by sequelize

How do I define a model for which created_at and updated_at are provided rather than generated?
I'm importing data from somewhere that already has data for created_at and updated_at fields that I would like to preserve rather than generating whenever the object is created/updated by sequelize (our secondary store).
I've tried every likely permutation of model definition and options to get this to work and still sequelize overwrites my fields with it's own timestamps: {silent: true}, etc...
To be clear, the input data has createdAt and updatedAt and I'd like to use sequelize's bulkCreate(), etc such that input values provided are used and stored on the model as created_at and updated_at rather than generated by sequelize.
This is my current model definition:
const Lead = sequelize.define('lead', {
objectId: {
type: Sequelize.STRING,
field: 'objectId',
primaryKey: true,
allowNull: false
},
firstName: {
type: Sequelize.STRING,
field: 'first_name'
},
lastName: {
type: Sequelize.STRING,
field: 'last_name'
},
phoneNumber: {
type: Sequelize.STRING,
field: 'phone_number'
},
createdAt: {
type: Sequelize.DATE,
field: 'created_at',
},
updatedAt: {
type: Sequelize.DATE,
field: 'updated_at'
}
}, {
freezeTableName: true, // Model tableName will be the same as the model name
timestamps: false,
underscored: true
});
The option is
timestamps: false,
Where timestamps is written fully lowercase
Update:
From looking at the code of the bulkCreate() function, it looks that it always updates the timestamps, so, without a patch this is not possible right now

Resources