@@ -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>
0 commit comments