diff --git a/app/src/main/java/org/gnucash/android/db/DatabaseHelper.java b/app/src/main/java/org/gnucash/android/db/DatabaseHelper.java index c254a2e96..30a312445 100644 --- a/app/src/main/java/org/gnucash/android/db/DatabaseHelper.java +++ b/app/src/main/java/org/gnucash/android/db/DatabaseHelper.java @@ -32,6 +32,8 @@ import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; +import androidx.annotation.NonNull; + import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.model.Commodity; import org.xml.sax.SAXException; @@ -353,4 +355,24 @@ private void createDatabaseTables(SQLiteDatabase db) { throw new RuntimeException(e); } } + + /** + * Escape the given argument for use in a {@code LIKE} statement. + * @hide + */ + public static String escapeForLike(@NonNull String arg) { + // Shamelessly borrowed from com.android.providers.media.util.DatabaseUtils + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arg.length(); i++) { + final char c = arg.charAt(i); + switch (c) { + case '%': sb.append('\\'); + break; + case '_': sb.append('\\'); + break; + } + sb.append(c); + } + return sb.toString(); + } } diff --git a/app/src/main/java/org/gnucash/android/db/adapter/TransactionsDbAdapter.java b/app/src/main/java/org/gnucash/android/db/adapter/TransactionsDbAdapter.java index ac8651940..88e4f2dd6 100644 --- a/app/src/main/java/org/gnucash/android/db/adapter/TransactionsDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/adapter/TransactionsDbAdapter.java @@ -17,6 +17,7 @@ package org.gnucash.android.db.adapter; +import static org.gnucash.android.db.DatabaseHelper.escapeForLike; import static org.gnucash.android.db.DatabaseSchema.AccountEntry; import static org.gnucash.android.db.DatabaseSchema.ScheduledActionEntry; import static org.gnucash.android.db.DatabaseSchema.SplitEntry; @@ -24,6 +25,7 @@ import android.content.ContentValues; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; @@ -555,13 +557,17 @@ public Cursor fetchTransactionSuggestions(String prefix, String accountUID) { String[] projectionIn = new String[]{TransactionEntry.TABLE_NAME + ".*"}; String selection = "(" + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID + " = ?" + " OR " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TEMPLATE + "=1 )" - + " AND " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_DESCRIPTION + " LIKE '" + prefix + "%'"; + + " AND " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_DESCRIPTION + " LIKE '" + escapeForLike(prefix) + "%'"; String[] selectionArgs = new String[]{accountUID}; String sortOrder = TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " DESC"; + String subquery = queryBuilder.buildQuery(projectionIn, selection, null, null, sortOrder, null); + + // Need to use inner subquery because ORDER BY must be before GROUP BY! + SQLiteQueryBuilder queryBuilder2 = new SQLiteQueryBuilder(); + queryBuilder2.setTables("(" + subquery + ")"); String groupBy = TransactionEntry.COLUMN_DESCRIPTION; String limit = Integer.toString(5); - - return queryBuilder.query(mDb, projectionIn, selection, selectionArgs, groupBy, null, sortOrder, limit); + return queryBuilder2.query(mDb, null, null, selectionArgs, groupBy, null, null, limit); } /** diff --git a/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java b/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java index fd67d88f1..48f78e257 100644 --- a/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java +++ b/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java @@ -16,6 +16,7 @@ package org.gnucash.android.ui.account; +import static org.gnucash.android.db.DatabaseHelper.escapeForLike; import static org.gnucash.android.util.ColorExtKt.parseColor; import android.app.Activity; @@ -461,7 +462,7 @@ public Cursor loadInBackground() { if (mFilter != null) { cursor = adapter .fetchAccounts(DatabaseSchema.AccountEntry.COLUMN_HIDDEN + "= 0 AND " - + DatabaseSchema.AccountEntry.COLUMN_NAME + " LIKE '%" + mFilter + "%'", + + DatabaseSchema.AccountEntry.COLUMN_NAME + " LIKE '%" + escapeForLike(mFilter) + "%'", null, null); } else if (!TextUtils.isEmpty(mParentAccountUID)) cursor = adapter.fetchSubAccounts(mParentAccountUID);