How to index an object with completion fields - elasticsearch

Following http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
How can I index/insert (I can do mapping) an object using Nest client library to be able to provide following options:
"input": ...,
"output": ...,
"payload" : ...,
"weight" : ...
I would like to be able to provide multiple values in 'input' option.
Can't find anyway of doing this using NEST.
Thank you

NEST provides the SuggestField type in order to assist in indexing completion suggestions. You don't necessarily need to use this type, you can provide your own that contains the expected completion fields (input, output, etc...), but the purpose of SuggestField is to make the whole process easier by already providing a baked in type.
Usage:
Add a suggest field to the document/type you are indexing:
public class MyType
{
public SuggestField Suggest { get; set; }
}
Your mapping should look something like:
client.Map<MyType>(m => m
.Properties(ps => ps
.Completion(c => c.Name(x => x.Suggest).Payloads(true))
)
);
Indexing example:
var myType = new MyType
{
Suggest = new SuggestField
{
Input = new [] { "Nevermind", "Nirvana" },
Output = "Nirvana - Nevermind",
Payload = new { id = 1234 },
Weight = 34
}
};
client.Index<MyType>(myType);
Hope that helps.

Related

How to combine Aggregate function with update

I could not find a way to translate the fowling MongoDb command into C#
db.Queue.aggregate(
[
{ $match: { "Processed": false } },
{ $sort: { "LastTimeChanged": 1  } },
{ $limit: 1 },
{ $set: { "WorkerName": "WORKER_NAME", "Processed": true }  },
{ "$merge": "Queue"  }])
The issues that I fund was with the $set and $merge command
$set -> in the MongoDb.Driver for .NET, associated with the Aggregate command I could not find any command that look like the $set
$merge -> the merge command examples are exclusive for merging collections and in this case, I could not find a way to use the Merge method in the API.
Any one can throw light here!??
thanks
Paulo Aboim Pinto
I found a way to execute the command using the MongoDb.Driver but I thing there should be a better and fluent way of doing it
var filter = Builders<QueueCollection>.Filter.And
(
Builders<QueueCollection>.Filter.Eq(x => x.WorkerName, string.Empty),
Builders<QueueCollection>.Filter.Eq(x => x.Processed, false)
);
var sortOptions = Builders<QueueCollection>
.Sort.Ascending("LastTimeChanged");
this.queueCollection.Aggregate()
.Match(filter)
.Sort(sortOptions)
.Limit(1)
.AppendStage<QueueCollection>("{ $set: { 'WorkerName': 'WORKER_NAME' } }")
.AppendStage<QueueCollection>("{ $merge: 'Queue' }")
.ToList();
This works for now, but I would like to want still to know:
How do I replace the $set in the Aggregate pipeline
How do I write a proper $merge command.
thanks in advance for any answer
Paulo Aboim Pinto

I want to update values ​in an array in an array while using MongoTemplate

First, I will show the state stored in mongodb.
As you can see, it is a structure with a list called replies in a list called comments. And inside replies there is an array called likes.
comments : [
Object1 : {
replies : [
likes : [
0 : {},
1 : {}
]
]
},
Object2 : {
replies : [
likes : [
0 : {},
1 : {}
]
]
}
]
What I want to do here is to insert/subtract a value only from the likes array inside a specific replies structure. I'm currently using Spring boot and have tried the following:
Query query = new Query();
Criteria criteria = Criteria.where("_id").is(new ObjectId(postId))
.andOperator(Criteria.where("comments")
.elemMatch(Criteria.where("_id").is(new ObjectId(commentId))
.andOperator(Criteria.where("replies")
.elemMatch(Criteria.where("_id").is(new ObjectId(replyId)))
)
)
);
query.addCriteria(criteria);
Update update = new Update();
if (state) {
// remove user id
update.pull("comments.$[].replies.$.likes", new ObjectId(userId));
} else {
// add user id
update.push("comments.$[].replies.$.likes").value(new ObjectId(userId));
}
mongoTemplate.updateFirst(query, update, MyEntity.class);
It is an operation to add or remove userId according to boolean state. As a result of the attempt, up to a specific comment is found, but userId is unconditionally entered in the first likes list of the replies list inside the comment. What I want is to get into the likes list inside a specific reply. Am I using the wrong parameter in update.push()? I would appreciate it if you could tell me how to solve it.
Not a direct answer to your question as I'm not experienced with spring's criteria builder, but here's how you would do it in mongo directly, which might help you to figure it out:
You could define arrayfilters allowing you to keep track of the corresponding indices of each comments and replies. You can then use those indices to push a new object at the exact matching indices:
db.collection.update({
_id: "<postId>"
},
{
$push: {
"comments.$[comments].replies.$[replies].likes": {
_id: "newlyInsertedLikeId"
}
}
},
{
arrayFilters: [
{
"comments._id": "<commentId>"
},
{
"replies._id": "<replyId>"
}
]
})
Here's an example on mongoplayground: https://mongoplayground.net/p/eNdDXXlyi2X

