How to do case sensitive queries on dynamic fields - indexing

I want to do case sensitive queries against dynamic fields in a RavenDB index. I have seen that this can be done using an AbstractAnalyzerGenerator thing, but I have not found information on how to use it.
My document class looks similar to:
class Thing {
public string Name;
public DateTime CreatedAt;
public Dictionary<string, object> Properties;
}
The index map is set up like this:
Map = things => from thing in things
select new { Name = thing.Name,
CreatedAt = thing.CreatedAt,
_ = thing.Properties.Select( p =>
p.CreateField(p.Key, p.Value, false, true) );
}
Now I would like to use this magic AbstractAnalzyzerGenerator thing to make some of the property fields case sensitive, based on their name. Unfortunately I don't know how.. :)

ErikR,
If you need case sensitive, you need to do it like this:
p.CreateField(p.Key, p.Value, false, **false**) );

Related

Apache Ignite : Ignite Repository query with "IN" clause, returns no records

I am using Apache Ignite as the back-end data store in a SpringBoot Application.
I have a requirement where I need to get all the entities whose name matches one of the names from a set of names.
Hence i am trying to get it implemented using a #Query configuration and a method named findAllByName(Iterable<String> names)as below:
Here on the Query, I am trying to use the 'IN' clause and want to pass an array of names as an input to the 'IN' clause.
#RepositoryConfig(cacheName = "Category")
public interface CategoryRepository extends IgniteRepository<Category, Long>
{
List<Category> findByName(String name);
#Query("SELECT * FROM Category WHERE name IN ( ? )")
Iterable<Category> findAllByName(Iterable<String> names); // this method always returns empty list .
}
In this the method findAllByName always returns empty list, even when ignite has Categories for which the name field matches the data passed in the query.
I am unable to figure out if there is a problem with the Syntax or the query of the method signature or the parameters.
Please try using String[] names instead for supplying parameters.
UPDATE: I have just checked the source, and we don't have tests for such scenario. It means that you're on uncharted territory even if it is somehow possible to get to work.
Otherwise looks unsupported currently.
I know your question is more specific to Spring Data Ignite feature. However, as an alternate, you can achieve it using the SqlQuery abstraction of Ignite.
You will form your query like this. I have pasted the sample below with custom sql function inSet that you will write. Also, the below tells how this is used in your sql.
IgniteCache<String, MyRecord> cache = this.ignite
.cache(this.environment.getProperty(Cache.CACHE_NAME));
String sql = "from “my-ignite-cache”.MyRecord WHERE
MyRecord.city=? AND inSet(?, MyRecord.flight)"
SqlQuery<String, MyRecord> sqlQuery = new SqlQuery<>(MyRecord.class,
sql);
sqlQuery.setArgs(MyCity, [Flight1, Flight2 ] );
QueryCursor<Entry<String, MyRecord>> resultCursor = cache.query(sqlQuery);
You can iterate the result cursor to do something meaningful from the extracted data.
resultCursor.forEach(e -> {
MyRecord record = e.getValue();
// do something with result
});
Below is the Ignite Custom Sql function which is used in the above Query - this will help in replicating the IN clause feature.
#QuerySqlFunction
public static boolean inSet(List<String> filterParamArgIds, String id) {
return filterParamArgIds.contains(id);
}
And finally, as a reference MyRecord referred above can be defined something like this.
public class MyRecord implements Serializable {
#QuerySqlField(name = "city", index = true)
private String city;
#QuerySqlField(name = "flight", index = true)
private String flight;
}

Storing maps in Hibernate/GORM

I'm having trouble understanding how to represent some information I'm dealing with using Hibernate in a Grails application. It's really more of a SQL problem though.
I have a number of Items to store in a database, from a JSON file. Each Item has a map of legality values. A legality map looks like this:
{
"form2003": "legal",
"form2007": "legal",
"form2008": "banned",
"form2009": "restricted"
"form2013": "legal"
}
or this
{
"form2003": "legal",
"form2004": "legal",
"form2005": "legal",
"form2007": "restricted",
"form2008": "banned",
"form2009": "banned"
}
Keys range from "form2001" to "form2013", while values can be 'banned' 'restricted' or 'legal'. A fourth status is indicated implictly by the lack of an explicit status.
Right now I'm representing Items in their own table/domain type, with a parallel Legalities table; Items have a one-to-one relationship with Legalities. I have several other table/domain classes similar to Legalities, each with their own one-to-one relationship with Item.
The Legalities domain class I have now basically looks like this
class Legalities {
static belongsTo = [item: Item]
String form2000
String form2001
...
String form2013
static constraints = {
form2000(nullable: true)
...
form2013(nullable: true)
}
}
I need to be able to efficiently find every Item where a given field, like form2010, has a given value. This seems like it should be straightforward, with a HQL query like this:
"from Item as i where i.legalities.form2010 = \'legal\'"
The thing is, Grails and Hibernate won't let me parameterize queries on field names, only on field values!! The field name comes from a form so I end up having to try to sanitize the field name myself and assemble the query string manually. Is there some other way I'm supposed to be structuring the information here? Or am I running into a deficiency of Hibernate?
Try using criteria to create your queries like so:
def getLegalitiesForForm(formNum, formVal) {
String formField = "form20${formNum}"
return Legalities.createCriteria().get {
eq formField, formVal
}
}
http://grails.org/doc/2.3.4/guide/GORM.html#criteria

