sequelize: find in json array - node.js

In sequelize i have a model:
{
modelId: {
type: DataTypes.UUID ,
allowNull: false,
primaryKey: true,
defaultValue: DataTypes.UUIDV4
}
name: DataTypes.STRING(1024),
type: DataTypes.STRING,
obj: DataTypes.JSON
}
and in DB, my obj is an array like this:
[{
id: '123456',
text: 'test',
name 'xpto'
},{
id: '32554',
text: 'test2',
name 'xpte'
},{
id: '36201',
text: 'test3',
name 'xpta'
}]
i tried these:
btp.findAll({
where: {
obj:{
[Op.contains]:[{id: req.body.id}]
}
},
attributes: ['modelId','name','type','obj']
})
but does not work, return this error:
{"name": "SequelizeDatabaseError",
"parent": {
"name": "error",
"length": 128,
"severity": "ERROR",
"code": "42704",
"file": "parse_coerce.c",
"line": "1832",
"routine": "enforce_generic_type_consistency",
"sql":"....."}
so, i need to find in database all entries have in obj, id: '123456'
my question is the same than this:
https://github.com/sequelize/sequelize/issues/7349
but thats does not working for me, i need to return all entries that contains...
i'm using "sequelize": "4.28.6", and "pg-hstore": "^2.3.2",
can any one help?

I'm not familiar with the specific error you're getting, but one potential issue is that you're using a JSON column instead of JSONB. In Postgres, JSON columns just store the raw JSON text, and so don't support the containment operator (#>) which is needed for Sequelize's "contains".
All you need to do to fix this is change the column definition in the model to:
obj: DataTypes.JSONB
The only other issue I can think of would be req.body.id having an invalid value. I'd suggest verifying that it's actually getting a valid ID string.
Beyond these 2 potential issues, the query you wrote should work.

Related

Mongoose Find not filtering for object property inside array (MongoDB 4.4.1)

I've been trying to make a chat app and I use the following schema for messages:
const messageObject = {
sender: {type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
message: {type: String, require: true, min: 1, max: global.messageMaxLength},
time: Number,
seen: Boolean
}
const MessageSchema = mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
messages: [messageObject]
}, {versionKey: false}) ;
module.exports = mongoose.model('Messages', MessageSchema);
It takes in entries successfully. Example of entries:
"_id": "5fb3d971e6092d2da001bbad",
"messages": [
{
"_id": "5fc58bfe0e0ffb313c27fa0a",
"message": "Hello user",
"time": 1606781949959,
"seen": false
},
{
"_id": "5fc58c010e0ffb313c27fa0b",
"message": "Hello user",
"time": 1606781953442,
"seen": false
},
{
"_id": "5fc58c020e0ffb313c27fa0c",
"message": "Hello user",
"time": 1606781954137,
"seen": false
}
]
}
I want only the seen:false messages now, but when I try to code that in find, or aggregate, I get all the data, ie, the above records:
MessageModel.find({$and: [{_id: req.userData.userid}, {'messages.seen': false}]}).then(result => {
res.status(200).json({
message: "passed",
result,
});
})
It however works fine and returns [] if I give {"messages.seen": null}}, and 1 entry with seen: null will return an entire array.
Ive seen all forums there's no place where anybody has encountered this kind of error. Please help.
Thanks
Your message objects are nested inside a document and Mongo will only return either the full document or specific top level fields you have projected. Mongo will not filter out objects within a nested array (as is the case with your schema). So if any of the message objects within the array match the selector, the whole document itself passes the match and returns as a result.
Your options are to either:
Filter out the true/false messages within your code
Change your db structure so that you have a separate messages collection where each document is a single message. So in your example, the collection would look like:
{
"_id": "5fc58bfe0e0ffb313c27fa0a",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781949959,
"seen": false
},
{
"_id": "5fc58c010e0ffb313c27fa0b",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781953442,
"seen": false
},
{
"_id": "5fc58c020e0ffb313c27fa0c",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781954137,
"seen": false
}
And then you can query that collection with:
{ "parentId": "5fb3d971e6092d2da001bbad","seen": false}

How to add a field from associated model using Sequelize?

