Querying The Offline Store¶
End users can run various OData system queries like $search and $filter on the local offline store after the initial download.
The Search System Query¶
If the property type of an entity is Edm.String
, then the property is searchable. By default, all searchable properties of the entity are scanned for a given search term using a LIKE SQL query. If there are many searchable properties, the search may be time-consuming. To improve performance, selective search is supported, limiting searches to selected properties.
Offline OData supports the $search system query with the following capabilities and limitations:
-
Capabilities:
- Searching simple properties of the type
Edm.String
. - Searching collection-typed properties.
- Searching simple properties of the type
-
Limitations:
- Searching dynamic properties is only possible if the underlying entity type is
open
. However, selective search is not supported for dynamic properties, meaning all dynamic properties will be searched even if only one dynamic property is selected. - If no searchable property is selected or if the targeting entity type has no searchable properties, the $search query option will be ignored when generating the query result.
- Searching dynamic properties is only possible if the underlying entity type is
The Search OData API¶
The following OData Core APIs are designed for the $search
query:
It's worth noting that Offline OData supports prefix search introduced in SAPOData. A search term ending with a star (*) indicates prefix matching.
The examples below illustrate how to apply the $search query on the local store.
// search from the Email property of the Customer entity type
EntitySet customerSet = dataService.getEntitySet("Customers");
EntityType customerType = customerSet.getEntityType();
Property emailProp = customerType.getProperty("Email");
// basic search term
SearchTerm searchTerm = SearchTerm.word("Jenni");
DataQuery searchQuery = new DataQuery().from(customerSet).select(emailProp).search(searchTerm);
EntityValueList entities = dataService.executeQuery(searchQuery).getEntityList();
// prefix search
searchTerm = SearchTerm.prefix("Jenni");
searchQuery = new DataQuery().from(customerSet).select(emailProp).search(searchTerm);
entities = dataService.executeQuery(searchQuery).getEntityList();
// search phrase
StringList stringList = new StringList();
stringList.add("Jennifer@sap.com");
searchQuery = new DataQuery().from(customerSet).select(emailProp).search(SearchTerm.phrase(stringList));
entities = dataService.executeQuery(searchQuery).getEntityList();
// logical search operator
searchQuery = new DataQuery().from(customerSet).search(SearchTerm.word("Jenni").or(SearchTerm.word("Victor")));
entities = dataService.executeQuery(searchQuery).getEntityList();
// search from the Email property of the Customer entity type
val customerSet = dataService.getEntitySet("Customers")
val customerType = customerSet.entityType
val emailProp = customerType.getProperty("Email")
// basic search term
var searchTerm = SearchTerm.word("Jenni")
var searchQuery = DataQuery().from(customerSet).select(emailProp).search(searchTerm)
var entities = dataService.executeQuery(searchQuery).getEntityList()
// prefix search
searchTerm = SearchTerm.prefix("Jenni")
searchQuery = DataQuery().from(customerSet).select(emailProp).search(searchTerm)
entities = dataService.executeQuery(searchQuery).getEntityList()
// search phrase
val stringList = StringList()
stringList.add("Jennifer@sap.com")
searchQuery = DataQuery().from(customerSet).select(emailProp).search(SearchTerm.phrase(stringList))
entities = dataService.executeQuery(searchQuery).getEntityList()
// logical search operator
searchQuery = DataQuery().from(customerSet).search(SearchTerm.word("Jenni").or(SearchTerm.word("Victor")))
entities = dataService.executeQuery(searchQuery).getEntityList()
// search from the Email property of the Customer entity type
let customerSet: EntitySet = dataService.entitySet(withName:"Customers")
let customerType: EntityType = customerSet.entityType
let emailProp = customerType.property(withName:"Email")
// basic search term
var searchTerm : SearchTerm = SearchTerm.word("Jenni")
var searchQuery = DataQuery().from(customerSet).select(emailProp).search(searchTerm)
var entities = try dataService.executeQuery(searchQuery).entityList()
// prefix search
searchTerm = SearchTerm.prefix("Jenni")
searchQuery = DataQuery().from(customerSet).select(emailProp).search(searchTerm)
entities = try dataService.executeQuery(searchQuery).entityList()
// search phrase
let stringList : StringList = StringList()
stringList.append("Jennifer@sap.com")
searchQuery = DataQuery().from(customerSet).select(emailProp).search(SearchTerm.phrase(stringList))
entities = try dataService.executeQuery(searchQuery).entityList()
// logical search operator
searchQuery = DataQuery().from(customerSet).search(SearchTerm.word("Jenni").or(SearchTerm.word("Victor")));
entities = try dataService.executeQuery(searchQuery).entityList()
Note
$search system query with an empty search term (E.g. $search="" ) is ignored and will be removed while querying the local store. It is mandatory to provide a valid search term or search phrase to the $search system query option.
The Search Case Sensitivity¶
By default, the UltraLite database uses a case-sensitive collation, which makes the search functionality case-sensitive as well. You can modify the case sensitivity in the mobile services cockpit; however, this option only affects new databases. The collation of existing UltraLite databases cannot be changed. If the collation is set to case-insensitive in the mobile services cockpit, search conditions will also be case-insensitive.
For an existing case-sensitive database, you can perform a case-insensitive search for a single character. For example, the query $search=[Aa]bc
will search for both Abc
and abc
from the local store.
The Underlying UltraLite Search SQL¶
The UltraLite LIKE SQL operator is used to search within searchable columns. For more information on the restrictions of the LIKE search, refer to [UltraLite LIKE Search Condition] (https://help.sap.com/docs/SAP_SQL_Anywhere/7eca48cee41b418bbd54feff83d57803/82719aea6ce21014ad0dfc15ee220514.html).
Terms can be searched using a LIKE query with the string %term% in the selected searchable columns.
The following characters have special meaning in UltraLite LIKE query:
_ : The underscore (_) matches any one character. For example, "a_" matches "ab" and "a5", but not "a".
% : The percent (%) will match any string with zero or more characters. For example, "go%" matches "goes" and "goods".
[] : The [] (square brackets)matches any single character in the specified range or set. For example, "[BT]oy" matches "Boy" or "Toy". [%] matches only the percent character (the percent character does not act as a wildcard in this context), and [_] matches only the underscore character.
Search Performance Considerations¶
Performance of a search query is linear to the number of rows in the table multiplied by the number of columns being searched. Therefore, performance may degrade with larger datasets. To boost the performance, it is necessary to index the columns being searched and utilize prefix search to leverage the index efficiently. For example, if there are 1 million rows in the Customers entity set, and the property email of type Edm.String
is indexed, using a prefix search query like $select=Email&$search=Emily*
will benefit significantly from the index. If the index is not present, and the prefix search is not applicable, it is recommended to use the search query together with other filters to limit the rows to search.
Debugging Search With ILOData¶
You can use the $search system query within the ILOData command line utility. Here are a few ILOData query examples:
- Basic search query:
get Customers?$search=abc
- Search query with logical operator AND, OR, NOT:
get Customers?$search=abc AND (NOT def) OR xyz
- Search query with parenthesis:
get Customers?$search=abc AND (def OR xyz)
- Search query with NOT operator:
get Customers?$search=NOT def
Note
AND, NOT, OR are uppercase logical operators for the $search system query.
Offline OData Query Functions¶
The Offline OData supports the below functions for the standard OData $filter system query options.
cast
ceiling
concat
day
date
endswith
floor
fractionalseconds
hour
indexof
isof
length
minute
month
now
round
second
startswith
substring
substringof
contains
time
tolower
totaloffsetminutes
totalseconds
toupper
trim
year
Besides the above standard query functions, Offline OData supports the following query functions specially designed for the Offline OData use case.
sap.entityexists
sap.islocal
sap.hasPendingChanges
sap.inerrorstate
sap.upsertedlastdownload
The sap.entityexists
Function¶
The sap.entityexists
is the Offline OData specific function to check if a related entity exists.
The following example illustrates how to use the sap.entityexists
query function on the local store.
EntitySet customerSet = dataService.getEntitySet("Customers");
EntityType customerType = customerSet.getEntityType();
Property ordersNavigationProp = customerType.getProperty("Orders");
DataPath dataPathToOrders = DataPath.of(ordersNavigationProp)
// GET Customers?$filter=sap.entityexists(Orders)
DataQuery searchQuery = new DataQuery().from(customerSet).filter(OfflineODataQueryFunction.entityExists(dataPathToOrders));
EntityValueList entities = dataService.executeQuery(searchQuery).getEntityList();
val customerSet = dataService.getEntitySet("Customers")
val customerType = customerSet.entityType
val ordersNavigationProp = customerType.getProperty("Orders")
var dataPathToOrders = DataPath.of(ordersNavigationProp)
// GET Customers?$filter=sap.entityexists(Orders)
var searchQuery = DataQuery().from(customerSet).filter(OfflineODataQueryFunction.entityExists(dataPathToOrders))
var entities = dataService.executeQuery(searchQuery).getEntityList()
let customerSet: EntitySet = dataService.entitySet(withName:"Customers")
let customerType: EntityType = customerSet.entityType
let ordersNavigationProp = customerType.property(withName:"Orders")
let dataPathToOrders = DataPath.of(ordersNavigationProp)
// GET Customers?$filter=sap.entityexists(Orders)
let searchQuery = DataQuery().from(customerSet).filter(OfflineODataQueryFunction.entityExists(dataPathToOrders))
let entities = try dataService.executeQuery(searchQuery).entityList()
Note
ILOData command line utility supports the function sap.entityexists
. Below is an example ILOData query: GET Customers?$filter=sap.entityexists(Orders)
The sap.islocal
Function¶
The sap.islocal
is a Offline OData specific function to check if an entity is local. An entity is local if there are changes that have been made to the entity that have not yet been downloaded from the back end, regardless of whether or not those operations have been sent to the back end.
For more information, refer to Local Information
Note
The sap.islocal
function is supported by the ILOData command line utility. Below is an example ILOData query: GET Customers?$filter=not sap.islocal()
The sap.hasPendingChanges
Function¶
The sap.hasPendingChanges
is the Offline OData specific function to check if an entity has any pending change requests in the request queue. The sap.hasPendingChanges
is different from sap.islocal
based on whether or not the change requests have been sent to the back end. An entity that has changes in the request queue that have not been sent will be both local and pending. When the changes in the request queue have been sent to the back end but the results have not been received by a download operation, the entity will be local but not pending.
For more information, refer to Pending Information.
Note
The sap.hasPendingChanges
function is supported by the ILOData command line utility. Below is an example ILOData query: GET Customers?$filter=sap.hasPendingChanges()
The sap.inerrorstate
Function¶
The sap.inerrorstate
is the Offline OData specific function to check if an entity is in the error state.
For more information, refer to Finding Entities in Error State.
Note
The sap.inerrorstate
function is supported by the ILOData command line utility. Below is an example ILOData query: GET Customers?$filter=sap.inerrorstate()
The sap.upsertedlastdownload
Function¶
The sap.upsertedlastdownload
is the Offline OData specific function to check if an entity was inserted or updated in the last download.
The following example illustrates how to use the sap.upsertedlastdownload
query function on the local store.
EntitySet customerSet = dataService.getEntitySet("Customers");
// GET Customers?$filter=sap.upsertedlastdownload()
DataQuery searchQuery = new DataQuery().from(customerSet).filter(OfflineODataQueryFunction.upsertedLastDownload());
EntityValueList entities = dataService.executeQuery(searchQuery).getEntityList();
val customerSet = dataService.getEntitySet("Customers")
// GET Customers?$filter=sap.upsertedlastdownload()
var searchQuery = DataQuery().from(customerSet).filter(OfflineODataQueryFunction.upsertedLastDownload())
var entities = dataService.executeQuery(searchQuery).getEntityList()
let customerSet: EntitySet = dataService.entitySet(withName:"Customers")
// GET Customers?$filter=sap.upsertedlastdownload()
let searchQuery = DataQuery().from(customerSet).filter(OfflineODataQueryFunction.upsertedLastDownload())
let entities = try dataService.executeQuery(searchQuery).entityList()
Note
The sap.upsertedlastdownload
function is supported by the ILOData command line utility. Below is an example ILOData query: GET Customers?$filter=sap.upsertedlastdownload()