Skip to content

Commit f9dc7ef

Browse files
committed
Merge branch 'fts4gen'
2 parents c43c6dd + 3ae9071 commit f9dc7ef

File tree

6 files changed

+138
-70
lines changed

6 files changed

+138
-70
lines changed
84 KB
Binary file not shown.

core/src/androidTest/java/io/snabble/sdk/ProductDatabaseTest.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,32 @@ public void testBoostedPromotionsQuery() throws Throwable {
5454
}
5555

5656
@Test
57-
public void testTextSearch() {
57+
public void testTextSearch() throws IOException, SnabbleSdk.SnabbleException {
58+
setupSdkWithDb("demoDb_1_6.sqlite3");
59+
5860
ProductDatabase productDatabase = snabbleSdk.getProductDatabase();
59-
Cursor cursor = productDatabase.searchByFoldedName("muller", null);
61+
Cursor cursor = productDatabase.searchByFoldedName("gold", null);
6062
cursor.moveToFirst();
6163
Product product = productDatabase.productAtCursor(cursor);
62-
assertEquals(product.getSku(), "1");
63-
assertEquals(product.getName(), "Müllermilch Banane 0,4l");
64+
assertEquals(product.getSku(), "31");
65+
assertEquals(product.getName(), "Goldbären 200g");
66+
cursor.close();
67+
68+
cursor = productDatabase.searchByFoldedName("foo", null);
69+
assertEquals(0, cursor.getCount());
70+
cursor.close();
71+
}
72+
73+
@Test
74+
public void testTextSearchNoFTS() throws IOException, SnabbleSdk.SnabbleException {
75+
setupSdkWithDb("demoDb_1_6_no_fts.sqlite3");
76+
77+
ProductDatabase productDatabase = snabbleSdk.getProductDatabase();
78+
Cursor cursor = productDatabase.searchByFoldedName("gold", null);
79+
cursor.moveToFirst();
80+
Product product = productDatabase.productAtCursor(cursor);
81+
assertEquals(product.getSku(), "31");
82+
assertEquals(product.getName(), "Goldbären 200g");
6483
cursor.close();
6584

6685
cursor = productDatabase.searchByFoldedName("foo", null);

core/src/androidTest/java/io/snabble/sdk/SnabbleSdkTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static void setupMockWebServer() throws Exception {
5555

5656
final Dispatcher dispatcher = new Dispatcher() {
5757
@Override
58-
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
58+
public MockResponse dispatch(RecordedRequest request) {
5959
if (request.getPath().equals(metadataUrl)) {
6060
return new MockResponse()
6161
.addHeader("Content-Type", "application/json; charset=utf-8")
@@ -116,18 +116,19 @@ public void setupSdkWithDb(String testDbName) throws IOException, SnabbleSdk.Sna
116116
config.metadataUrl = metadataUrl;
117117
config.endpointBaseUrl = "http://" + mockWebServer.getHostName() + ":" + mockWebServer.getPort();
118118
config.productDbName = testDbName;
119+
config.generateSearchIndex = true;
119120

120121
snabbleSdk = SnabbleSdk.setupBlocking((Application) context.getApplicationContext(), config);
121122
}
122123

123124
@After
124-
public void teardownSdk() throws IOException {
125+
public void teardownSdk() {
125126
FileUtils.deleteQuietly(context.getFilesDir());
126127
FileUtils.deleteQuietly(new File(context.getFilesDir().getParentFile(), "/databases/"));
127128
}
128129

129130
@Test
130-
public void testSdkInitialization() throws IOException {
131+
public void testSdkInitialization() {
131132
String endpointBaseUrl = snabbleSdk.getEndpointBaseUrl();
132133
String metadataUrl = snabbleSdk.getMetadataUrl();
133134
String appDbUrl = snabbleSdk.getAppDbUrl();

core/src/main/java/io/snabble/sdk/ProductDatabase.java

Lines changed: 101 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public static class Config {
4646
int bundledSchemaVersionMajor;
4747
int bundledSchemaVersionMinor;
4848
boolean autoUpdateIfMissing;
49+
boolean generateSearchIndex;
4950
}
5051

5152
private SQLiteDatabase db;
@@ -64,6 +65,7 @@ public static class Config {
6465
private Date lastUpdateDate;
6566
private int schemaVersionMajor;
6667
private int schemaVersionMinor;
68+
private boolean generateSearchIndex;
6769

6870
private SnabbleSdk sdk;
6971
private Application application;
@@ -88,6 +90,7 @@ public static class Config {
8890
this.bundledRevisionId = config.bundledRevisionId;
8991
this.bundledSchemaVersionMajor = config.bundledSchemaVersionMajor;
9092
this.bundledSchemaVersionMinor = config.bundledSchemaVersionMinor;
93+
this.generateSearchIndex = config.generateSearchIndex;
9194

9295
this.productDatabaseDownloader = new ProductDatabaseDownloader(sdk, this);
9396
this.productApi = new ProductApi(sdk);
@@ -207,6 +210,7 @@ private boolean open(boolean allowCopyFromBundle) {
207210
Formatter.formatFileSize(application, size()));
208211
}
209212

213+
createFTSIndexIfNecessary();
210214
parseLastUpdateTimestamp();
211215
return true;
212216
} catch (Exception e) {
@@ -438,6 +442,79 @@ synchronized void applyFullUpdate(InputStream inputStream) throws IOException {
438442
Logger.d("Full update took %d ms", time2);
439443
}
440444

445+
private void createFTSIndexIfNecessary() {
446+
if(generateSearchIndex) {
447+
long time = SystemClock.elapsedRealtime();
448+
449+
synchronized (dbLock) {
450+
Cursor cursor;
451+
cursor = rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='searchByName'", null, null);
452+
boolean hasFTS = cursor != null && cursor.getCount() == 1;
453+
if(cursor != null){
454+
cursor.close();
455+
}
456+
457+
if(!hasFTS) {
458+
db.beginTransaction();
459+
460+
exec("DROP TABLE IF EXISTS searchByName");
461+
exec("CREATE VIRTUAL TABLE searchByName USING fts4(sku TEXT, foldedName TEXT)");
462+
463+
ContentValues contentValues = new ContentValues(2);
464+
cursor = rawQuery("SELECT sku, name FROM products", null, null);
465+
if (cursor != null) {
466+
while (cursor.moveToNext()) {
467+
contentValues.clear();
468+
contentValues.put("sku", cursor.getString(0));
469+
contentValues.put("foldedName", StringNormalizer.normalize(cursor.getString(1)));
470+
db.insert("searchByName", null, contentValues);
471+
}
472+
cursor.close();
473+
} else {
474+
Logger.d("Could not create FTS4 index, no products");
475+
}
476+
477+
db.setTransactionSuccessful();
478+
db.endTransaction();
479+
480+
Logger.d("Created FTS4 index in " + (SystemClock.elapsedRealtime() - time) + " ms");
481+
} else {
482+
Logger.d("Already has FTS4 index");
483+
}
484+
}
485+
}
486+
}
487+
488+
private Cursor rawQuery(String sql, String[] args, CancellationSignal cancellationSignal) {
489+
if (db == null) {
490+
return null;
491+
}
492+
493+
long time = SystemClock.elapsedRealtime();
494+
Cursor cursor;
495+
496+
synchronized (dbLock) {
497+
try {
498+
cursor = db.rawQuery(sql, args, cancellationSignal);
499+
} catch (Exception e) {
500+
// query could not be executed
501+
Logger.e(e.toString());
502+
return null;
503+
}
504+
}
505+
506+
// query executes when we call the first function that needs data, not on db.rawQuery
507+
int count = cursor.getCount();
508+
509+
long time2 = SystemClock.elapsedRealtime() - time;
510+
if (time2 > 16) {
511+
Logger.d("Query performance warning (%d ms, %d rows) for SQL: %s",
512+
time2, count, bindArgs(sql, args));
513+
}
514+
515+
return cursor;
516+
}
517+
441518
/**
442519
* Updates the current database in the background by requesting new data from the backend.
443520
* <p>
@@ -788,33 +865,35 @@ private Cursor productQuery(String appendSql, String[] args, boolean distinct) {
788865
return productQuery(appendSql, args, distinct, null);
789866
}
790867

791-
private Cursor rawQuery(String sql, String[] args, CancellationSignal cancellationSignal) {
792-
if (db == null) {
868+
/**
869+
* This function is deprecated and will be removed from the SDK in the future. There will be no
870+
* alternative function to find products by name.
871+
*
872+
* Find a product by its name. Matching is normalized, so "Apple" finds also "apple".
873+
*
874+
* @param name The name of the product.
875+
* @return The first product matching the name, otherwise null if no product was found.
876+
*/
877+
public Product findByName(String name) {
878+
if (name == null || name.length() == 0) {
793879
return null;
794880
}
795881

796-
long time = SystemClock.elapsedRealtime();
797-
Cursor cursor;
882+
StringNormalizer.normalize(name);
798883

799-
synchronized (dbLock) {
800-
try {
801-
cursor = db.rawQuery(sql, args, cancellationSignal);
802-
} catch (Exception e) {
803-
// query could not be executed
804-
return null;
805-
}
806-
}
884+
Cursor cursor = productQuery("JOIN searchByName ns ON ns.sku = p.sku " +
885+
"WHERE ns.foldedName MATCH ? LIMIT 1", new String[]{
886+
name
887+
}, false);
807888

808-
// query executes when we call the first function that needs data, not on db.rawQuery
809-
int count = cursor.getCount();
889+
return getFirstProductAndClose(cursor);
890+
}
810891

811-
long time2 = SystemClock.elapsedRealtime() - time;
812-
if (time2 > 16) {
813-
Logger.d("Query performance warning (%d ms, %d rows) for SQL: %s",
814-
time2, count, bindArgs(sql, args));
892+
private void exec(String sql){
893+
Cursor cursor = rawQuery(sql, null, null);
894+
if(cursor != null){
895+
cursor.close();
815896
}
816-
817-
return cursor;
818897
}
819898

820899
private String bindArgs(String sql, String[] args) {
@@ -1074,31 +1153,6 @@ public void findByWeighItemIdOnline(String weighItemId,
10741153
}
10751154
}
10761155

1077-
/**
1078-
* This function is deprecated and will be removed from the SDK in the future. There will be no
1079-
* alternative function to find products by name.
1080-
*
1081-
* Find a product by its name. Matching is normalized, so "Apple" finds also "apple".
1082-
*
1083-
* @param name The name of the product.
1084-
* @return The first product matching the name, otherwise null if no product was found.
1085-
*/
1086-
@Deprecated
1087-
public Product findByName(String name) {
1088-
if (name == null || name.length() == 0) {
1089-
return null;
1090-
}
1091-
1092-
StringNormalizer.normalize(name);
1093-
1094-
Cursor cursor = productQuery("JOIN searchByName s ON " + getSearchIndexColumn() + " = p.sku " +
1095-
"WHERE s.foldedName MATCH ? LIMIT 1", new String[]{
1096-
name
1097-
}, false);
1098-
1099-
return getFirstProductAndClose(cursor);
1100-
}
1101-
11021156
/**
11031157
* This function is deprecated and will be removed from the SDK in the future. There will be no
11041158
* alternative function to search for products.
@@ -1111,31 +1165,16 @@ public Product findByName(String name) {
11111165
*
11121166
* @param cancellationSignal Calls can be cancelled with a {@link CancellationSignal}. Can be null.
11131167
*/
1114-
@Deprecated
11151168
public Cursor searchByFoldedName(String searchString, CancellationSignal cancellationSignal) {
1116-
return productQuery("JOIN searchByName s ON " + getSearchIndexColumn() + " = p.sku " +
1117-
"WHERE s.foldedName MATCH ? " +
1169+
return productQuery("JOIN searchByName ns ON ns.sku = p.sku " +
1170+
"WHERE ns.foldedName MATCH ? " +
11181171
"AND p.weighing != " + Product.Type.PreWeighed.getDatabaseValue() + " " +
11191172
"AND p.isDeposit = 0 " +
11201173
"LIMIT 100", new String[]{
11211174
searchString + "*"
11221175
}, true, cancellationSignal);
11231176
}
11241177

1125-
private String getSearchIndexColumn() {
1126-
// older schema version used the docid field as a primary key
1127-
// we changed skus to be TEXT instead of INTEGER keys, so we cant use docid anymore
1128-
// to still support older version we set the field name here
1129-
String indexColumn;
1130-
1131-
if(schemaVersionMajor == 1 && schemaVersionMinor <= 4){
1132-
indexColumn = "s.docid";
1133-
} else {
1134-
indexColumn = "s.sku";
1135-
}
1136-
return indexColumn;
1137-
}
1138-
11391178
/**
11401179
* Returns a {@link Cursor} which can be iterated for items containing the given scannable code.
11411180
* <p>

core/src/main/java/io/snabble/sdk/SnabbleSdk.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ public static class Config {
123123
*/
124124
public boolean productDbDownloadIfMissing = true;
125125

126+
/**
127+
* If set to true, creates an full text index to support searching in the product database
128+
* using findByName or searchByName.
129+
*
130+
* Note that this increases setup time of the SDK and it is highly recommended to use
131+
* the non-blocking initialization function.
132+
*/
133+
public boolean generateSearchIndex = false;
134+
126135
/**
127136
* Optional SSLSocketFactory that gets used for HTTP requests.
128137
*
@@ -384,6 +393,7 @@ private void setupSdk(Config config, final SetupCompletionListener setupCompleti
384393
dbConfig.bundledSchemaVersionMajor = config.productDbBundledSchemaVersionMajor;
385394
dbConfig.bundledSchemaVersionMinor = config.productDbBundledSchemaVersionMinor;
386395
dbConfig.autoUpdateIfMissing = config.productDbDownloadIfMissing;
396+
dbConfig.generateSearchIndex = config.generateSearchIndex;
387397

388398
productDatabase = new ProductDatabase(this,
389399
config.productDbName,

sample/src/main/java/io/snabble/testapp/App.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.io.IOException;
1212
import java.nio.charset.Charset;
1313

14-
import io.snabble.sdk.PaymentMethod;
1514
import io.snabble.sdk.SnabbleSdk;
1615
import io.snabble.sdk.ui.SnabbleUI;
1716
import io.snabble.sdk.ui.telemetry.Telemetry;

0 commit comments

Comments
 (0)