How to expose graphql field with different name

I am exploring GraphQL and would like to know if there is any way of renaming the response field for example i have a POJO with these field
class POJO {
Long id;
String name;
}
GraphQL query:
type POJO {
id: Long
name: String
}
My response is something like this
{
"POJO" {
"id": 123,
"name": "abc"
}
}
Can i rename the name field to something like userName so that my response is below
{
"POJO" {
"id": 123,
"userName": "abc"
}
}
You can use GraphQL Aliases to modify individual keys in the JSON response.
If this is your original query
query {
POJO {
id
name
}
}
you can introduce a GraphQL alias userName for the field name like so:
query {
POJO {
id
userName: name
}
}
You can also use GraphQL aliases to use the same query or mutation field multiple times in the same GraphQL operation. This get's especially interesting when using field parameters:
query {
first: POJO(first: 1) {
id
name
}
second: POJO(first: 1, skip: 1) {
id
name
}
}
The question is: how are you creating the schema in the first place? There's no intrinsic connection between Java and GraphQL types - they are completely unrelated unless you correlate them. So you can name the fields any way you want in the schema, and make a resolver (DataFetcher) that gets the value from anywhere (thus any POJO field too).
If you're using a tool to generate the schema from Java types (graphql-java-annotations, graphql-spqr etc), then use that tool's facilities to drive the mapping. Both the mentioned tools allow customizing the mapping via annotations. GraphQL-SPQR enables the same via external configuration as well.
If you clarify your question further, I'll be able to give a more precise answer.
Looks like GraphQLName annotation can help.
Example from documentation :
"Additionally, #GraphQLName can be used to override field name. You can use #GraphQLDescription to set a description."
These can also be used for field parameters:
public String field(#GraphQLName("val") String value) {
return value;
}
I know this question is very old but following code is used for renaming the field:
public class ProductReviewType: ObjectGraphType<ProductReview>
{
public ProductReviewType()
{
Field(x => x.ProductReviewId, type: typeof(IdGraphType)).Description("some desc here");
Field(x => x.ProductId).Description("some desc here");
Field("reviewername", x => x.ReviewerName).Description("some desc here");
Field("reviewdate",x => x.ReviewDate).Description("some desc here");
Field("emailaddress", x => x.EmailAddress).Description("some desc here");
Field("rating", x => x.Rating).Description("some desc here");
Field("comments",x => x.Comments).Description("some desc here");
Field("modifieddate", x => x.ModifiedDate).Description("some desc here");
}
}
In the above code, modifieddate would be the field name for property "ModifiedDate".

How to model recursive data structures in GraphQL

