Using MongoDb and #RepositoryRestController and Pageable from Spring.
Controller method(passing in -- ?page=0&size=20&sort=events.field1,asc):
..WithCreator(#RequestBody ..., #ParameterObject final Pageable page)
I'm having a hard time finding out how to search for a nested array of objects' field using the Spring Pageable object.
I can sort in mongo by the array of objects' field. eg.:
// a document in the db
{
events:[
{field1:"Test"}
]
}
//can sort the above document like so:
{ "$sort" : { "events.field1" : -1}}
The problem is, the Pageable object actually looks through the persistent entity and filters out fields that it doesn't find in there.
In this case of "events.field1" the Sort object translates to UNSORTED in the end. It does find the "events" field, but that's where it ends.
Question:
Is there a way with Pageable around this, other than passing the sort as a separate query field, or am I missing something?
The translation/field filtering of the original "raw" Sort object happens here:
class org.springframework.data.rest.webmvc.json JacksonMappingAwareSortTranslator..
public Sort translateSort(Sort input, PersistentEntity<?, ?> rootEntity){..}
Related
What does the a JSON blob with search filters, allowed operators: $eq, $ne, $in, $nin, $gt, $lt, $gte, $lte, $exists in the Swagger documentation that is shown in the DataStax Document API Swagger UI, it's not that documented so I want to ask if the query string is based on MongoDB?
The Document API exposed on top of Cassandra is provided by the open source project Stargate, indeed developed by Datastax and embedded in their Saas solution Astra.
The JSON query String than you created is parsed and converted in a proper CQL query under the hood.
Source code doesn't lie you can find the full code here and specially parsing of the where clause here
public List<FilterCondition> convertToFilterOps(
List<PathSegment> prependedPath,
JsonNode filterJson) {
List<FilterCondition> conditions = new ArrayList<>();
if (!filterJson.isObject()) {
throw new DocumentAPIRequestException("Search was expecting a JSON object as input.");
}
ObjectNode input = (ObjectNode) filterJson;
Iterator<String> fields = input.fieldNames();
while (fields.hasNext()) {
String fieldName = fields.next();
if (fieldName.isEmpty()) {
throw new DocumentAPIRequestException(
"The field(s) you are searching for can't be the empty string!");
}
...
The query string is pretty similar in spirit to what you'd find with Mongo.
Here are some sample where clauses to give an idea:
{"name": {"$eq": "Eric"}} - simple enough, matches documents that have a field name with value Eric
{"a.age": {"$gt": 0}} - You can also reference nested fields in a document
{"friends.[0].name": {"$in": ["Cassandra"]}} - Array elements are referenced using [], this would match if the document's first friend is named Cassandra.
{"friends.*.age": {"$gte": 24}} - Wildcard * can be used to match any element in an array, or any field at a particular level of nesting. This matches any friend whose age is >= 24.
There's a MongoDB collection that was populated with documents like this one:
{
"_id" : ObjectId("5b7f83b591fae49715443590"),
"content" : "{\n\t\"email\":\"username#domain.com\",\n\t\"country_code\": \"US\"\n}"
}
As you can see "content"has a JSON string
I was able to parse the string using JSON.parse() like:
db.getCollection('my_collection').find({}).map(function(doc){ return {'_id':doc._id, 'content': JSON.parse(doc.content)}});
Giving me an array of objects with _id and content as an object.
Is there a way to execute queries (filtering, aggregate functions) over content preferably without getting an array from map?
like you said In terms of the database content is a string. So you need to save the entire collection where content will be a object so you will be able to filtering, aggregate etc...
I am converting filters from the client to a QueryImpl using the setQueryObject method.
When I try to add another complex criteria to that query my original query is moved to a field named baseQuery and the new criteria is the query field.
When the query is executed only the new criteria is used and the baseQuery is not used.
It only happens when the client query is formatted like this: { "$or" : [{ "field1" : { "$regex" : "value1", "$options" : "i"}},...]}
and the new criteria is formatted in the same way(meaning an $or operation).
It seems that when I try to merge 2 $or queries it happens but when I merge $or with $and it concatenates the queries properly.
Am I using it wrong or is it a genuine bug?
Edit:
Code:
public static List<Entity> getData(client.Query query) {
QueryImpl<Entity> finalQuery = Morphia.realAccess().extractFromQuery(Entity.class,query);
finalQuery.and(finalQuery.or(finalQuery.criteria("field").equal(false), finalQuery.criteria("field").doesNotExist()));
return finalQuery.asList();
}
public <E> QueryImpl<E> extractFromQuery(Class<E> clazz, client.Query query) {
QueryImpl<E> result = new QueryImpl<E>(clazz,this.db.getCollection(clazz),this.db);
result.setQueryObject(query.getFiltersAsDBObject);
return result;
}
QueryImpl and setQueryObject() are internal constructs. You really shouldn't be using them as they may change or go away without warning. You should be using the public query builder API to build up your query document.
I am having the same problem, It seems to be working when I do this:
finalQuery.and();
finalQuery.and(finalQuery.or(finalQuery.criteria("field").equal(false), finalQuery.criteria("field").doesNotExist()));
It is kind of ugly and would love to hear if someone have a different approach, but the only way I was able to find to convert the data from the client side which is a DBObject is to use the setQueryObject() of QueryImpl.
The mongo docs specify that you can specify a query hint for count queries using the following syntax:
db.orders.find(
{ ord_dt: { $gt: new Date('01/01/2012') }, status: "D" }
).hint( { status: 1 } ).count()
Can you do this using the mongo template? I have a Query object and am calling the withHint method. I then call mongoTemplate.count(query); However, I'm pretty sure it's not using the hint, though I'm not positive.
Sure, there are a few forms of this including going down to the basic driver, but assuming using your defined classes you can do:
Date date = new DateTime(2012,1,1,0,0).toDate();
Query query = new Query();
query.addCriteria(Criteria.where("ord_dt").gte(date));
query.addCriteria(Criteria.where("status").is("D"));
query.withHint("status_1");
long count = mongoOperation.count(query, Class);
So you basically build up a Query object and use that object passed to your operation, which is .count() in this case.
The "hint" here is the name of the index as a "string" name of the index to use on the collection. Probably something like "status_1" by default, but whatever the actual name is given.
I have a collection called "Binders"
In this collection I have a field called "docs" that references a "Docs" schema
I want to pull a "Binder" by id with findOne() which is no problem.
But I want to limit the returned "Docs" by a field comparison
I am somewhat new to mongoose but if i had pseudo my query...
Binder.findOne({_id: req.binder._id}, {$where {docs.type = 'type1'}}, function(error, binder) {
// So this would return my full binder but only the docs where their .type = 'type1'
});
Where I get lost is the limiting based on a Schema
{$where {docs.type = 'type1'}}
So if I pulled a binder and it had 50 docs, but none of the docs were of type1 I would still get my binder info, however there would be no documents in the Docs field.
Hope that makes sense.
If I'm following you right and docs is an array, then you can use the $elemMatch projection operator to do this:
Binder.findOne({_id: req.binder._id}, {docs: {$elemMatch: {type: 'type1'}}}, ...