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.request;
18 
19 import static android.health.connect.Constants.DEFAULT_LONG;
20 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_UNKNOWN;
21 
22 import static com.android.server.healthconnect.storage.utils.WhereClauses.LogicalOperator.AND;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.health.connect.Constants;
27 import android.health.connect.datatypes.RecordTypeIdentifier;
28 import android.util.Slog;
29 
30 import com.android.server.healthconnect.storage.utils.StorageUtils;
31 import com.android.server.healthconnect.storage.utils.WhereClauses;
32 
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Objects;
36 
37 /**
38  * No need to have delete-requests for child tables as ideally they should be following cascaded
39  * deletes. If not please rethink the table structure and if possible remove the parent-child
40  * relationship.
41  *
42  * @hide
43  */
44 public class DeleteTableRequest {
45 
46     private static final String TAG = "HealthConnectDelete";
47     private final String mTableName;
48     @RecordTypeIdentifier.RecordType private final int mRecordType;
49 
50     private String mIdColumnName;
51     private String mPackageColumnName;
52     private String mTimeColumnName;
53     private List<Long> mPackageFilters;
54     private long mStartTime = DEFAULT_LONG;
55     private long mEndTime = DEFAULT_LONG;
56     private boolean mRequiresUuId;
57     private List<String> mIds;
58     private boolean mEnforcePackageCheck;
59     private int mNumberOfUuidsToDelete;
60     private WhereClauses mCustomWhereClauses;
61     private long mLessThanOrEqualValue;
62 
63     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
DeleteTableRequest( @onNull String tableName, @RecordTypeIdentifier.RecordType int recordType)64     public DeleteTableRequest(
65             @NonNull String tableName, @RecordTypeIdentifier.RecordType int recordType) {
66         Objects.requireNonNull(tableName);
67 
68         mTableName = tableName;
69         mRecordType = recordType;
70     }
71 
72     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
DeleteTableRequest(@onNull String tableName)73     public DeleteTableRequest(@NonNull String tableName) {
74         Objects.requireNonNull(tableName);
75 
76         mTableName = tableName;
77         mRecordType = RECORD_TYPE_UNKNOWN;
78     }
79 
getPackageColumnName()80     public String getPackageColumnName() {
81         return mPackageColumnName;
82     }
83 
requiresPackageCheck()84     public boolean requiresPackageCheck() {
85         return mEnforcePackageCheck;
86     }
87 
setEnforcePackageCheck( String packageColumnName, String uuidColumnName)88     public DeleteTableRequest setEnforcePackageCheck(
89             String packageColumnName, String uuidColumnName) {
90         mEnforcePackageCheck = true;
91         mPackageColumnName = packageColumnName;
92         mIdColumnName = uuidColumnName;
93         return this;
94     }
95 
setIds(@onNull String idColumnName, @NonNull List<String> ids)96     public DeleteTableRequest setIds(@NonNull String idColumnName, @NonNull List<String> ids) {
97         Objects.requireNonNull(ids);
98         Objects.requireNonNull(idColumnName);
99 
100         mIds = ids.stream().map(StorageUtils::getNormalisedString).toList();
101         mIdColumnName = idColumnName;
102         return this;
103     }
104 
setId(@onNull String idColumnName, @NonNull String id)105     public DeleteTableRequest setId(@NonNull String idColumnName, @NonNull String id) {
106         Objects.requireNonNull(id);
107         Objects.requireNonNull(idColumnName);
108 
109         mIds = Collections.singletonList(StorageUtils.getNormalisedString(id));
110         mIdColumnName = idColumnName;
111         return this;
112     }
113 
requiresRead()114     public boolean requiresRead() {
115         return mRequiresUuId || mEnforcePackageCheck;
116     }
117 
setRequiresUuId(@onNull String idColumnName)118     public DeleteTableRequest setRequiresUuId(@NonNull String idColumnName) {
119         Objects.requireNonNull(idColumnName);
120 
121         mRequiresUuId = true;
122         mIdColumnName = idColumnName;
123 
124         return this;
125     }
126 
getRecordType()127     public int getRecordType() {
128         return mRecordType;
129     }
130 
131     @Nullable
getIdColumnName()132     public String getIdColumnName() {
133         return mIdColumnName;
134     }
135 
136     @Nullable
getIds()137     public List<String> getIds() {
138         return mIds;
139     }
140 
141     @NonNull
getTableName()142     public String getTableName() {
143         return mTableName;
144     }
145 
146     @NonNull
setPackageFilter( String packageColumnName, List<Long> packageFilters)147     public DeleteTableRequest setPackageFilter(
148             String packageColumnName, List<Long> packageFilters) {
149         mPackageFilters = packageFilters;
150         mPackageColumnName = packageColumnName;
151 
152         return this;
153     }
154 
155     @NonNull
getDeleteCommand()156     public String getDeleteCommand() {
157         return "DELETE FROM " + mTableName + getWhereCommand();
158     }
159 
getReadCommand()160     public String getReadCommand() {
161         return "SELECT "
162                 + mIdColumnName
163                 + ", "
164                 + mPackageColumnName
165                 + " FROM "
166                 + mTableName
167                 + getWhereCommand();
168     }
169 
getWhereCommand()170     public String getWhereCommand() {
171         WhereClauses whereClauses =
172                 Objects.isNull(mCustomWhereClauses) ? new WhereClauses(AND) : mCustomWhereClauses;
173         whereClauses.addWhereInLongsClause(mPackageColumnName, mPackageFilters);
174         whereClauses.addWhereBetweenTimeClause(mTimeColumnName, mStartTime, mEndTime);
175         whereClauses.addWhereInClauseWithoutQuotes(mIdColumnName, mIds);
176 
177         if (Constants.DEBUG) {
178             Slog.d(
179                     TAG,
180                     "delete query: tableName: "
181                             + mTableName
182                             + " whereClause: "
183                             + whereClauses.get(true));
184         }
185 
186         return whereClauses.get(true);
187     }
188 
189     @NonNull
setTimeFilter( @onNull String timeColumnName, long startTime, long endTime)190     public DeleteTableRequest setTimeFilter(
191             @NonNull String timeColumnName, long startTime, long endTime) {
192         Objects.requireNonNull(timeColumnName);
193 
194         // Return if the params will result in no impact on the query
195         if (startTime < 0 || endTime < startTime) {
196             return this;
197         }
198 
199         mStartTime = startTime;
200         mEndTime = endTime;
201         mTimeColumnName = timeColumnName;
202 
203         return this;
204     }
205 
206     /**
207      * Sets total number of UUIDs being deleted by this request.
208      *
209      * @param numberOfUuidsToDelete Number of UUIDs being deleted
210      */
setNumberOfUuidsToDelete(int numberOfUuidsToDelete)211     public void setNumberOfUuidsToDelete(int numberOfUuidsToDelete) {
212         this.mNumberOfUuidsToDelete = numberOfUuidsToDelete;
213     }
214 
215     /**
216      * Total number of records deleted.
217      *
218      * @return Number of records deleted by this request
219      */
getTotalNumberOfRecordsDeleted()220     public int getTotalNumberOfRecordsDeleted() {
221         if (requiresRead()) {
222             return mNumberOfUuidsToDelete;
223         }
224         return mIds.size();
225     }
226 }
227