I have a tree data structure that I would like to return via a GraphQL API.
The structure is not particularly large (small enough not to be a problem to return it in one call).
The maximum depth of the structure is not set.
I have modeled the structure as something like:
type Tag{
id: String!
children: [Tag]
}
The problem appears when one wants to get the tags to an arbitrary depth.
To get all the children to (for example) level 3 one would write a query like:
{
tags {
id
children {
id
children {
id
}
}
}
}
Is there a way to write a query to return all the tags to an arbitrary depth?
If not what is the recommended way to model a structure like the one above in a GraphQL API.
Some time ago I came up with another solution, which is the same approach like #WuDo suggested.
The idea is to flatten the tree on data level using IDs to reference them (each child with it's parent) and marking the roots of the tree, then on client side build up the tree again recursively.
This way you should not worry about limiting the depth of your query like in #samcorcos's answer.
schema:
type Query {
tags: [Tag]
}
type Tag {
id: ID!
children: [ID]
root: Boolean
}
response:
{
"tags": [
{"id": "1", "children": ["2"], "root": true},
{"id": "2", "children": [], "root": false}
]
}
client tree buildup:
import find from 'lodash/find';
import isArray from 'lodash/isArray';
const rootTags = [...tags.map(obj => ({...obj})).filter(tag => tag.root === true)];
const mapChildren = childId => {
const tag = find(tags, tag => tag.id === childId) || null;
if (isArray(tag.children) && tag.children.length > 0) {
tag.children = tag.children.map(mapChildren).filter(tag => tag !== null);
}
}
const tagTree = rootTags.map(tag => {
tag.children = tag.children.map(mapChildren).filter(tag => tag !== null);
return tag;
});
// Update 2022-08-16 Fixed typo
Another option if you're willing to give up on the type-safety and subfield querying that GraphQL provides along with the ability to cache and reference the objects by their IDs is to encode the data as JSON. The gaphql-type-json package provides resolvers to make this easy. These are also included with permission by graphql-scalars which contains a lot of other handy scalars.
I'm doing this for the hierarchical data that defines the controls for a dynamic form. In this case, there aren't any IDs to lose, so it's an easy win.

Elasticsearch NEST 2 How to correctly map and use nested classes and bulk index

I have three main questions I need help answering.
How do you correctly map and store a nested map?
How do you search a nested part of a document?
How do you bulk index?
I'm using Nest version 2 and have been looking over the new documentation which can be found Here. The documentation has been useful in creating certain parts of the code but unfortunately doesn't explain how they fit together.
Here is the class I'm trying to map.
[ElasticsearchType(Name = "elasticsearchproduct", IdProperty = "ID")]
public class esProduct
{
public int ID { get; set; }
[Nested]
public List<PriceList> PriceList { get; set; }
}
[ElasticsearchType(Name = "PriceList")]
public class PriceList
{
public int ID { get; set; }
public decimal Price { get; set; }
}
and my mapping code
var node = new Uri(HOST);
var settings = new ConnectionSettings(node).DefaultIndex("my-application");
var client = new ElasticClient(settings);
var map = new CreateIndexDescriptor("my-application")
.Mappings(ms => ms
.Map<esProduct>(m => m
.AutoMap()
.Properties(ps => ps
.Nested<PriceList>(n => n
.Name(c => c.PriceList)
.AutoMap()
)
)
)
);
var response = client.Index(map);
This is the response I get:
Valid NEST response built from a succesful low level call on POST: /my-application/createindexdescriptor
So that seems to work. next index.
foreach (DataRow dr in ProductTest.Tables[0].Rows)
{
int id = Convert.ToInt32(dr["ID"].ToString());
List<PriceList> PriceList = new List<PriceList>();
DataRow[] resultPrice = ProductPriceTest.Tables[0].Select("ID = " + id);
foreach (DataRow drPrice in resultPrice)
{
PriceList.Add(new PriceList
{
ID = Convert.ToInt32(drPrice["ID"].ToString()),
Price = Convert.ToDecimal(drPrice["Price"].ToString())
}
esProduct product = new esProduct
{
ProductDetailID = id,
PriceList = PriceList
};
var updateResponse = client.Update<esProduct>(DocumentPath<esProduct>.Id(id), descriptor => descriptor
.Doc(product)
.RetryOnConflict(3)
.Refresh()
);
var index = client.Index(product);
}
}
Again this seems to work but when I come to search it does seem to work as expected.
var searchResults = client.Search<esProduct>(s => s
.From(0)
.Size(10)
.Query(q => q
.Nested(n => n
.Path(p => p.PriceList)
.Query(qq => qq
.Term(t => t.PriceList.First().Price, 100)
)
)
));
It does return results but I was expecting
.Term(t => t.PriceList.First().Price, 100)
to look move like
.Term(t => t.Price, 100)
and know that is was searching the nested PriceList class, is this not the case?
In the new version 2 documentation I can't find the bulk index section. I tried using this code
var descriptor = new BulkDescriptor();
***Inside foreach loop***
descriptor.Index<esProduct>(op => op
.Document(product)
.Id(id)
);
***Outside foreach loop***
var result = client.Bulk(descriptor);
which does return a success response but when I search I get no results.
Any help would be appreciated.
UPDATE
After a bit more investigation on #Russ advise I think the error must be with my bulk indexing of a class with a nested object.
When I use
var index = client.Index(product);
to index each product I can use
var searchResults = client.Search<esProduct>(s => s
.From(0)
.Size(10)
.Query(q => q
.Nested(n => n
.Path(p => p.PriceList)
.Query(qq => qq
.Term(t => t.PriceList.First().Price, 100)
)
)
)
);
to search and return results, but when I bulk index this no long works but
var searchResults = client.Search<esProduct>(s => s
.From(0)
.Size(10)
.Query(q => q
.Term(t => t.PriceList.First().Price, 100)
)
);
will work, code b doesn't work on the individual index method. Does anyone know why this has happened?
UPDATE 2
From #Russ suggested I have taken a look at the mapping.
the code I'm using to index is
var map = new CreateIndexDescriptor(defaultIndex)
.Mappings(ms => ms
.Map<esProduct>(m => m
.AutoMap()
.Properties(ps => ps
.Nested<PriceList>(n => n
.Name(c => c.PriceList)
.AutoMap()
)
)
)
);
var response = client.Index(map);
Which is posting
http://HOST/fresh-application2/createindexdescriptor {"mappings":{"elasticsearchproduct":{"properties":{"ID":{"type":"integer"},"priceList":{"type":"nested","properties":{"ID":{"type":"integer"},"Price":{"type":"double"}}}}}}}
and on the call to http://HOST/fresh-application2/_all/_mapping?pretty I'm getting
{
"fresh-application2" : {
"mappings" : {
"createindexdescriptor" : {
"properties" : {
"mappings" : {
"properties" : {
"elasticsearchproduct" : {
"properties" : {
"properties" : {
"properties" : {
"priceList" : {
"properties" : {
"properties" : {
"properties" : {
"ID" : {
"properties" : {
"type" : {
"type" : "string"
}
}
},
"Price" : {
"properties" : {
"type" : {
"type" : "string"
}
}
}
}
},
"type" : {
"type" : "string"
}
}
},
"ID" : {
"properties" : {
"type" : {
"type" : "string"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
fresh-application2 returned mapping doesn't mention nested type at all, which I'm guessing is the issue.
The mapping my working nested query looks more like this
{
"my-application2" : {
"mappings" : {
"elasticsearchproduct" : {
"properties" : {
"priceList" : {
"type" : "nested",
"properties" : {
"ID" : {
"type" : "integer"
},
"Price" : {
"type" : "double"
}
}
},
"ID" : {
"type" : "integer"
},
}
}
}
}
}
This has the nested type returned. I think the one which isn't returning nested as a type is when I started using .AutoMap() , am I using it correctly?
UPDATE
I have fixed my mapping problem. I have changed my mapping code to
var responseMap = client.Map<esProduct>(ms => ms
.AutoMap()
.Properties(ps => ps
.Nested<PriceList>(n => n
.Name(c => c.PriceList)
.AutoMap()
)
)
);
Whilst you're developing, I would recommend logging out requests and responses to Elasticsearch so you can see what is being sent when using NEST; this'll make it easier to relate to the main Elasticsearch documentation and also ensure that the body of the requests and responses match your expectations (for example, useful for mappings, queries, etc).
The mappings that you have look fine, although you can forgo the attributes since you are using fluent mapping; there's no harm in having them there but they are largely superfluous (the type name for the esProduct is the only part that will apply) in this case because .Properties() will override inferred or attribute based mapping that is applied from calling .AutoMap().
In your indexing part, you update the esProduct and then immediately after that, index the same document again; I'm not sure what the intention is here but the update call looks superfluous to me; the index call will overwrite the document with the given id in the index straight after the update (and will be visible in search results after the refresh interval). The .RetryOnConflict(3) on the update will use optimistic concurrency control to perform the update (which is effectively a get then index operation on the document inside of the cluster, that will try 3 times if the version of the document changes in between the get and index). If you're replacing the whole document with an update i.e. not a partial update then the retry on conflict is not really necessary (and as per previous note, the update call in your example looks unnecssary altogether since the index call is going to overwrite the document with the given id in the index).
The nested query looks correct; You specify the path to the nested type and then the query to a field on the nested type will also include the path.I'll update the NEST nested query usage documentation to better demonstrate.
The bulk call looks fine; you may want to send documents in batches e.g. bulk index 500 documents at a time, if you need to index a lot of documents. How many to send in one bulk call is going to depend on a number of factors including the document size, how it is analyzed, performance of the cluster, so will need to experiment to get a good bulk size call for your circumstances.
I'd check to make sure that you are hitting the right index, that the index contains the count of documents that you expect and find a document that you know has a PriceList.Price of 100 and see what is indexed for it. It might be quicker to do this using Sense while you're getting up an running.

Resources