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