1 /* 2 * Copyright (C) 2022 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 com.android.server.healthconnect.storage.utils; 18 19 import android.annotation.Nullable; 20 21 import com.android.server.healthconnect.storage.request.ReadTableRequest; 22 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.List; 26 import java.util.stream.Collectors; 27 28 /** @hide */ 29 public final class WhereClauses { 30 public enum LogicalOperator { 31 AND(" AND "), 32 OR(" OR "); 33 34 private final String opKeyword; 35 LogicalOperator(String opKeyword)36 LogicalOperator(String opKeyword) { 37 this.opKeyword = opKeyword; 38 } 39 } 40 41 private final List<String> mClauses = new ArrayList<>(); 42 private final LogicalOperator mLogicalOperator; 43 WhereClauses(LogicalOperator logicalOperator)44 public WhereClauses(LogicalOperator logicalOperator) { 45 mLogicalOperator = logicalOperator; 46 } 47 48 addWhereBetweenClause(String columnName, long start, long end)49 public WhereClauses addWhereBetweenClause(String columnName, long start, long end) { 50 mClauses.add(columnName + " >= " + start + " AND " + columnName + " < " + end); 51 52 return this; 53 } 54 55 /** 56 * Adds a clause for a time column between two values. 57 * 58 * <p>If the clause would be guaranteed empty (end < 0 or start > end) no clause is added. 59 * 60 * @param columnName the column to check (or no effect if null) 61 * @param startTime the earliest start time (inclusive) 62 * @param endTime the latest end time (inclusive) 63 */ addWhereBetweenTimeClause( @ullable String columnName, long startTime, long endTime)64 public WhereClauses addWhereBetweenTimeClause( 65 @Nullable String columnName, long startTime, long endTime) { 66 if (columnName == null || endTime < 0 || endTime < startTime) { 67 // Below method has check for startTime less than 0. 68 // If both startTime and endTime are less than 0 then no new time clause will be added 69 // and just return current object. 70 return addWhereLaterThanTimeClause(columnName, startTime); 71 } 72 73 mClauses.add(columnName + " >= " + startTime + " AND " + columnName + " < " + endTime); 74 75 return this; 76 } 77 78 /** 79 * Adds a clause for a time column after a given time. 80 * 81 * <p>If the clause would be guaranteed empty (startTime < 0) then no clause is added. 82 * 83 * @param columnName the column to check (or no effect if null) 84 * @param startTime the earliest start time (inclusive) 85 */ addWhereLaterThanTimeClause(@ullable String columnName, long startTime)86 public WhereClauses addWhereLaterThanTimeClause(@Nullable String columnName, long startTime) { 87 if (startTime < 0 || columnName == null) { 88 return this; 89 } 90 91 mClauses.add(columnName + " > " + startTime); 92 93 return this; 94 } 95 addWhereInClause(String columnName, List<String> values)96 public WhereClauses addWhereInClause(String columnName, List<String> values) { 97 if (values == null || values.isEmpty()) return this; 98 99 mClauses.add(columnName + " IN " + "('" + String.join("', '", values) + "')"); 100 101 return this; 102 } 103 104 /** 105 * Adds a clause for a value contained in a list of values. 106 * 107 * <p>If the clause would be guaranteed empty (values null or emoty) no clause is added. 108 * 109 * @param columnName the column to check (or no effect if null) 110 * @param values the column to check (or no effect if null) 111 */ addWhereInClauseWithoutQuotes( @ullable String columnName, @Nullable List<String> values)112 public WhereClauses addWhereInClauseWithoutQuotes( 113 @Nullable String columnName, @Nullable List<String> values) { 114 if (columnName == null || values == null || values.isEmpty()) return this; 115 116 mClauses.add(columnName + " IN " + "(" + String.join(", ", values) + ")"); 117 118 return this; 119 } 120 addWhereEqualsClause(String columnName, String value)121 public WhereClauses addWhereEqualsClause(String columnName, String value) { 122 if (columnName == null || value == null || value.isEmpty() || columnName.isEmpty()) { 123 return this; 124 } 125 126 mClauses.add(columnName + " = " + StorageUtils.getNormalisedString(value)); 127 return this; 128 } 129 addWhereGreaterThanClause(String columnName, String value)130 public WhereClauses addWhereGreaterThanClause(String columnName, String value) { 131 mClauses.add(columnName + " > '" + value + "'"); 132 133 return this; 134 } 135 136 /** Add clause columnName > value */ addWhereGreaterThanClause(String columnName, long value)137 public WhereClauses addWhereGreaterThanClause(String columnName, long value) { 138 mClauses.add(columnName + " > " + value); 139 140 return this; 141 } 142 addWhereGreaterThanOrEqualClause(String columnName, long value)143 public WhereClauses addWhereGreaterThanOrEqualClause(String columnName, long value) { 144 mClauses.add(columnName + " >= " + value); 145 146 return this; 147 } 148 addWhereLessThanOrEqualClause(String columnName, long value)149 public WhereClauses addWhereLessThanOrEqualClause(String columnName, long value) { 150 mClauses.add(columnName + " <= " + value); 151 152 return this; 153 } 154 155 /** Add clause columnName < value */ addWhereLessThanClause(String columnName, long value)156 public WhereClauses addWhereLessThanClause(String columnName, long value) { 157 mClauses.add(columnName + " < " + value); 158 159 return this; 160 } 161 162 /** 163 * Adds where in condition for the column. 164 * 165 * @param columnName Column name on which where condition to be applied 166 * @param values to check in the where condition 167 */ addWhereInIntsClause(String columnName, Collection<Integer> values)168 public WhereClauses addWhereInIntsClause(String columnName, Collection<Integer> values) { 169 if (values == null || values.isEmpty()) return this; 170 171 mClauses.add( 172 columnName 173 + " IN (" 174 + values.stream().map(String::valueOf).collect(Collectors.joining(", ")) 175 + ")"); 176 177 return this; 178 } 179 180 /** 181 * Adds where in condition for the column. 182 * 183 * @param columnName Column name on which where condition to be applied 184 * @param values to check in the where condition 185 */ addWhereInLongsClause( @ullable String columnName, @Nullable Collection<Long> values)186 public WhereClauses addWhereInLongsClause( 187 @Nullable String columnName, @Nullable Collection<Long> values) { 188 if (columnName == null || values == null || values.isEmpty()) return this; 189 190 mClauses.add( 191 columnName 192 + " IN (" 193 + values.stream() 194 .distinct() 195 .map(String::valueOf) 196 .collect(Collectors.joining(", ")) 197 + ")"); 198 199 return this; 200 } 201 202 /** 203 * Creates IN clause, where in range is another SQL request. Returns instance with extra clauses 204 * set. 205 */ addWhereInSQLRequestClause(String columnName, ReadTableRequest inRequest)206 public WhereClauses addWhereInSQLRequestClause(String columnName, ReadTableRequest inRequest) { 207 mClauses.add(columnName + " IN (" + inRequest.getReadCommand() + ") "); 208 209 return this; 210 } 211 212 /** Adds other {@link WhereClauses} as conditions of this where clause. */ addNestedWhereClauses(WhereClauses... otherWhereClauses)213 public WhereClauses addNestedWhereClauses(WhereClauses... otherWhereClauses) { 214 for (WhereClauses whereClauses : otherWhereClauses) { 215 if (whereClauses.mClauses.isEmpty()) { 216 // skip empty WhereClauses so we don't add empty pairs of parentheses "()" to the 217 // final SQL statement 218 continue; 219 } 220 if (mLogicalOperator.equals(whereClauses.mLogicalOperator)) { 221 // If the logical operator matches we don't need extra parentheses 222 mClauses.addAll(whereClauses.mClauses); 223 } else { 224 mClauses.add("(" + whereClauses.get(/* withWhereKeyword= */ false) + ")"); 225 } 226 } 227 228 return this; 229 } 230 231 /** 232 * Returns where clauses joined by 'AND', if the input parameter isIncludeWHEREinClauses is true 233 * then the clauses are preceded by 'WHERE'. 234 */ get(boolean withWhereKeyword)235 public String get(boolean withWhereKeyword) { 236 if (mClauses.isEmpty()) { 237 return ""; 238 } 239 240 return (withWhereKeyword ? " WHERE " : "") 241 + String.join(mLogicalOperator.opKeyword, mClauses); 242 } 243 } 244