How do I write Sequelize queries with optional params?
Let's say, I have a following query:
const result : SomeModel[] = await SomeModel.findAll(
{where:
{
id: givenId,
type: someType
}
});
How do I rephrase the same query in case someType is null?
If someType is null, then it should be removed from the where clause and results should be return only on basis of 'id'.
How about defining the where clause outside...
let where = { id: givenId };
if(someType)
where.type = someType;
...and using it in your query like so
await SomeModel.findAll({where});
Related
I have an node-express endpoint that accepts several url parameters as filters. For example
{{baseUrl}}/projects?page=1&name=myProject
The request is then translated to a mongoDB query.
Model:
const projectSchema = mongoose.Schema({
name: {
type: String,
required: true,
},
users: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
});
const Project = mongoose.model('Project', projectSchema);
const filter = pick(req.query, ['name']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
options.populate = 'users';
const result = await projectService.queryProjects(filter, options, req.user);
This works fine, but when i use mongoDB filter expressions like "/MyProject/" the filter is passed as a string and not as an expression.
What do i need to change to get the expected result?
#current result
console.log(filter);
#Output: { name: '/MyProject/' }
#expected result
const test = { name: /MyProject/ };
console.log(test);
#Output { name: /MyProject/ }
/MyProject/ (without quotes is a regular expression):
> typeof "/MyProject/"
'string'
> typeof /MyProject/
'object'
> /MyProject/ instanceof String
false
> /MyProject/ instanceof RegExp
true
Now if you want to construct a RegEx from a string you may want to consider using RegEx constructor:
new RegExp("MyProject")
/MyProject/
Or, you can read your filter as a string from your query parameters then use the other way of passing filters to MongoDB with $regex:
const filter = pick(req.query, ['name']);
const result = await projectService.queryProjects({"name": {"$regex": filter, "$options": "i"}, options, req.user);
WARNING
Either way please be careful when using such approaches as they may lead to ReDOS and/or NoSQL injection.
How can I query this data with name, what should I write in where clause?
{a: [{name: 'test'}, {name: 'test2'}]}
The example bellow is for first element only. I would like to have for every index.
a: {
0: {
name: value
},
},
Query by name using pure SQL.
await sequelize.query(
SELECT
*
FROM
table_name
WHERE
a->>'name' = 'something';
);
Converting this to sequelize functions should be something equivalent to this query.
Model.findAll<Model>({
where: { 'a': { name: 'something' } }
});
EDIT AFTER THE COMMENT:
I couldn’t find sequelize function for jsonb_array_elements, so I hope a pure SQL query can help.
await sequelize.query(
SELECT * FROM table_name, jsonb_array_elements(table_name.jsonb_column_name) with ordinality arr(item_object)
WHERE item_object->>'name' = 'test';
)
// jsonb_column_name is the same as a
haven't used graphql or mongodb previously. What is the proper way to pass objects for the update mutation?
Since the only other way i see to pass multiple dynamically appearing parameters is to use input type which is appears to be a bit ineffective to me (in terms of how it looks in the code, especially with bigger objects), i just pass the possible values themselves. however in this case i need to dynamically construct updateObject, which again, going to get messy for the bigger models.
for example now i did:
Mutation: {
updateHub: async (_, { id, url, ports, enabled }) => {
const query = {'_id': id};
const updateFields = {
...(url? {url: url} : null),
...(ports? {ports: ports} : null),
...(enabled? {enabled: enabled} : null)
};
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
any advise on the better way to handle this?
thanks!
It appears your code could benefit from using ES6 spread syntax -- it would permit you to deal with an arbitrary number of properties from your args object without the need for serial tertiary statements.
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = { ...restArgs };
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
If for some reason you need to explicitly set the undefined properties to null in your object, you could possibly use some a config obj and method like defaults from the lodash library as shown below:
import { defaults } from 'lodash';
const nullFill = { url: null, ports: null, enabled: null }; // include any other properties that may be needed
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = defaults(restArgs, nullFill);
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
Also, FWIW, I would consider placing the dynamic arguments that could be potentially be updated on its own input type, such as HubInput in this case, as suggested in the graphql docs. Below I've shown how this might work with your mutation. Note that because nothing on HubInput is flagged as requird (!) you are able to pass a dynamic collection of properties to update. Also note that if you take this appraoch you will need to properly destructure your args object initially in your mutation, something like { id, input }.
input HubInput {
url: String
ports: // whatever this type is, like [String]
enabled: Boolean
// ...Anything else that might need updating
}
type UpdateHubPayload {
success: Boolean
message: String
hub: Hub // assumes you have defined a type Hub
}
updateHub(id: Int, input: HubInput!): UpdateHubPayload
I'm trying to query for an object value nested in array and return only this value (together with the entire object it nested in)
i have tried the following code but it return all the affiliates in the affiliate array while i only want the one i searched :
// data structure :
{
_id: 1,
name: vendorName,
countryCode: US,
affiliates: [{
number: 1,
name: affName
},
{
number: 2,
name: affName
}
]
async function getDetails(user){
let vendorQuery = {
countryCode: user.countryCode,
affiliates: {
$elemMatch: {
number: user.affiliateNumber
}
}
}
let db = await mongoService.connect()
const collection = db.collection('vendors');
let vendorDetails = await collection.find( vendorQuery, {'affiliates.$':1} ).toArray()
console.log('brokerDetails : ',brokerDetails);
return vendorDetails
}
So in code above i expect the vendor object to be returned , but only with the matched affiliate and not all of them
You need to use $ projection operator to return only the matched value from the array.
Also, affiliatesQuery need stop be a part of countryQuery, and that whole should be the first argument of your find query.
Second argument of the find query is supposed to be projection.
Try this :
let countryQuery = {
countryCode: user.countryCode,
affiliates.number: user.affiliateNumber
}
let db = await mongoService.connect()
const collection = db.collection('vendors');
let vendorDetails = await collection.find(countryQuery, {'affiliates.$':1}).toArray()
you can use aggregation pipeline
const db = await mongoService.connect();
const collection = db.collection('vendors');
const result = await collection.aggregate([
// query documents
{
$match: {
countryCode: user.countryCode,
'affiliates.number': user.affiliateNumber,
}
},
// unwind affiliates array
{
$unwind : '$affiliates',
},
// filter docs again
{
$match: {
'affiliates.number': user.affiliateNumber,
},
},
// you can even replace root of the doc and return just object you need
{
$replaceRoot: { newRoot: "$affiliates" }
}
])
return result
I'm trying to add a mutation to allow for the client to add a document to the LineItem schema. The code I've written below allows me to do that when I test it using GraphiQL, but the response I get is null. How do I fix the code so that the response is the new document?
addLineItem: {
type: LineItemType,
args: {
orderId: {type: new GraphQLNonNull(GraphQLID)},
productId: {type: new GraphQLNonNull(GraphQLID)},
quantity: {type: new GraphQLNonNull(GraphQLInt)}
},
resolve(parent, args) {
Product.findById(args.productId, (err, result) => {
let price = result.price;
let subtotal = price*args.quantity;
let lineitem = new LineItem({
orderId : args.orderId,
productId : args.productId,
quantity : args.quantity,
subtotal: subtotal
});
return lineitem.save();
}}
}
},
The problem is that you are not returning any value in resolve function, the lineitem.save(); returns the value inside the callBack
make resolve function async, remove the findById callBack and await for the result, then implement your logic and return the value, like below:
async resolve(parent, args) {
const result = await Product.findById(args.productId);
let price = result.price;
let subtotal = price*args.quantity;
let lineitem = new LineItem({
orderId : args.orderId,
productId : args.productId,
quantity : args.quantity,
subtotal: subtotal
});
return lineitem.save();
}
Actually there is nothing wrong with your code . .
You need to specify a return value as a type (e.g. Boolean, String, etc). Types can be nullable, for example: the value can be null, and, in fact, they are nullable by default unless you define them with !
so there is nothing wrong with null return value.