How to change the default analyzer for dynamic fields

RavenDB uses the LowerCaseKeywordAnalyzer by default and switches (if I'm not mistaken) to the StandardAnalyzer if you set a field to FieldIndexing.Analyzed.
RavenDB also defaults to the LowerCaseKeywordAnalyzer for dynamic fields.
I would like to change this. I want RavenDB to use the StandardAnalyzer for ALL my dynamic fields.
How can I do that?
Do I have to use a plugin and implement AbstractAnalyzerGenerator?
I would prefer not to since this will make deployment a lot more complicated for something as simple as changing the default analyzer.
I fixed this problem with the next code.
public class Product_ByFields : AbstractIndexCreationTask<Product>
{
public Product_ByFields()
{
Map = products => from product in products
select new
{
_ = product.FieldValues.Select(f => CreateField(f.Key, f.Value.SearchTerms, false, true))
};
this.Analyze("__all_fields", "Lucene.Net.Analysis.Standard.StandardAnalyzer");
}
}
You can use this:
_= CreateField("Foo", "bar", stored: false, analyzed: true);

Returning composite class with Neo4jClient

The Neop4jClient cypher wiki (https://github.com/Readify/Neo4jClient/wiki/cypher) contains an example of using lambda expressions to return multiple projections...
var query = client
.Cypher
.Start(new { root = client.RootNode })
.Match("root-[:HAS_BOOK]->book-[:PUBLISHED_BY]->publisher")
.Return((book, publisher) => new {
Book = book.As<Book>(),
Publisher = publisher.As<Publisher>(),
});
So the query will return details of both book nodes and publisher nodes. But I want to do something slightly different. I want to combine the contents of a single node type with a property of the matched path. Lets say I have Person nodes with a property 'name', and a class defined so,,,
public class descendant
{
public string name { get; set; }
public int depth { get; set; }
}
A cypher query like this will return what I want, which is all descendants of a given node with the depth of the relationship...
match p=(n:Person)<-[*]-(child:Person)
where n.name='George'
return distinct child.name as name, length(p) as depth
If I try a Neo4jClient query like this...
var query =
_graphClient.Cypher
.Match("p=(n:Person)<-[*]-(child:Person)")
.Where("n.name='George'")
.Return<descendant>("child.name, length(p)") ;
I get an error that the syntax is obsolete, but I can't figure out how should I project the cypher results onto my C# POCO. Any ideas anyone?
The query should look like this:
var query =
_graphClient.Cypher
.Match("p=(n:Person)<-[*]-(child:Person)")
.Where((Person n) => n.name == "George")
.Return((n,p) => new descendant
{
name = n.As<Person>().Name,
depth = p.Length()
});
The Return statement should have the 2 parameters you care about (in this case n and p) and project them via the lambda syntax (=>) to create a new descendant instance.
The main point this differs from the example, is that the example creates a new anonymous type, whereas you want to create a concrete type.
We then use the property initializer (code inside the { } braces) to set the name and depth, using the As<> and Length extension methods to get the values you want.
As a side note, I've also changed the Where clause to use parameters, you should always do this if you can, it will make your queries both faster and safer.

Full text search using linq

I have a list of names in a observable collection returned from wcf service and database is oracle, I want to make full text search on the list using LINQ.
service is consumed in silverlight application.
Any suggestions pls.
How about this?
var found = thelist.Where(str => str.Contains(strToSearchFor));
or maybe this -
var found = thelist.Where(str => str.ToLower().Contains(strToSearchFor.ToLower()));
if it is not list of strings it would like like this:
var found = thelist.Where(obj => obj.strProperty.Contains(strToSearchFor));
If you need this solution to be case insensitive the solution of Hogan can be done without creatring a new string (by using the ToLower() method).
First, create an extension method:
public static class Extensions
{
public static bool Contains(this string source, string stringToMatch, StringComparison comparisonType)
{
return source.IndexOf(stringToMatch, comparisonType) >= 0;
}
}
Then you can make the Hogan solution case insensitive like this:
var found = thelist.Where(str => str.Contains(strToSearchFor, StringComparison.OrdinalIgnoreCase));