blob: a73a71946bc76874125a3971ce4e2a418b9adc96 [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.database.sqlite;
18
Jeff Sharkeyb13ea302018-07-25 14:52:14 -060019import android.annotation.NonNull;
20import android.annotation.Nullable;
Mathew Inwood41b31942018-08-10 16:00:53 +010021import android.annotation.UnsupportedAppUsage;
Jeff Sharkeyb13ea302018-07-25 14:52:14 -060022import android.content.ContentValues;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070023import android.database.Cursor;
24import android.database.DatabaseUtils;
Jeff Sharkeyb13ea302018-07-25 14:52:14 -060025import android.os.Build;
Jeff Browna7771df2012-05-07 20:06:46 -070026import android.os.CancellationSignal;
27import android.os.OperationCanceledException;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070028import android.provider.BaseColumns;
29import android.text.TextUtils;
Jeff Sharkeyb13ea302018-07-25 14:52:14 -060030import android.util.ArrayMap;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070031import android.util.Log;
32
Jeff Sharkey42122bf2018-07-26 09:39:18 -060033import libcore.util.EmptyArray;
Jeff Sharkeyb13ea302018-07-25 14:52:14 -060034
35import java.util.Arrays;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070036import java.util.Iterator;
Jeff Sharkey7049e652018-09-13 17:05:07 -060037import java.util.List;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070038import java.util.Map;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070039import java.util.Map.Entry;
Jeff Sharkeyb13ea302018-07-25 14:52:14 -060040import java.util.Objects;
Daniel Lehmann50b1f8d2011-06-01 15:24:23 -070041import java.util.Set;
Owen Linab18d1f2009-05-06 16:45:59 -070042import java.util.regex.Pattern;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070043
44/**
Joshua Baxter3639e2f2018-03-26 14:55:14 -070045 * This is a convenience class that helps build SQL queries to be sent to
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070046 * {@link SQLiteDatabase} objects.
47 */
Jeff Sharkeyb89df9e2018-07-26 14:36:59 -060048public class SQLiteQueryBuilder {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070049 private static final String TAG = "SQLiteQueryBuilder";
Owen Linab18d1f2009-05-06 16:45:59 -070050 private static final Pattern sLimitPattern =
51 Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070052
53 private Map<String, String> mProjectionMap = null;
Jeff Sharkey7049e652018-09-13 17:05:07 -060054 private List<Pattern> mProjectionGreylist = null;
55
Mathew Inwood31755f92018-12-20 13:53:36 +000056 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070057 private String mTables = "";
Mathew Inwood31755f92018-12-20 13:53:36 +000058 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Brad Fitzpatrickae6cdd12010-03-14 11:38:06 -070059 private StringBuilder mWhereClause = null; // lazily created
Mathew Inwood31755f92018-12-20 13:53:36 +000060 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070061 private boolean mDistinct;
62 private SQLiteDatabase.CursorFactory mFactory;
Daniel Lehmann50b1f8d2011-06-01 15:24:23 -070063 private boolean mStrict;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070064
65 public SQLiteQueryBuilder() {
66 mDistinct = false;
67 mFactory = null;
68 }
69
70 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -070071 * Mark the query as {@code DISTINCT}.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070072 *
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -070073 * @param distinct if true the query is {@code DISTINCT}, otherwise it isn't
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070074 */
75 public void setDistinct(boolean distinct) {
76 mDistinct = distinct;
77 }
78
79 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -070080 * Get if the query is marked as {@code DISTINCT}, as last configured by
Jeff Sharkey0ec586b2019-02-14 15:29:16 -070081 * {@link #setDistinct(boolean)}.
82 */
83 public boolean getDistinct() {
84 return mDistinct;
85 }
86
87 /**
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070088 * Returns the list of tables being queried
89 *
90 * @return the list of tables being queried
91 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -070092 public @Nullable String getTables() {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070093 return mTables;
94 }
95
96 /**
97 * Sets the list of tables to query. Multiple tables can be specified to perform a join.
98 * For example:
99 * setTables("foo, bar")
100 * setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)")
101 *
102 * @param inTables the list of tables to query on
103 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700104 public void setTables(@Nullable String inTables) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700105 mTables = inTables;
106 }
107
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700108 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700109 * Append a chunk to the {@code WHERE} clause of the query. All chunks appended are surrounded
110 * by parenthesis and {@code AND}ed with the selection passed to {@link #query}. The final
111 * {@code WHERE} clause looks like:
112 * <p>
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700113 * WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
114 *
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700115 * @param inWhere the chunk of text to append to the {@code WHERE} clause.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700116 */
Jeff Sharkeyb89df9e2018-07-26 14:36:59 -0600117 public void appendWhere(@NonNull CharSequence inWhere) {
Brad Fitzpatrickae6cdd12010-03-14 11:38:06 -0700118 if (mWhereClause == null) {
119 mWhereClause = new StringBuilder(inWhere.length() + 16);
120 }
Jeff Sharkey6adc98c2018-07-12 19:47:49 -0600121 mWhereClause.append(inWhere);
Jeff Sharkey6adc98c2018-07-12 19:47:49 -0600122 }
123
124 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700125 * Append a chunk to the {@code WHERE} clause of the query. All chunks appended are surrounded
Jeff Sharkey91be9262018-07-19 09:30:16 -0600126 * by parenthesis and ANDed with the selection passed to {@link #query}. The final
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700127 * {@code WHERE} clause looks like:
128 * <p>
Jeff Sharkey91be9262018-07-19 09:30:16 -0600129 * WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
Jeff Sharkey99cc1182018-07-16 14:34:21 -0600130 *
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700131 * @param inWhere the chunk of text to append to the {@code WHERE} clause. it will be escaped
Jeff Sharkey91be9262018-07-19 09:30:16 -0600132 * to avoid SQL injection attacks
Jeff Sharkey99cc1182018-07-16 14:34:21 -0600133 */
Jeff Sharkeyb89df9e2018-07-26 14:36:59 -0600134 public void appendWhereEscapeString(@NonNull String inWhere) {
Jeff Sharkey99cc1182018-07-16 14:34:21 -0600135 if (mWhereClause == null) {
136 mWhereClause = new StringBuilder(inWhere.length() + 16);
137 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700138 DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
139 }
140
141 /**
Jeff Sharkeyb89df9e2018-07-26 14:36:59 -0600142 * Add a standalone chunk to the {@code WHERE} clause of this query.
143 * <p>
144 * This method differs from {@link #appendWhere(CharSequence)} in that it
145 * automatically appends {@code AND} to any existing {@code WHERE} clause
146 * already under construction before appending the given standalone
147 * expression wrapped in parentheses.
148 *
149 * @param inWhere the standalone expression to append to the {@code WHERE}
150 * clause. It will be wrapped in parentheses when it's appended.
151 */
152 public void appendWhereStandalone(@NonNull CharSequence inWhere) {
153 if (mWhereClause == null) {
154 mWhereClause = new StringBuilder(inWhere.length() + 16);
155 }
156 if (mWhereClause.length() > 0) {
157 mWhereClause.append(" AND ");
158 }
159 mWhereClause.append('(').append(inWhere).append(')');
160 }
161
162 /**
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700163 * Sets the projection map for the query. The projection map maps
164 * from column names that the caller passes into query to database
165 * column names. This is useful for renaming columns as well as
166 * disambiguating column names when doing joins. For example you
167 * could map "name" to "people.name". If a projection map is set
168 * it must contain all column names the user may request, even if
169 * the key and value are the same.
170 *
171 * @param columnMap maps from the user column names to the database column names
172 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700173 public void setProjectionMap(@Nullable Map<String, String> columnMap) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700174 mProjectionMap = columnMap;
175 }
176
177 /**
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700178 * Gets the projection map for the query, as last configured by
179 * {@link #setProjectionMap(Map)}.
180 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700181 public @Nullable Map<String, String> getProjectionMap() {
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700182 return mProjectionMap;
183 }
184
185 /**
Jeff Sharkey7049e652018-09-13 17:05:07 -0600186 * Sets a projection greylist of columns that will be allowed through, even
187 * when {@link #setStrict(boolean)} is enabled. This provides a way for
188 * abusive custom columns like {@code COUNT(*)} to continue working.
189 *
190 * @hide
191 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700192 public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) {
Jeff Sharkey7049e652018-09-13 17:05:07 -0600193 mProjectionGreylist = projectionGreylist;
194 }
195
196 /**
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700197 * Gets the projection greylist for the query, as last configured by
198 * {@link #setProjectionGreylist(List)}.
199 *
200 * @hide
201 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700202 public @Nullable List<Pattern> getProjectionGreylist() {
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700203 return mProjectionGreylist;
204 }
205
206 /**
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700207 * Sets the cursor factory to be used for the query. You can use
208 * one factory for all queries on a database but it is normally
Jeff Brown75ea64f2012-01-25 19:37:13 -0800209 * easier to specify the factory when doing this query.
210 *
211 * @param factory the factory to use.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700212 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700213 public void setCursorFactory(@Nullable SQLiteDatabase.CursorFactory factory) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700214 mFactory = factory;
215 }
216
217 /**
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700218 * Sets the cursor factory to be used for the query, as last configured by
219 * {@link #setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory)}.
220 */
Jeff Sharkeya30e5c32019-02-28 12:02:10 -0700221 public @Nullable SQLiteDatabase.CursorFactory getCursorFactory() {
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700222 return mFactory;
223 }
224
225 /**
Daniel Lehmann50b1f8d2011-06-01 15:24:23 -0700226 * When set, the selection is verified against malicious arguments.
227 * When using this class to create a statement using
228 * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
229 * non-numeric limits will raise an exception. If a projection map is specified, fields
230 * not in that map will be ignored.
231 * If this class is used to execute the statement directly using
232 * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
233 * or
234 * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
235 * additionally also parenthesis escaping selection are caught.
236 *
237 * To summarize: To get maximum protection against malicious third party apps (for example
238 * content provider consumers), make sure to do the following:
239 * <ul>
240 * <li>Set this value to true</li>
241 * <li>Use a projection map</li>
242 * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
243 * </ul>
244 * By default, this value is false.
245 */
Jeff Sharkey91be9262018-07-19 09:30:16 -0600246 public void setStrict(boolean flag) {
247 mStrict = flag;
Dmitri Plotnikov40eb4aa2010-04-14 16:09:46 -0700248 }
249
250 /**
Jeff Sharkey0ec586b2019-02-14 15:29:16 -0700251 * Get if the query is marked as strict, as last configured by
252 * {@link #setStrict(boolean)}.
253 */
254 public boolean getStrict() {
255 return mStrict;
256 }
257
258 /**
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700259 * Build an SQL query string from the given clauses.
260 *
261 * @param distinct true if you want each row to be unique, false otherwise.
262 * @param tables The table names to compile the query against.
263 * @param columns A list of which columns to return. Passing null will
264 * return all columns, which is discouraged to prevent reading
265 * data from storage that isn't going to be used.
266 * @param where A filter declaring which rows to return, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700267 * {@code WHERE} clause (excluding the {@code WHERE} itself). Passing {@code null} will
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700268 * return all rows for the given URL.
269 * @param groupBy A filter declaring how to group rows, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700270 * {@code GROUP BY} clause (excluding the {@code GROUP BY} itself). Passing {@code null}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700271 * will cause the rows to not be grouped.
272 * @param having A filter declare which row groups to include in the cursor,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700273 * if row grouping is being used, formatted as an SQL {@code HAVING}
274 * clause (excluding the {@code HAVING} itself). Passing null will cause
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700275 * all row groups to be included, and is required when row
276 * grouping is not being used.
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700277 * @param orderBy How to order the rows, formatted as an SQL {@code ORDER BY} clause
278 * (excluding the {@code ORDER BY} itself). Passing null will use the
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700279 * default sort order, which may be unordered.
280 * @param limit Limits the number of rows returned by the query,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700281 * formatted as {@code LIMIT} clause. Passing null denotes no {@code LIMIT} clause.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700282 * @return the SQL query string
283 */
284 public static String buildQueryString(
285 boolean distinct, String tables, String[] columns, String where,
286 String groupBy, String having, String orderBy, String limit) {
287 if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
288 throw new IllegalArgumentException(
289 "HAVING clauses are only permitted when using a groupBy clause");
290 }
Owen Linab18d1f2009-05-06 16:45:59 -0700291 if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
292 throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
293 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700294
295 StringBuilder query = new StringBuilder(120);
296
297 query.append("SELECT ");
298 if (distinct) {
299 query.append("DISTINCT ");
300 }
301 if (columns != null && columns.length != 0) {
302 appendColumns(query, columns);
303 } else {
304 query.append("* ");
305 }
306 query.append("FROM ");
307 query.append(tables);
308 appendClause(query, " WHERE ", where);
309 appendClause(query, " GROUP BY ", groupBy);
310 appendClause(query, " HAVING ", having);
311 appendClause(query, " ORDER BY ", orderBy);
Owen Linab18d1f2009-05-06 16:45:59 -0700312 appendClause(query, " LIMIT ", limit);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700313
314 return query.toString();
315 }
316
317 private static void appendClause(StringBuilder s, String name, String clause) {
318 if (!TextUtils.isEmpty(clause)) {
319 s.append(name);
320 s.append(clause);
321 }
322 }
323
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700324 /**
325 * Add the names that are non-null in columns to s, separating
326 * them with commas.
327 */
328 public static void appendColumns(StringBuilder s, String[] columns) {
329 int n = columns.length;
330
331 for (int i = 0; i < n; i++) {
332 String column = columns[i];
333
334 if (column != null) {
335 if (i > 0) {
336 s.append(", ");
337 }
338 s.append(column);
339 }
340 }
341 s.append(' ');
342 }
343
344 /**
345 * Perform a query by combining all current settings and the
346 * information passed into this method.
347 *
348 * @param db the database to query on
Jeff Sharkey91be9262018-07-19 09:30:16 -0600349 * @param projectionIn A list of which columns to return. Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700350 * null will return all columns, which is discouraged to prevent
351 * reading data from storage that isn't going to be used.
352 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700353 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700354 * itself). Passing null will return all rows for the given URL.
355 * @param selectionArgs You may include ?s in selection, which
356 * will be replaced by the values from selectionArgs, in order
357 * that they appear in the selection. The values will be bound
358 * as Strings.
359 * @param groupBy A filter declaring how to group rows, formatted
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700360 * as an SQL {@code GROUP BY} clause (excluding the {@code GROUP BY}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700361 * itself). Passing null will cause the rows to not be grouped.
362 * @param having A filter declare which row groups to include in
363 * the cursor, if row grouping is being used, formatted as an
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700364 * SQL {@code HAVING} clause (excluding the {@code HAVING} itself). Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700365 * null will cause all row groups to be included, and is
366 * required when row grouping is not being used.
367 * @param sortOrder How to order the rows, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700368 * {@code ORDER BY} clause (excluding the {@code ORDER BY} itself). Passing null
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700369 * will use the default sort order, which may be unordered.
370 * @return a cursor over the result set
371 * @see android.content.ContentResolver#query(android.net.Uri, String[],
372 * String, String[], String)
373 */
Jeff Sharkey91be9262018-07-19 09:30:16 -0600374 public Cursor query(SQLiteDatabase db, String[] projectionIn,
375 String selection, String[] selectionArgs, String groupBy,
376 String having, String sortOrder) {
377 return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800378 null /* limit */, null /* cancellationSignal */);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700379 }
380
381 /**
382 * Perform a query by combining all current settings and the
383 * information passed into this method.
384 *
385 * @param db the database to query on
Jeff Sharkey91be9262018-07-19 09:30:16 -0600386 * @param projectionIn A list of which columns to return. Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700387 * null will return all columns, which is discouraged to prevent
388 * reading data from storage that isn't going to be used.
389 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700390 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700391 * itself). Passing null will return all rows for the given URL.
392 * @param selectionArgs You may include ?s in selection, which
393 * will be replaced by the values from selectionArgs, in order
394 * that they appear in the selection. The values will be bound
395 * as Strings.
396 * @param groupBy A filter declaring how to group rows, formatted
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700397 * as an SQL {@code GROUP BY} clause (excluding the {@code GROUP BY}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700398 * itself). Passing null will cause the rows to not be grouped.
399 * @param having A filter declare which row groups to include in
400 * the cursor, if row grouping is being used, formatted as an
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700401 * SQL {@code HAVING} clause (excluding the {@code HAVING} itself). Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700402 * null will cause all row groups to be included, and is
403 * required when row grouping is not being used.
404 * @param sortOrder How to order the rows, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700405 * {@code ORDER BY} clause (excluding the {@code ORDER BY} itself). Passing null
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700406 * will use the default sort order, which may be unordered.
407 * @param limit Limits the number of rows returned by the query,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700408 * formatted as {@code LIMIT} clause. Passing null denotes no {@code LIMIT} clause.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700409 * @return a cursor over the result set
410 * @see android.content.ContentResolver#query(android.net.Uri, String[],
411 * String, String[], String)
412 */
Jeff Sharkey91be9262018-07-19 09:30:16 -0600413 public Cursor query(SQLiteDatabase db, String[] projectionIn,
414 String selection, String[] selectionArgs, String groupBy,
415 String having, String sortOrder, String limit) {
416 return query(db, projectionIn, selection, selectionArgs,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800417 groupBy, having, sortOrder, limit, null);
418 }
419
420 /**
421 * Perform a query by combining all current settings and the
422 * information passed into this method.
423 *
424 * @param db the database to query on
Jeff Sharkey91be9262018-07-19 09:30:16 -0600425 * @param projectionIn A list of which columns to return. Passing
Jeff Brown75ea64f2012-01-25 19:37:13 -0800426 * null will return all columns, which is discouraged to prevent
427 * reading data from storage that isn't going to be used.
428 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700429 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
Jeff Brown75ea64f2012-01-25 19:37:13 -0800430 * itself). Passing null will return all rows for the given URL.
431 * @param selectionArgs You may include ?s in selection, which
432 * will be replaced by the values from selectionArgs, in order
433 * that they appear in the selection. The values will be bound
434 * as Strings.
435 * @param groupBy A filter declaring how to group rows, formatted
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700436 * as an SQL {@code GROUP BY} clause (excluding the {@code GROUP BY}
Jeff Brown75ea64f2012-01-25 19:37:13 -0800437 * itself). Passing null will cause the rows to not be grouped.
438 * @param having A filter declare which row groups to include in
439 * the cursor, if row grouping is being used, formatted as an
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700440 * SQL {@code HAVING} clause (excluding the {@code HAVING} itself). Passing
Jeff Brown75ea64f2012-01-25 19:37:13 -0800441 * null will cause all row groups to be included, and is
442 * required when row grouping is not being used.
443 * @param sortOrder How to order the rows, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700444 * {@code ORDER BY} clause (excluding the {@code ORDER BY} itself). Passing null
Jeff Brown75ea64f2012-01-25 19:37:13 -0800445 * will use the default sort order, which may be unordered.
446 * @param limit Limits the number of rows returned by the query,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700447 * formatted as {@code LIMIT} clause. Passing null denotes no {@code LIMIT} clause.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800448 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800449 * If the operation is canceled, then {@link OperationCanceledException} will be thrown
450 * when the query is executed.
451 * @return a cursor over the result set
452 * @see android.content.ContentResolver#query(android.net.Uri, String[],
453 * String, String[], String)
454 */
Jeff Sharkey91be9262018-07-19 09:30:16 -0600455 public Cursor query(SQLiteDatabase db, String[] projectionIn,
456 String selection, String[] selectionArgs, String groupBy,
457 String having, String sortOrder, String limit, CancellationSignal cancellationSignal) {
458 if (mTables == null) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700459 return null;
460 }
461
Jeff Sharkeybe8e0d02018-07-25 14:01:59 -0600462 final String sql;
463 final String unwrappedSql = buildQuery(
464 projectionIn, selection, groupBy, having,
465 sortOrder, limit);
466
Jeff Sharkey91be9262018-07-19 09:30:16 -0600467 if (mStrict && selection != null && selection.length() > 0) {
Daniel Lehmann50b1f8d2011-06-01 15:24:23 -0700468 // Validate the user-supplied selection to detect syntactic anomalies
469 // in the selection string that could indicate a SQL injection attempt.
470 // The idea is to ensure that the selection clause is a valid SQL expression
471 // by compiling it twice: once wrapped in parentheses and once as
472 // originally specified. An attacker cannot create an expression that
473 // would escape the SQL expression while maintaining balanced parentheses
474 // in both the wrapped and original forms.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700475
Jeff Sharkeybe8e0d02018-07-25 14:01:59 -0600476 // NOTE: The ordering of the below operations is important; we must
477 // execute the wrapped query to ensure the untrusted clause has been
478 // fully isolated.
479
480 // Validate the unwrapped query
481 db.validateSql(unwrappedSql, cancellationSignal); // will throw if query is invalid
482
483 // Execute wrapped query for extra protection
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600484 final String wrappedSql = buildQuery(projectionIn, wrap(selection), groupBy,
Jeff Sharkeybe8e0d02018-07-25 14:01:59 -0600485 having, sortOrder, limit);
486 sql = wrappedSql;
487 } else {
488 // Execute unwrapped query
489 sql = unwrappedSql;
490 }
Jeff Sharkey6adc98c2018-07-12 19:47:49 -0600491
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600492 final String[] sqlArgs = selectionArgs;
Jeff Sharkey91be9262018-07-19 09:30:16 -0600493 if (Log.isLoggable(TAG, Log.DEBUG)) {
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600494 if (Build.IS_DEBUGGABLE) {
495 Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
496 } else {
497 Log.d(TAG, sql);
498 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700499 }
500 return db.rawQueryWithFactory(
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600501 mFactory, sql, sqlArgs,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800502 SQLiteDatabase.findEditTable(mTables),
Jeff Brown4c1241d2012-02-02 17:05:00 -0800503 cancellationSignal); // will throw if query is invalid
Daniel Lehmann50b1f8d2011-06-01 15:24:23 -0700504 }
505
506 /**
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600507 * Perform an update by combining all current settings and the
508 * information passed into this method.
509 *
510 * @param db the database to update on
511 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700512 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600513 * itself). Passing null will return all rows for the given URL.
514 * @param selectionArgs You may include ?s in selection, which
515 * will be replaced by the values from selectionArgs, in order
516 * that they appear in the selection. The values will be bound
517 * as Strings.
518 * @return the number of rows updated
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600519 */
520 public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values,
521 @Nullable String selection, @Nullable String[] selectionArgs) {
522 Objects.requireNonNull(mTables, "No tables defined");
523 Objects.requireNonNull(db, "No database defined");
524 Objects.requireNonNull(values, "No values defined");
525
526 final String sql;
527 final String unwrappedSql = buildUpdate(values, selection);
528
529 if (mStrict) {
530 // Validate the user-supplied selection to detect syntactic anomalies
531 // in the selection string that could indicate a SQL injection attempt.
532 // The idea is to ensure that the selection clause is a valid SQL expression
533 // by compiling it twice: once wrapped in parentheses and once as
534 // originally specified. An attacker cannot create an expression that
535 // would escape the SQL expression while maintaining balanced parentheses
536 // in both the wrapped and original forms.
537
538 // NOTE: The ordering of the below operations is important; we must
539 // execute the wrapped query to ensure the untrusted clause has been
540 // fully isolated.
541
542 // Validate the unwrapped query
543 db.validateSql(unwrappedSql, null); // will throw if query is invalid
544
545 // Execute wrapped query for extra protection
546 final String wrappedSql = buildUpdate(values, wrap(selection));
547 sql = wrappedSql;
548 } else {
549 // Execute unwrapped query
550 sql = unwrappedSql;
551 }
552
Jeff Sharkey42122bf2018-07-26 09:39:18 -0600553 if (selectionArgs == null) {
554 selectionArgs = EmptyArray.STRING;
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600555 }
Jeff Sharkey42122bf2018-07-26 09:39:18 -0600556 final ArrayMap<String, Object> rawValues = values.getValues();
557 final int valuesLength = rawValues.size();
558 final Object[] sqlArgs = new Object[valuesLength + selectionArgs.length];
559 for (int i = 0; i < sqlArgs.length; i++) {
560 if (i < valuesLength) {
561 sqlArgs[i] = rawValues.valueAt(i);
562 } else {
563 sqlArgs[i] = selectionArgs[i - valuesLength];
564 }
565 }
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600566 if (Log.isLoggable(TAG, Log.DEBUG)) {
567 if (Build.IS_DEBUGGABLE) {
568 Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
569 } else {
570 Log.d(TAG, sql);
571 }
572 }
573 return db.executeSql(sql, sqlArgs);
574 }
575
576 /**
577 * Perform a delete by combining all current settings and the
578 * information passed into this method.
579 *
580 * @param db the database to delete on
581 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700582 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600583 * itself). Passing null will return all rows for the given URL.
584 * @param selectionArgs You may include ?s in selection, which
585 * will be replaced by the values from selectionArgs, in order
586 * that they appear in the selection. The values will be bound
587 * as Strings.
588 * @return the number of rows deleted
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600589 */
590 public int delete(@NonNull SQLiteDatabase db, @Nullable String selection,
591 @Nullable String[] selectionArgs) {
592 Objects.requireNonNull(mTables, "No tables defined");
593 Objects.requireNonNull(db, "No database defined");
594
595 final String sql;
596 final String unwrappedSql = buildDelete(selection);
597
598 if (mStrict) {
599 // Validate the user-supplied selection to detect syntactic anomalies
600 // in the selection string that could indicate a SQL injection attempt.
601 // The idea is to ensure that the selection clause is a valid SQL expression
602 // by compiling it twice: once wrapped in parentheses and once as
603 // originally specified. An attacker cannot create an expression that
604 // would escape the SQL expression while maintaining balanced parentheses
605 // in both the wrapped and original forms.
606
607 // NOTE: The ordering of the below operations is important; we must
608 // execute the wrapped query to ensure the untrusted clause has been
609 // fully isolated.
610
611 // Validate the unwrapped query
612 db.validateSql(unwrappedSql, null); // will throw if query is invalid
613
614 // Execute wrapped query for extra protection
615 final String wrappedSql = buildDelete(wrap(selection));
616 sql = wrappedSql;
617 } else {
618 // Execute unwrapped query
619 sql = unwrappedSql;
620 }
621
622 final String[] sqlArgs = selectionArgs;
623 if (Log.isLoggable(TAG, Log.DEBUG)) {
624 if (Build.IS_DEBUGGABLE) {
625 Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
626 } else {
627 Log.d(TAG, sql);
628 }
629 }
630 return db.executeSql(sql, sqlArgs);
631 }
632
633 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700634 * Construct a {@code SELECT} statement suitable for use in a group of
635 * {@code SELECT} statements that will be joined through {@code UNION} operators
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700636 * in buildUnionQuery.
637 *
638 * @param projectionIn A list of which columns to return. Passing
639 * null will return all columns, which is discouraged to
640 * prevent reading data from storage that isn't going to be
641 * used.
642 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700643 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700644 * itself). Passing null will return all rows for the given
645 * URL.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700646 * @param groupBy A filter declaring how to group rows, formatted
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700647 * as an SQL {@code GROUP BY} clause (excluding the {@code GROUP BY} itself).
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700648 * Passing null will cause the rows to not be grouped.
649 * @param having A filter declare which row groups to include in
650 * the cursor, if row grouping is being used, formatted as an
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700651 * SQL {@code HAVING} clause (excluding the {@code HAVING} itself). Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700652 * null will cause all row groups to be included, and is
653 * required when row grouping is not being used.
654 * @param sortOrder How to order the rows, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700655 * {@code ORDER BY} clause (excluding the {@code ORDER BY} itself). Passing null
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700656 * will use the default sort order, which may be unordered.
657 * @param limit Limits the number of rows returned by the query,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700658 * formatted as {@code LIMIT} clause. Passing null denotes no {@code LIMIT} clause.
659 * @return the resulting SQL {@code SELECT} statement
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700660 */
661 public String buildQuery(
Jonas Schwertfeger84029032010-11-12 11:42:28 +0100662 String[] projectionIn, String selection, String groupBy,
663 String having, String sortOrder, String limit) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700664 String[] projection = computeProjection(projectionIn);
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600665 String where = computeWhere(selection);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700666
667 return buildQueryString(
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600668 mDistinct, mTables, projection, where,
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700669 groupBy, having, sortOrder, limit);
670 }
671
672 /**
Jonas Schwertfeger84029032010-11-12 11:42:28 +0100673 * @deprecated This method's signature is misleading since no SQL parameter
674 * substitution is carried out. The selection arguments parameter does not get
675 * used at all. To avoid confusion, call
676 * {@link #buildQuery(String[], String, String, String, String, String)} instead.
677 */
678 @Deprecated
679 public String buildQuery(
680 String[] projectionIn, String selection, String[] selectionArgs,
681 String groupBy, String having, String sortOrder, String limit) {
682 return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit);
683 }
684
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600685 /** {@hide} */
686 public String buildUpdate(ContentValues values, String selection) {
687 if (values == null || values.isEmpty()) {
688 throw new IllegalArgumentException("Empty values");
689 }
690
691 StringBuilder sql = new StringBuilder(120);
692 sql.append("UPDATE ");
693 sql.append(mTables);
694 sql.append(" SET ");
695
696 final ArrayMap<String, Object> rawValues = values.getValues();
697 for (int i = 0; i < rawValues.size(); i++) {
698 if (i > 0) {
699 sql.append(',');
700 }
701 sql.append(rawValues.keyAt(i));
702 sql.append("=?");
703 }
704
705 final String where = computeWhere(selection);
706 appendClause(sql, " WHERE ", where);
707 return sql.toString();
708 }
709
710 /** {@hide} */
711 public String buildDelete(String selection) {
712 StringBuilder sql = new StringBuilder(120);
713 sql.append("DELETE FROM ");
714 sql.append(mTables);
715
716 final String where = computeWhere(selection);
717 appendClause(sql, " WHERE ", where);
718 return sql.toString();
719 }
720
Jonas Schwertfeger84029032010-11-12 11:42:28 +0100721 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700722 * Construct a {@code SELECT} statement suitable for use in a group of
723 * {@code SELECT} statements that will be joined through {@code UNION} operators
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700724 * in buildUnionQuery.
725 *
726 * @param typeDiscriminatorColumn the name of the result column
727 * whose cells will contain the name of the table from which
728 * each row was drawn.
729 * @param unionColumns the names of the columns to appear in the
730 * result. This may include columns that do not appear in the
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700731 * table this {@code SELECT} is querying (i.e. mTables), but that do
732 * appear in one of the other tables in the {@code UNION} query that we
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700733 * are constructing.
734 * @param columnsPresentInTable a Set of the names of the columns
735 * that appear in this table (i.e. in the table whose name is
736 * mTables). Since columns in unionColumns include columns that
737 * appear only in other tables, we use this array to distinguish
738 * which ones actually are present. Other columns will have
739 * NULL values for results from this subquery.
740 * @param computedColumnsOffset all columns in unionColumns before
741 * this index are included under the assumption that they're
742 * computed and therefore won't appear in columnsPresentInTable,
743 * e.g. "date * 1000 as normalized_date"
744 * @param typeDiscriminatorValue the value used for the
745 * type-discriminator column in this subquery
746 * @param selection A filter declaring which rows to return,
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700747 * formatted as an SQL {@code WHERE} clause (excluding the {@code WHERE}
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700748 * itself). Passing null will return all rows for the given
749 * URL.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700750 * @param groupBy A filter declaring how to group rows, formatted
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700751 * as an SQL {@code GROUP BY} clause (excluding the {@code GROUP BY} itself).
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700752 * Passing null will cause the rows to not be grouped.
753 * @param having A filter declare which row groups to include in
754 * the cursor, if row grouping is being used, formatted as an
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700755 * SQL {@code HAVING} clause (excluding the {@code HAVING} itself). Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700756 * null will cause all row groups to be included, and is
757 * required when row grouping is not being used.
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700758 * @return the resulting SQL {@code SELECT} statement
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700759 */
760 public String buildUnionSubQuery(
761 String typeDiscriminatorColumn,
762 String[] unionColumns,
763 Set<String> columnsPresentInTable,
764 int computedColumnsOffset,
765 String typeDiscriminatorValue,
766 String selection,
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700767 String groupBy,
768 String having) {
769 int unionColumnsCount = unionColumns.length;
770 String[] projectionIn = new String[unionColumnsCount];
771
772 for (int i = 0; i < unionColumnsCount; i++) {
773 String unionColumn = unionColumns[i];
774
775 if (unionColumn.equals(typeDiscriminatorColumn)) {
776 projectionIn[i] = "'" + typeDiscriminatorValue + "' AS "
777 + typeDiscriminatorColumn;
778 } else if (i <= computedColumnsOffset
779 || columnsPresentInTable.contains(unionColumn)) {
780 projectionIn[i] = unionColumn;
781 } else {
782 projectionIn[i] = "NULL AS " + unionColumn;
783 }
784 }
785 return buildQuery(
Jonas Schwertfeger84029032010-11-12 11:42:28 +0100786 projectionIn, selection, groupBy, having,
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700787 null /* sortOrder */,
788 null /* limit */);
789 }
790
791 /**
Jonas Schwertfeger84029032010-11-12 11:42:28 +0100792 * @deprecated This method's signature is misleading since no SQL parameter
793 * substitution is carried out. The selection arguments parameter does not get
794 * used at all. To avoid confusion, call
Jean-Baptiste Queruf4072fc2010-11-17 16:47:59 -0800795 * {@link #buildUnionSubQuery}
Jonas Schwertfeger84029032010-11-12 11:42:28 +0100796 * instead.
797 */
798 @Deprecated
799 public String buildUnionSubQuery(
800 String typeDiscriminatorColumn,
801 String[] unionColumns,
802 Set<String> columnsPresentInTable,
803 int computedColumnsOffset,
804 String typeDiscriminatorValue,
805 String selection,
806 String[] selectionArgs,
807 String groupBy,
808 String having) {
809 return buildUnionSubQuery(
810 typeDiscriminatorColumn, unionColumns, columnsPresentInTable,
811 computedColumnsOffset, typeDiscriminatorValue, selection,
812 groupBy, having);
813 }
814
815 /**
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700816 * Given a set of subqueries, all of which are {@code SELECT} statements,
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700817 * construct a query that returns the union of what those
818 * subqueries return.
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700819 * @param subQueries an array of SQL {@code SELECT} statements, all of
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700820 * which must have the same columns as the same positions in
821 * their results
822 * @param sortOrder How to order the rows, formatted as an SQL
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700823 * {@code ORDER BY} clause (excluding the {@code ORDER BY} itself). Passing
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700824 * null will use the default sort order, which may be unordered.
825 * @param limit The limit clause, which applies to the entire union result set
826 *
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700827 * @return the resulting SQL {@code SELECT} statement
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700828 */
829 public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) {
830 StringBuilder query = new StringBuilder(128);
831 int subQueryCount = subQueries.length;
832 String unionOperator = mDistinct ? " UNION " : " UNION ALL ";
833
834 for (int i = 0; i < subQueryCount; i++) {
835 if (i > 0) {
836 query.append(unionOperator);
837 }
838 query.append(subQueries[i]);
839 }
840 appendClause(query, " ORDER BY ", sortOrder);
841 appendClause(query, " LIMIT ", limit);
842 return query.toString();
843 }
844
Mathew Inwood31755f92018-12-20 13:53:36 +0000845 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Jeff Sharkey91be9262018-07-19 09:30:16 -0600846 private String[] computeProjection(String[] projectionIn) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700847 if (projectionIn != null && projectionIn.length > 0) {
848 if (mProjectionMap != null) {
849 String[] projection = new String[projectionIn.length];
850 int length = projectionIn.length;
851
852 for (int i = 0; i < length; i++) {
853 String userColumn = projectionIn[i];
854 String column = mProjectionMap.get(userColumn);
855
Michael Chan99c44832009-04-27 16:28:51 -0700856 if (column != null) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700857 projection[i] = column;
Michael Chan99c44832009-04-27 16:28:51 -0700858 continue;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700859 }
Michael Chan99c44832009-04-27 16:28:51 -0700860
Daniel Lehmann50b1f8d2011-06-01 15:24:23 -0700861 if (!mStrict &&
Dmitri Plotnikov40eb4aa2010-04-14 16:09:46 -0700862 ( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
Michael Chan99c44832009-04-27 16:28:51 -0700863 /* A column alias already exist */
864 projection[i] = userColumn;
865 continue;
866 }
867
Jeff Sharkey7049e652018-09-13 17:05:07 -0600868 // If greylist is configured, we might be willing to let
869 // this custom column bypass our strict checks.
870 if (mProjectionGreylist != null) {
871 boolean match = false;
872 for (Pattern p : mProjectionGreylist) {
873 if (p.matcher(userColumn).matches()) {
874 match = true;
875 break;
876 }
877 }
878
879 if (match) {
880 Log.w(TAG, "Allowing abusive custom column: " + userColumn);
881 projection[i] = userColumn;
882 continue;
883 }
884 }
885
Michael Chan99c44832009-04-27 16:28:51 -0700886 throw new IllegalArgumentException("Invalid column "
Jeff Sharkey91be9262018-07-19 09:30:16 -0600887 + projectionIn[i]);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700888 }
889 return projection;
890 } else {
891 return projectionIn;
892 }
893 } else if (mProjectionMap != null) {
894 // Return all columns in projection map.
895 Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
896 String[] projection = new String[entrySet.size()];
897 Iterator<Entry<String, String>> entryIter = entrySet.iterator();
898 int i = 0;
899
900 while (entryIter.hasNext()) {
901 Entry<String, String> entry = entryIter.next();
902
903 // Don't include the _count column when people ask for no projection.
904 if (entry.getKey().equals(BaseColumns._COUNT)) {
905 continue;
906 }
907 projection[i++] = entry.getValue();
908 }
909 return projection;
910 }
911 return null;
912 }
Jeff Sharkeyb13ea302018-07-25 14:52:14 -0600913
914 private @Nullable String computeWhere(@Nullable String selection) {
915 final boolean hasInternal = !TextUtils.isEmpty(mWhereClause);
916 final boolean hasExternal = !TextUtils.isEmpty(selection);
917
918 if (hasInternal || hasExternal) {
919 final StringBuilder where = new StringBuilder();
920 if (hasInternal) {
921 where.append('(').append(mWhereClause).append(')');
922 }
923 if (hasInternal && hasExternal) {
924 where.append(" AND ");
925 }
926 if (hasExternal) {
927 where.append('(').append(selection).append(')');
928 }
929 return where.toString();
930 } else {
931 return null;
932 }
933 }
934
935 /**
936 * Wrap given argument in parenthesis, unless it's {@code null} or
937 * {@code ()}, in which case return it verbatim.
938 */
939 private @Nullable String wrap(@Nullable String arg) {
940 if (TextUtils.isEmpty(arg)) {
941 return arg;
942 } else {
943 return "(" + arg + ")";
944 }
945 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700946}