I'm making a web API using Node, Express, and Sequelize. I have models Users and Teams (shown below). Users has a teamId that references Teams.id, and there is an association between the two to reflect that.
User definition
const User = sequelize.define('User', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
displayName: {
type: DataTypes.STRING,
allowNull: false
},
teamId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Team',
key: 'id'
}
}
}
Team definition
const Team = sequelize.define('Team', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}
}
Association
User.belongsTo(Team, { as: 'team', foreignKey: 'teamId' });
Query
Users.findAll({
include: [
{
model: Team,
as: 'team'
}
]
});
As expected, this returns a list of User objects, with the added "team" property the respective Team objects embedded like so:
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"team": {
"id": 1,
"name": "My Awesome Team"
}
}
]
My goal is to return User objects, but instead of embedding the entire Team object under the team property, I'd like to add just the name of the team as the value of the property, like this:
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"team": "My Awesome Team"
}
]
Is there a way to accomplish this with Sequelize?
Well, through typing up my question I thought of better ways to search for the answer, and I think I found one right off the bat...
I found this StackOverflow question where the asker was doing what I am trying to do.
The solution is to use attributes.include and Sequelize.col() to include the attribute I want, and in my include options, use attributes: [] to hide the Team objects.
Going off the examples I included in the original post, this is what works for me, giving me nearly the exact output I wanted:
Users.findAll({
attributes:{
include: [[Sequelize.col(team.name), 'teamName']]
}
include: [
{
model: Team,
as: 'team',
attributes: []
}
]
});
Output
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"teamName": "My Awesome Team"
}
]
Since the association uses the alias "team", I had to name my property something different, and went with "teamName" which is perfectly fine and probably preferable since it is more descriptive.

sequelize: Not include model if where conditon of included model matches

