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 com.android.server.healthconnect.storage.utils.WhereClauses.LogicalOperator.OR; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.ContentValues; 25 import android.database.Cursor; 26 import android.health.connect.datatypes.RecordTypeIdentifier; 27 import android.health.connect.internal.datatypes.RecordInternal; 28 import android.util.ArrayMap; 29 import android.util.Pair; 30 31 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper; 32 import com.android.server.healthconnect.storage.utils.StorageUtils; 33 import com.android.server.healthconnect.storage.utils.WhereClauses; 34 35 import java.lang.annotation.ElementType; 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.lang.annotation.Target; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Objects; 42 43 /** @hide */ 44 public class UpsertTableRequest { 45 public static final int INVALID_ROW_ID = -1; 46 47 public static final int TYPE_STRING = 0; 48 public static final int TYPE_BLOB = 1; 49 private final String mTable; 50 private ContentValues mContentValues; 51 private final List<Pair<String, Integer>> mUniqueColumns; 52 private List<UpsertTableRequest> mChildTableRequests = Collections.emptyList(); 53 private String mParentCol; 54 private long mRowId = INVALID_ROW_ID; 55 private WhereClauses mWhereClausesForUpdate; 56 private IRequiresUpdate mRequiresUpdate = new IRequiresUpdate() {}; 57 private Integer mRecordType; 58 private RecordInternal<?> mRecordInternal; 59 private RecordHelper<?> mRecordHelper; 60 private List<String> mPostUpsertCommands = Collections.emptyList(); 61 62 @Nullable private ArrayMap<String, Boolean> mExtraWritePermissionsStateMapping; 63 UpsertTableRequest(@onNull String table, @NonNull ContentValues contentValues)64 public UpsertTableRequest(@NonNull String table, @NonNull ContentValues contentValues) { 65 this(table, contentValues, Collections.emptyList()); 66 } 67 68 @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression UpsertTableRequest( @onNull String table, @NonNull ContentValues contentValues, @NonNull List<Pair<String, Integer>> uniqueColumns)69 public UpsertTableRequest( 70 @NonNull String table, 71 @NonNull ContentValues contentValues, 72 @NonNull List<Pair<String, Integer>> uniqueColumns) { 73 Objects.requireNonNull(table); 74 Objects.requireNonNull(contentValues); 75 Objects.requireNonNull(uniqueColumns); 76 77 mTable = table; 78 mContentValues = contentValues; 79 mUniqueColumns = uniqueColumns; 80 } 81 getUniqueColumnsCount()82 public int getUniqueColumnsCount() { 83 return mUniqueColumns.size(); 84 } 85 86 @NonNull withParentKey(long rowId)87 public UpsertTableRequest withParentKey(long rowId) { 88 mRowId = rowId; 89 return this; 90 } 91 92 /** 93 * Use this if you want to add row_id of the parent table to all the child entries in {@code 94 * parentCol} 95 */ 96 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 97 @NonNull setParentColumnForChildTables(@ullable String parentCol)98 public UpsertTableRequest setParentColumnForChildTables(@Nullable String parentCol) { 99 mParentCol = parentCol; 100 return this; 101 } 102 103 @NonNull setRequiresUpdateClause(@onNull IRequiresUpdate requiresUpdate)104 public UpsertTableRequest setRequiresUpdateClause(@NonNull IRequiresUpdate requiresUpdate) { 105 Objects.requireNonNull(requiresUpdate); 106 107 mRequiresUpdate = requiresUpdate; 108 return this; 109 } 110 111 @NonNull getTable()112 public String getTable() { 113 return mTable; 114 } 115 116 @NonNull getContentValues()117 public ContentValues getContentValues() { 118 // Set the parent column of the creator of this requested to do that 119 if (!Objects.isNull(mParentCol) && mRowId != INVALID_ROW_ID) { 120 mContentValues.put(mParentCol, mRowId); 121 } 122 123 return mContentValues; 124 } 125 126 @NonNull getChildTableRequests()127 public List<UpsertTableRequest> getChildTableRequests() { 128 return mChildTableRequests; 129 } 130 131 @NonNull setChildTableRequests( @onNull List<UpsertTableRequest> childTableRequests)132 public UpsertTableRequest setChildTableRequests( 133 @NonNull List<UpsertTableRequest> childTableRequests) { 134 Objects.requireNonNull(childTableRequests); 135 136 mChildTableRequests = childTableRequests; 137 return this; 138 } 139 140 @NonNull getUpdateWhereClauses()141 public WhereClauses getUpdateWhereClauses() { 142 if (mWhereClausesForUpdate == null) { 143 return getReadWhereClauses(); 144 } 145 146 return mWhereClausesForUpdate; 147 } 148 setUpdateWhereClauses(WhereClauses whereClauses)149 public UpsertTableRequest setUpdateWhereClauses(WhereClauses whereClauses) { 150 Objects.requireNonNull(whereClauses); 151 152 mWhereClausesForUpdate = whereClauses; 153 return this; 154 } 155 getReadRequest()156 public ReadTableRequest getReadRequest() { 157 return new ReadTableRequest(getTable()).setWhereClause(getReadWhereClauses()); 158 } 159 getReadRequestUsingUpdateClause()160 public ReadTableRequest getReadRequestUsingUpdateClause() { 161 return new ReadTableRequest(getTable()).setWhereClause(getUpdateWhereClauses()); 162 } 163 164 @NonNull getReadWhereClauses()165 private WhereClauses getReadWhereClauses() { 166 WhereClauses readWhereClause = new WhereClauses(OR); 167 168 for (Pair<String, Integer> uniqueColumn : mUniqueColumns) { 169 switch (uniqueColumn.second) { 170 case TYPE_BLOB -> readWhereClause.addWhereEqualsClause( 171 uniqueColumn.first, StorageUtils.getHexString( 172 mContentValues.getAsByteArray(uniqueColumn.first))); 173 case TYPE_STRING -> readWhereClause.addWhereEqualsClause( 174 uniqueColumn.first, mContentValues.getAsString(uniqueColumn.first)); 175 default -> throw new UnsupportedOperationException( 176 "Unable to find type: " + uniqueColumn.second); 177 } 178 } 179 180 return readWhereClause; 181 } 182 requiresUpdate(Cursor cursor, UpsertTableRequest request)183 public boolean requiresUpdate(Cursor cursor, UpsertTableRequest request) { 184 return mRequiresUpdate.requiresUpdate(cursor, getContentValues(), request); 185 } 186 getRowIdColName()187 public String getRowIdColName() { 188 return RecordHelper.PRIMARY_COLUMN_NAME; 189 } 190 191 @RecordTypeIdentifier.RecordType getRecordType()192 public int getRecordType() { 193 Objects.requireNonNull(mRecordType); 194 return mRecordType; 195 } 196 setRecordType(@ecordTypeIdentifier.RecordType int recordIdentifier)197 public void setRecordType(@RecordTypeIdentifier.RecordType int recordIdentifier) { 198 mRecordType = recordIdentifier; 199 } 200 setHelper( RecordHelper<?> recordHelper)201 public <T extends RecordInternal<?>> UpsertTableRequest setHelper( 202 RecordHelper<?> recordHelper) { 203 mRecordHelper = recordHelper; 204 205 return this; 206 } 207 208 @NonNull getChildTablesWithRowsToBeDeletedDuringUpdate()209 public List<RecordHelper.TableColumnPair> getChildTablesWithRowsToBeDeletedDuringUpdate() { 210 return mRecordHelper == null 211 ? Collections.emptyList() 212 : mRecordHelper.getChildTablesWithRowsToBeDeletedDuringUpdate( 213 mExtraWritePermissionsStateMapping); 214 } 215 216 @NonNull getAllChildTables()217 public List<String> getAllChildTables() { 218 return mRecordHelper == null ? Collections.emptyList() : mRecordHelper.getAllChildTables(); 219 } 220 getRecordInternal()221 public RecordInternal<?> getRecordInternal() { 222 return mRecordInternal; 223 } 224 setRecordInternal(RecordInternal<?> recordInternal)225 public void setRecordInternal(RecordInternal<?> recordInternal) { 226 mRecordInternal = recordInternal; 227 } 228 setExtraWritePermissionsStateMapping( @ullable ArrayMap<String, Boolean> extraWritePermissionsToState)229 public <T extends RecordInternal<?>> UpsertTableRequest setExtraWritePermissionsStateMapping( 230 @Nullable ArrayMap<String, Boolean> extraWritePermissionsToState) { 231 mExtraWritePermissionsStateMapping = extraWritePermissionsToState; 232 return this; 233 } 234 235 /** Get SQL commands to be exected after this upsert has completed. */ getPostUpsertCommands()236 public List<String> getPostUpsertCommands() { 237 return mPostUpsertCommands; 238 } 239 240 /** Set SQL commands to be exected after this upsert has completed. */ setPostUpsertCommands(List<String> commands)241 public UpsertTableRequest setPostUpsertCommands(List<String> commands) { 242 mPostUpsertCommands = commands; 243 return this; 244 } 245 246 @Target(ElementType.TYPE_USE) 247 @Retention(RetentionPolicy.SOURCE) 248 @IntDef({TYPE_STRING, TYPE_BLOB}) 249 public @interface ColumnType {} 250 251 public interface IRequiresUpdate { requiresUpdate( Cursor cursor, ContentValues contentValues, UpsertTableRequest request)252 default boolean requiresUpdate( 253 Cursor cursor, ContentValues contentValues, UpsertTableRequest request) { 254 return true; 255 } 256 } 257 } 258