• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.StorageUtils.DELIMITER;
20 
21 import android.health.connect.Constants;
22 import android.util.Pair;
23 import android.util.Slog;
24 
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Objects;
29 
30 /**
31  * Creates a request and the table create statements for it.
32  *
33  * <p>Note: For every child table. This class automatically creates index statements for all the
34  * defined foreign keys.
35  *
36  * @hide
37  */
38 public final class CreateTableRequest {
39     public static final String TAG = "HealthConnectCreate";
40     public static final String FOREIGN_KEY_COMMAND = " FOREIGN KEY (";
41     private static final String CREATE_INDEX_COMMAND = "CREATE INDEX IF NOT EXISTS idx_";
42     private static final String CREATE_TABLE_COMMAND = "CREATE TABLE IF NOT EXISTS ";
43     private static final String UNIQUE_COMMAND = "UNIQUE ( ";
44     private final String mTableName;
45     private final List<Pair<String, String>> mColumnInfo;
46     private final List<String> mColumnsToIndex = new ArrayList<>();
47     private final List<List<String>> mUniqueColumns = new ArrayList<>();
48     private List<ForeignKey> mForeignKeys = new ArrayList<>();
49     private List<CreateTableRequest> mChildTableRequests = Collections.emptyList();
50     private List<GeneratedColumnInfo> mGeneratedColumnInfo = Collections.emptyList();
51 
CreateTableRequest(String tableName, List<Pair<String, String>> columnInfo)52     public CreateTableRequest(String tableName, List<Pair<String, String>> columnInfo) {
53         mTableName = tableName;
54         mColumnInfo = columnInfo;
55     }
56 
getTableName()57     public String getTableName() {
58         return mTableName;
59     }
60 
addForeignKey( String referencedTable, List<String> columnNames, List<String> referencedColumnNames)61     public CreateTableRequest addForeignKey(
62             String referencedTable, List<String> columnNames, List<String> referencedColumnNames) {
63         mForeignKeys = mForeignKeys == null ? new ArrayList<>() : mForeignKeys;
64         mForeignKeys.add(new ForeignKey(referencedTable, columnNames, referencedColumnNames));
65 
66         return this;
67     }
68 
createIndexOn(String columnName)69     public CreateTableRequest createIndexOn(String columnName) {
70         Objects.requireNonNull(columnName);
71 
72         mColumnsToIndex.add(columnName);
73         return this;
74     }
75 
getChildTableRequests()76     public List<CreateTableRequest> getChildTableRequests() {
77         return mChildTableRequests;
78     }
79 
setChildTableRequests( List<CreateTableRequest> childCreateTableRequests)80     public CreateTableRequest setChildTableRequests(
81             List<CreateTableRequest> childCreateTableRequests) {
82         Objects.requireNonNull(childCreateTableRequests);
83 
84         mChildTableRequests = childCreateTableRequests;
85         return this;
86     }
87 
getCreateCommand()88     public String getCreateCommand() {
89         final StringBuilder builder = new StringBuilder(CREATE_TABLE_COMMAND);
90         builder.append(mTableName);
91         builder.append(" (");
92         mColumnInfo.forEach(
93                 (columnInfo) ->
94                         builder.append(columnInfo.first)
95                                 .append(" ")
96                                 .append(columnInfo.second)
97                                 .append(", "));
98 
99         for (GeneratedColumnInfo generatedColumnInfo : mGeneratedColumnInfo) {
100             builder.append(generatedColumnInfo.getColumnName())
101                     .append(" ")
102                     .append(generatedColumnInfo.getColumnType())
103                     .append(" AS (")
104                     .append(generatedColumnInfo.getExpression())
105                     .append("), ");
106         }
107 
108         if (mForeignKeys != null) {
109             for (ForeignKey foreignKey : mForeignKeys) {
110                 builder.append(foreignKey.getFkConstraint()).append(", ");
111             }
112         }
113 
114         if (!mUniqueColumns.isEmpty()) {
115             mUniqueColumns.forEach(
116                     columns -> {
117                         builder.append(UNIQUE_COMMAND);
118                         for (String column : columns) {
119                             builder.append(column).append(", ");
120                         }
121                         builder.setLength(builder.length() - 2); // Remove the last 2 char i.e. ", "
122                         builder.append("), ");
123                     });
124         }
125         builder.setLength(builder.length() - 2); // Remove the last 2 char i.e. ", "
126 
127         builder.append(")");
128         if (Constants.DEBUG) {
129             Slog.d(TAG, "Create table: " + builder);
130         }
131 
132         return builder.toString();
133     }
134 
addUniqueConstraints(List<String> columnNames)135     public CreateTableRequest addUniqueConstraints(List<String> columnNames) {
136         mUniqueColumns.add(columnNames);
137         return this;
138     }
139 
getCreateIndexStatements()140     public List<String> getCreateIndexStatements() {
141         List<String> result = new ArrayList<>();
142         if (mForeignKeys != null) {
143             int index = 0;
144             for (ForeignKey foreignKey : mForeignKeys) {
145                 result.add(foreignKey.getFkIndexStatement(index++));
146             }
147         }
148 
149         if (!mColumnsToIndex.isEmpty()) {
150             for (String columnToIndex : mColumnsToIndex) {
151                 result.add(getCreateIndexCommand(columnToIndex));
152             }
153         }
154 
155         return result;
156     }
157 
setGeneratedColumnInfo( List<GeneratedColumnInfo> generatedColumnInfo)158     public CreateTableRequest setGeneratedColumnInfo(
159             List<GeneratedColumnInfo> generatedColumnInfo) {
160         Objects.requireNonNull(generatedColumnInfo);
161 
162         mGeneratedColumnInfo = generatedColumnInfo;
163         return this;
164     }
165 
getCreateIndexCommand(String indexName, List<String> columnNames)166     private String getCreateIndexCommand(String indexName, List<String> columnNames) {
167         Objects.requireNonNull(columnNames);
168         Objects.requireNonNull(indexName);
169 
170         return CREATE_INDEX_COMMAND
171                 + indexName
172                 + " ON "
173                 + mTableName
174                 + "("
175                 + String.join(DELIMITER, columnNames)
176                 + ")";
177     }
178 
getCreateIndexCommand(String columnName)179     private String getCreateIndexCommand(String columnName) {
180         Objects.requireNonNull(columnName);
181 
182         return CREATE_INDEX_COMMAND
183                 + mTableName
184                 + "_"
185                 + columnName
186                 + " ON "
187                 + mTableName
188                 + "("
189                 + columnName
190                 + ")";
191     }
192 
193     /**
194      * Indicates whether some other object is "equal to" this one.
195      *
196      * @param obj the reference object with which to compare.
197      * @return {@code true} if this object is the same as the obj
198      */
199     @Override
equals(Object obj)200     public boolean equals(Object obj) {
201         if (this == obj) return true;
202         if (!(obj instanceof CreateTableRequest that)) return false;
203         return Objects.equals(mTableName, that.mTableName)
204                 && Objects.equals(mColumnInfo, that.mColumnInfo)
205                 && Objects.equals(mUniqueColumns, that.mUniqueColumns)
206                 && Objects.equals(mForeignKeys, that.mForeignKeys)
207                 && Objects.equals(mChildTableRequests, that.mChildTableRequests)
208                 && Objects.equals(mGeneratedColumnInfo, that.mGeneratedColumnInfo)
209                 && Objects.equals(mColumnsToIndex, that.mColumnsToIndex);
210     }
211 
212     /** Returns a hash code value for the object. */
213     @Override
hashCode()214     public int hashCode() {
215         return Objects.hash(
216                 mTableName,
217                 mColumnInfo,
218                 mUniqueColumns,
219                 mForeignKeys,
220                 mChildTableRequests,
221                 mGeneratedColumnInfo);
222     }
223 
224     public static final class GeneratedColumnInfo {
225         private final String columnName;
226         private final String type;
227         private final String expression;
228 
GeneratedColumnInfo(String columnName, String type, String expression)229         public GeneratedColumnInfo(String columnName, String type, String expression) {
230             this.columnName = columnName;
231             this.type = type;
232             this.expression = expression;
233         }
234 
getColumnName()235         public String getColumnName() {
236             return columnName;
237         }
238 
getColumnType()239         public String getColumnType() {
240             return type;
241         }
242 
getExpression()243         public String getExpression() {
244             return expression;
245         }
246 
247         /**
248          * Indicates whether some other object is "equal to" this one.
249          *
250          * @param obj the reference object with which to compare.
251          * @return {@code true} if this object is the same as the obj
252          */
253         @Override
equals(Object obj)254         public boolean equals(Object obj) {
255             if (this == obj) return true;
256             if (!(obj instanceof GeneratedColumnInfo that)) return false;
257             return (Objects.equals(columnName, that.columnName)
258                     && Objects.equals(type, that.type)
259                     && Objects.equals(expression, that.expression));
260         }
261 
262         /** Returns a hash code value for the object. */
263         @Override
hashCode()264         public int hashCode() {
265             return Objects.hash(columnName, type, expression);
266         }
267     }
268 
269     private final class ForeignKey {
270         private final List<String> mColumnNames;
271         private final String mReferencedTableName;
272         private final List<String> mReferencedColumnNames;
273 
ForeignKey( String referencedTable, List<String> columnNames, List<String> referencedColumnNames)274         ForeignKey(
275                 String referencedTable,
276                 List<String> columnNames,
277                 List<String> referencedColumnNames) {
278             mReferencedTableName = referencedTable;
279             mColumnNames = columnNames;
280             mReferencedColumnNames = referencedColumnNames;
281         }
282 
getFkConstraint()283         String getFkConstraint() {
284             return FOREIGN_KEY_COMMAND
285                     + String.join(DELIMITER, mColumnNames)
286                     + ")"
287                     + " REFERENCES "
288                     + mReferencedTableName
289                     + "("
290                     + String.join(DELIMITER, mReferencedColumnNames)
291                     + ") ON DELETE CASCADE";
292         }
293 
getFkIndexStatement(int fkNumber)294         String getFkIndexStatement(int fkNumber) {
295             return getCreateIndexCommand(mTableName + "_" + fkNumber, mColumnNames);
296         }
297 
298         /**
299          * Indicates whether some other object is "equal to" this one.
300          *
301          * @param obj the reference object with which to compare.
302          * @return {@code true} if this object is the same as the obj
303          */
304         @Override
equals(Object obj)305         public boolean equals(Object obj) {
306             if (this == obj) return true;
307             if (!(obj instanceof ForeignKey that)) return false;
308             return (Objects.equals(mColumnNames, that.mColumnNames)
309                     && Objects.equals(mReferencedTableName, that.mReferencedTableName)
310                     && Objects.equals(mReferencedColumnNames, that.mReferencedColumnNames));
311         }
312 
313         /** Returns a hash code value for the object. */
314         @Override
hashCode()315         public int hashCode() {
316             return Objects.hash(mColumnNames, mReferencedTableName, mReferencedColumnNames);
317         }
318     }
319 }
320