1 /* 2 * Copyright (C) 2017 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 17 package androidx.sqlite.db; 18 19 import java.util.regex.Pattern; 20 21 /** 22 * A simple query builder to create SQL SELECT queries. 23 */ 24 @SuppressWarnings("unused") 25 public final class SupportSQLiteQueryBuilder { 26 private static final Pattern sLimitPattern = 27 Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); 28 29 private boolean mDistinct = false; 30 private final String mTable; 31 private String[] mColumns = null; 32 private String mSelection; 33 private Object[] mBindArgs; 34 private String mGroupBy = null; 35 private String mHaving = null; 36 private String mOrderBy = null; 37 private String mLimit = null; 38 39 /** 40 * Creates a query for the given table name. 41 * 42 * @param tableName The table name(s) to query. 43 * 44 * @return A builder to create a query. 45 */ 46 public static SupportSQLiteQueryBuilder builder(String tableName) { 47 return new SupportSQLiteQueryBuilder(tableName); 48 } 49 50 private SupportSQLiteQueryBuilder(String table) { 51 mTable = table; 52 } 53 54 /** 55 * Adds DISTINCT keyword to the query. 56 * 57 * @return this 58 */ 59 public SupportSQLiteQueryBuilder distinct() { 60 mDistinct = true; 61 return this; 62 } 63 64 /** 65 * Sets the given list of columns as the columns that will be returned. 66 * 67 * @param columns The list of column names that should be returned. 68 * 69 * @return this 70 */ 71 public SupportSQLiteQueryBuilder columns(String[] columns) { 72 mColumns = columns; 73 return this; 74 } 75 76 /** 77 * Sets the arguments for the WHERE clause. 78 * 79 * @param selection The list of selection columns 80 * @param bindArgs The list of bind arguments to match against these columns 81 * 82 * @return this 83 */ 84 public SupportSQLiteQueryBuilder selection(String selection, Object[] bindArgs) { 85 mSelection = selection; 86 mBindArgs = bindArgs; 87 return this; 88 } 89 90 /** 91 * Adds a GROUP BY statement. 92 * 93 * @param groupBy The value of the GROUP BY statement. 94 * 95 * @return this 96 */ 97 @SuppressWarnings("WeakerAccess") 98 public SupportSQLiteQueryBuilder groupBy(String groupBy) { 99 mGroupBy = groupBy; 100 return this; 101 } 102 103 /** 104 * Adds a HAVING statement. You must also provide {@link #groupBy(String)} for this to work. 105 * 106 * @param having The having clause. 107 * 108 * @return this 109 */ 110 public SupportSQLiteQueryBuilder having(String having) { 111 mHaving = having; 112 return this; 113 } 114 115 /** 116 * Adds an ORDER BY statement. 117 * 118 * @param orderBy The order clause. 119 * 120 * @return this 121 */ 122 public SupportSQLiteQueryBuilder orderBy(String orderBy) { 123 mOrderBy = orderBy; 124 return this; 125 } 126 127 /** 128 * Adds a LIMIT statement. 129 * 130 * @param limit The limit value. 131 * 132 * @return this 133 */ 134 public SupportSQLiteQueryBuilder limit(String limit) { 135 if (!isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { 136 throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); 137 } 138 mLimit = limit; 139 return this; 140 } 141 142 /** 143 * Creates the {@link SupportSQLiteQuery} that can be passed into 144 * {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}. 145 * 146 * @return a new query 147 */ 148 public SupportSQLiteQuery create() { 149 if (isEmpty(mGroupBy) && !isEmpty(mHaving)) { 150 throw new IllegalArgumentException( 151 "HAVING clauses are only permitted when using a groupBy clause"); 152 } 153 StringBuilder query = new StringBuilder(120); 154 155 query.append("SELECT "); 156 if (mDistinct) { 157 query.append("DISTINCT "); 158 } 159 if (mColumns != null && mColumns.length != 0) { 160 appendColumns(query, mColumns); 161 } else { 162 query.append(" * "); 163 } 164 query.append(" FROM "); 165 query.append(mTable); 166 appendClause(query, " WHERE ", mSelection); 167 appendClause(query, " GROUP BY ", mGroupBy); 168 appendClause(query, " HAVING ", mHaving); 169 appendClause(query, " ORDER BY ", mOrderBy); 170 appendClause(query, " LIMIT ", mLimit); 171 172 return new SimpleSQLiteQuery(query.toString(), mBindArgs); 173 } 174 175 private static void appendClause(StringBuilder s, String name, String clause) { 176 if (!isEmpty(clause)) { 177 s.append(name); 178 s.append(clause); 179 } 180 } 181 182 /** 183 * Add the names that are non-null in columns to s, separating 184 * them with commas. 185 */ 186 private static void appendColumns(StringBuilder s, String[] columns) { 187 int n = columns.length; 188 189 for (int i = 0; i < n; i++) { 190 String column = columns[i]; 191 if (i > 0) { 192 s.append(", "); 193 } 194 s.append(column); 195 } 196 s.append(' '); 197 } 198 199 private static boolean isEmpty(String input) { 200 return input == null || input.length() == 0; 201 } 202 } 203