I want to get all RecurringEvents that have no excludeDates for today.
I have following models:
Recurring events
const RecurringEvent = sequelize.definge('recurringEvent,{
id: {type: Sequelize.INTEGER, primaryKey:true},
title: Sequelize.STRING
});
And ExcludeDates with a foreign key recurringEventId
const ExcludeDate = sequelize.define('exclude_date',{
id: {type: Sequelize.INTEGER, primaryKey:true},
recurringEventId: Sequelize.INTEGER,
date: Sequelize.DATE
});
As a relationship i defined
RecurringEvent.hasMany(ExcludeDate, {foreignKey: 'recurringEventId'});
I can get all my RecurringEvents including the the excludeDates with
RecurringEvent.findAll({include:[{model:ExcludeDate}]});
That will give me an output like:
[
{
"id": 1,
"title": "Event1",
"exclude_dates": [
{
"id": 1,
"date": "2019-02-13",
"recurringEventId": 1,
},
{
"id": 2,
"date": "2019-02-14",
"recurringEventId": 1,
}
]
Now i would like to get the Recurring events but only if there is no exclude date for today.
so far i have tried
RecurringEvent.findAll({
include: [{
model: ExcludeDate,
where: {
date: {
[Op.ne]: moment().format('YYYY-MM-DD')
}
}
}]
})
But that only leaves out the ExcludeDate entry with the current Date like that :
[
{
"id": 1,
"title": "Event1",
"exclude_dates": [
{
"id": 2,
"date": "2019-02-14",
"recurringEventId": 1,
}
]
How can i exclude the whole RecurringEvent if and ExcludeDate for it is set for today?
Edit:
I also read in the docs
To move the where conditions from an included model from the ON condition to the top level WHERE you can use the '$nested.column$'
So i have tried this:
RecurringEvent.findAll({where:{
'$exclude_dates.date$':{
[Op.ne]: moment().format('YYYY-MM-DD')
}
},
include: [{model: ExcludeDate}]
})
But without any luck, i'm still getting RecurringEvents just without the one exclude date in the exclude_dates property
Try to add required: true, to your include model to make inner join instead of left join. e.g.
RecurringEvent.findAll({
include: [{
model: ExcludeDate,
required: true,
where: {
date: {
[Op.ne]: moment().format('YYYY-MM-DD')
}
}
}]
})

cant insert record with reference to another record

I have two models: platform and place (platform and place on platform)
platform
{
name: {
required: true,
unique: true,
type: String,
empty: false
},
description: {
required: false,
type: String,
empty: true
}
}
place
{
name: {
required: true,
type: String,
empty: false
},
platform: new Mongoose.Schema({
type: Mongoose.Schema.Types.ObjectId,
ref: PlatformSchema
})
}
The name of platform must be unique. First, I generate list of platforms. Then trying generate list of places on this platforms and receive error.
WriteError({
"code": 11000,
"index": 1,
"errmsg": "E11000 duplicate key error collection: test.places index: platform.ref.name_1 dup key: { : null }",
"op": {
"_id": "5b7ea477798f9c41f81c0234",
"name": "top",
"platform": {
"_id":"5b7ea41b878b4a41abcfc952"
}
}
})
Receive it until unique index for "name" field exists in platform schema.
I try insert place many ways:
PlaceRecord.insertMany([
{
name: "top",
platform: platformDocumentInstance
}
])
or
PlaceRecord.insertMany([
{
name: "top",
platform: platformDocumentInstance
}
])
or
PlaceRecord.insertMany([
{
name: "top",
platform: platformDocumentInstance
}
])
But result is same one. Note first record of place success inserted, but next record of place throw the exception. Please help.
It seems the document that you are inserting first has inserts null as platform.ref.name and when you insert second document your index platform.ref.name_1 fails because there is already a record with null.

Mongo/Mongoose Invalid atomic update value error

I am trying to write to write an update to a Mongo document using the Mongoose findOneAndUpdate function. Essentially, I have a document that has an array of another Schema in it, and when I attempt to append more of those schema type, I get the following error:
[Error: Invalid atomic update value for $__. Expected an object, received object]
I'm having a hard time figuring out what this error even means, much less what its source is.
The data I'm attempting to update is as follows:
{ section_id: 51e427ac550dabbb0900000d,
version_id: 7,
last_editor_id: 51ca0c4b5b0669307000000e,
changelog: 'Added modules via merge function.',
committed: true,
_id: 51e45c559b85903d0f00000a,
__v: 0,
modules:
[ { orderId: 0,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 1,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 2,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 3,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 4,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] },
{ orderId: 5,
type: 'test',
tags: [],
data: [],
images: [],
content: ["Some Content Here"] } ] }
The only difference is that when I retrieve it, there are three fewer modules, and I append some new ones to the array.
Would love to hear any thoughts, at least as to what the error means!
This is probably because the updated object is still a Mongoose object.
Try to convert it to a JS object before the findOneAndUpdate
object = object.toString()
And delete any potential ID attribute
delete object._id
Or simply
object = object.toObject();
I had the same problem and it turned out I was using $push incorrectly. I was doing
{$push:thing_to_push}
But it needed to be
{$push:{list_to_push_into:thing_to_push}}
#Magrelo and #plus led me to an answer that worked. Something like:
MyModel.findOneAndUpdate({ section_id: '51e427ac550dabbb0900000d' }, mongooseObject.toObject(), { upsert: true }, function(err, results) {
//...
});
Try passing update parameter value as string instead of mongoose model object. I was getting same error when I use to pass model object. Below is the code difference.
Code that was having issue:
updateUser: function(req, res) {
**var updatedUserModel = new Users(req.body);**
Users.findOneAndUpdate({tpx_id:req.params.id}, {$set:**updatedUserModel**}, function(err, User){
...
}
}
Working code:
updateUser: function(req, res) {
Users.findOneAndUpdate({tpx_id:req.params.id}, {$set:**req.body**}, function(err, User) {
...
}
I had the same issue. I ended up by using finOne() method
create a new one if no found, update the existing one if found.
I know there are two operations. but I just haven't find any way to do it in one step.
Another thing to check is if you are sending passing an array of changes to $set. The error message that I received when calling something like this:
db.products.update( { sku: "abc123" },
{ $set: [
{quantity: 500},
{instock: true}
]
}
)
Gave me the [Error: Invalid atomic update value for $set. Expected an object, received object]
Changing it to an object worked.
db.products.update( { sku: "abc123" },
{ $set: {
quantity: 500,
instock: true
}
}
)

Resources