10
votes

I have an entity called lastName with value "Benjamin". Is there a way in objectify that if the user put "Ben" or "jam" or "Benja". I still be able to find this entity using query.filter(). I must use the query as there are other search criteria iam checking.

I saw something in "Obgaektify" called "starts with" operator. but it isnot working. Any suggestions would be appreciated. Thanks

4
Why would you want to do a substring search within names? I've never seen a good use-case for this. - Nick Johnson
It isnot within names actully. It is within text search in general. but for simplicity i just said name :) - Shady Hussein
Well, the question remained. Fulltext search does stemming and normalization, then searches on whole words, because there's very little point in having a query for 'cat' return 'defecate'. - Nick Johnson
Well right now the requirements changed, I only need to check if it starts with the given text. like "benjamin" starts with "ben" not "jam" or anything else. So i will try the solution posted below and see the results. Thanks for your help - Shady Hussein

4 Answers

12
votes

There's no "LIKE" type queries for sub-string, however a case sensitive "starts with" could be simulated by taking advantage of the > and < operators on indexes.

// The start string
String searchStr = "Ben";

// emulate a "starts with" query
Query q = new Query("MyEntity")
q.addFilter("name", Query.FilterOperator.GREATER_THAN_OR_EQUAL, searchStr);
q.addFilter("name", Query.FilterOperator.LESS_THAN, searchStr + "\ufffd");

The query will 'search' the name property for items that begining with "Ben", and are less than "Ben\ufffd", where \ufffd is the highest possible unicode character.

2
votes

There is no standard existing index for contains-like queries. Btw, you can always introduce your own. At this case you can do:

  1. add and synthetic field like String[] lastNameIndex
  2. add method marked as @PrePersist that will fill lastNameIndex field with all available combinations
  3. When you want to find entities using this index do query.filter('lastNameIndex =', val)
0
votes

Putting the answer from Chris and the comment from Nick together, here is the code to build a query filter for objectify V4:

public <T> Query<T> fieldStartsWith(Query<T> query, String field, String search){
    query = query.filter(field + " >=", search);
    return query.filter(field + " <", searchStr+"\ufffd");
}
0
votes

I have used the tokenization method. Here is the code in Java:

private String tokenize(String phrase) {
StringBuilder tokens = new StringBuilder();
try {
  for (String word : phrase.split(" ")) {
    if (word.length() < 1) {
      continue;
    }
    int j = 1;
    while (true) {
      for (int i = 0; i < word.length() - j + 1; i++) {
        tokens.append(word.substring(i, i + j)).append(" ");
      }
      if (j == word.length()) {
        break;
      }
      j++;
    }
  }
} catch (Throwable t) {
  t.printStackTrace();
}
return tokens.toString();}

This allows to define an indexable field, then process standard Ofy queries and SearchService.