1 /*
2  * Copyright (C) 2023 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 android.healthconnect.cts.database;
18 
19 import androidx.annotation.IntDef;
20 import androidx.annotation.NonNull;
21 import androidx.annotation.Nullable;
22 
23 import java.lang.annotation.Retention;
24 import java.lang.annotation.RetentionPolicy;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Objects;
29 
30 /** ColumnInfo contains information about all the attributes that a column can hold. */
31 class ColumnInfo {
32     private final String mName;
33     private final String mDataType;
34     private final List<Integer> mConstraints;
35     private final List<String> mCheckConstraints;
36     private final String mDefaultValue;
37 
38     /** ColumnConstraint contains the constraints on a column. */
39     @IntDef({UNIQUE_CONSTRAINT, NOT_NULL_CONSTRAINT, AUTO_INCREMENT_CONSTRAINT})
40     @Retention(RetentionPolicy.SOURCE)
41     public @interface ColumnConstraint {}
42 
43     public static final int UNIQUE_CONSTRAINT = 0;
44     public static final int NOT_NULL_CONSTRAINT = 1;
45     public static final int AUTO_INCREMENT_CONSTRAINT = 2;
46 
47     /** Creates an instance for ColumnInfo. */
ColumnInfo(Builder builder)48     ColumnInfo(Builder builder) {
49         mName = builder.mName;
50         mDataType = builder.mDataType;
51         mConstraints = builder.mConstraints;
52         mCheckConstraints = builder.mCheckConstraints;
53         mDefaultValue = builder.mDefaultValue;
54     }
55 
56     /** Builder pattern for ColumnInfo. */
57     public static class Builder {
58         private final String mName;
59         private final String mDataType;
60         private final List<Integer> mConstraints;
61         private final List<String> mCheckConstraints;
62         private String mDefaultValue;
63 
Builder(@onNull String name, @NonNull String dataType)64         Builder(@NonNull String name, @NonNull String dataType) {
65             Objects.requireNonNull(name);
66             Objects.requireNonNull(dataType);
67             mName = name;
68             mDataType = dataType;
69             mConstraints = new ArrayList<>();
70             mCheckConstraints = new ArrayList<>();
71         }
72 
73         /** Sets the default value of the column. */
setDefaultValue(String defaultValue)74         public Builder setDefaultValue(String defaultValue) {
75             mDefaultValue = defaultValue;
76             return this;
77         }
78 
79         /** Appends constraint to the existing list of constraint. */
addConstraint(@olumnConstraint int constraint)80         public Builder addConstraint(@ColumnConstraint int constraint) {
81             mConstraints.add(constraint);
82             return this;
83         }
84 
85         /** Appends check constraint to the existing list of check constraint. */
addCheckConstraint(@onNull String checkConstraint)86         public Builder addCheckConstraint(@NonNull String checkConstraint) {
87             Objects.requireNonNull(checkConstraint);
88             mCheckConstraints.add(checkConstraint);
89             return this;
90         }
91 
92         /** Builds the columnInfo object. */
build()93         public ColumnInfo build() {
94             return new ColumnInfo(this);
95         }
96     }
97 
98     /**
99      * @return name of the column.
100      */
101     @NonNull
getName()102     public String getName() {
103         return mName;
104     }
105 
106     /**
107      * @return datatype of the column.
108      */
109     @NonNull
getDataType()110     public String getDataType() {
111         return mDataType;
112     }
113 
114     /**
115      * @return list of all the constraints of the column.
116      */
117     @Nullable
getConstraints()118     public List<Integer> getConstraints() {
119         return mConstraints;
120     }
121 
122     /**
123      * @return list of all check constraints of the column.
124      */
125     @Nullable
getCheckConstraints()126     public List<String> getCheckConstraints() {
127         return mCheckConstraints;
128     }
129 
130     /**
131      * @return the default value of the column if assigned otherwise null.
132      */
133     @Nullable
getDefaultValue()134     public String getDefaultValue() {
135         return mDefaultValue;
136     }
137 
138     /**
139      * @return true if the objects of the two ColumnInfo are same otherwise false.
140      */
141     @NonNull
isEqual(ColumnInfo expectedColumn)142     public Boolean isEqual(ColumnInfo expectedColumn) {
143         if (mName.equals(expectedColumn.mName) && mDataType.equals(expectedColumn.mDataType)) {
144             if (!Objects.equals(mDefaultValue, expectedColumn.mDefaultValue)) {
145                 return false;
146             }
147             List<Integer> constraintList = mConstraints;
148             List<Integer> expectedConstraintList = expectedColumn.mConstraints;
149             List<String> checkConstraintList = mCheckConstraints;
150             List<String> expectedCheckConstraintList = expectedColumn.mCheckConstraints;
151             Collections.sort(constraintList);
152             Collections.sort(expectedConstraintList);
153             Collections.sort(checkConstraintList);
154             Collections.sort(expectedCheckConstraintList);
155             return constraintList.equals(expectedConstraintList)
156                     && checkConstraintList.equals(expectedCheckConstraintList);
157         }
158         return false;
159     }
160 
161     /**
162      * Compares two ColumnInfo and stores any backward incompatible change to the corresponding
163      * ErrorInfo of column.
164      */
checkColumnDiff( ColumnInfo expectedColumn, List<String> modificationOfColumn, String tableName)165     public void checkColumnDiff(
166             ColumnInfo expectedColumn, List<String> modificationOfColumn, String tableName) {
167 
168         if (!mDataType.equals(expectedColumn.mDataType)) {
169             modificationOfColumn.add(
170                     "Datatype has been changed for column: "
171                             + mName
172                             + " of the table: "
173                             + tableName);
174         }
175         if (!Objects.equals(mDefaultValue, expectedColumn.mDefaultValue)) {
176             modificationOfColumn.add(
177                     "Default value has been changed for column: "
178                             + mName
179                             + " of the table: "
180                             + tableName);
181         }
182         List<Integer> constraintList1 = mConstraints.stream().sorted().toList();
183         List<Integer> constraintList2 = expectedColumn.mConstraints.stream().sorted().toList();
184 
185         if (!constraintList1.equals(constraintList2)) {
186             modificationOfColumn.add(
187                     "Constraints have been changed for column: "
188                             + mName
189                             + " of the table: "
190                             + tableName);
191         }
192         List<String> checkConstraintList1 = mCheckConstraints.stream().sorted().toList();
193         List<String> checkConstraintList2 =
194                 expectedColumn.mCheckConstraints.stream().sorted().toList();
195 
196         if (!checkConstraintList1.equals(checkConstraintList2)) {
197             modificationOfColumn.add(
198                     "Check constraints has been changed for the column: "
199                             + mName
200                             + " of the table: "
201                             + tableName);
202         }
203     }
204 }
205