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 import static com.android.server.healthconnect.storage.utils.StorageUtils.LIMIT_SIZE;
21 import static com.android.server.healthconnect.storage.utils.WhereClauses.LogicalOperator.AND;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.health.connect.Constants;
26 import android.util.Slog;
27 
28 import com.android.server.healthconnect.storage.TransactionManager;
29 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper;
30 import com.android.server.healthconnect.storage.utils.OrderByClause;
31 import com.android.server.healthconnect.storage.utils.SqlJoin;
32 import com.android.server.healthconnect.storage.utils.WhereClauses;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * A request for {@link TransactionManager} to read the DB
40  *
41  * @hide
42  */
43 public class ReadTableRequest {
44     private static final String TAG = "HealthConnectRead";
45     private static final String UNION_ALL = " UNION ALL ";
46 
47     private final String mTableName;
48     private RecordHelper<?> mRecordHelper;
49     private List<String> mColumnNames;
50     private SqlJoin mJoinClause;
51     private WhereClauses mWhereClauses = new WhereClauses(AND);
52     private boolean mDistinct = false;
53     private OrderByClause mOrderByClause = new OrderByClause();
54     private String mLimitClause = "";
55     private List<ReadTableRequest> mExtraReadRequests;
56     private List<ReadTableRequest> mUnionReadRequests;
57 
58     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
ReadTableRequest(@onNull String tableName)59     public ReadTableRequest(@NonNull String tableName) {
60         Objects.requireNonNull(tableName);
61 
62         mTableName = tableName;
63     }
64 
getRecordHelper()65     public RecordHelper<?> getRecordHelper() {
66         return mRecordHelper;
67     }
68 
setRecordHelper(RecordHelper<?> recordHelper)69     public ReadTableRequest setRecordHelper(RecordHelper<?> recordHelper) {
70         mRecordHelper = recordHelper;
71         return this;
72     }
73 
setColumnNames(@onNull List<String> columnNames)74     public ReadTableRequest setColumnNames(@NonNull List<String> columnNames) {
75         Objects.requireNonNull(columnNames);
76 
77         mColumnNames = columnNames;
78         return this;
79     }
80 
setWhereClause(WhereClauses whereClauses)81     public ReadTableRequest setWhereClause(WhereClauses whereClauses) {
82         mWhereClauses = whereClauses;
83         return this;
84     }
85 
86     /** Used to set Join Clause for the read query */
87     @NonNull
setJoinClause(SqlJoin joinClause)88     public ReadTableRequest setJoinClause(SqlJoin joinClause) {
89         mJoinClause = joinClause;
90         return this;
91     }
92 
93     /**
94      * Use this method to enable the Distinct clause in the read command.
95      *
96      * <p><b>NOTE: make sure to use the {@link ReadTableRequest#setColumnNames(List)} to set the
97      * column names to be used as the selection args.</b>
98      */
99     @NonNull
setDistinctClause(boolean isDistinctValuesRequired)100     public ReadTableRequest setDistinctClause(boolean isDistinctValuesRequired) {
101         mDistinct = isDistinctValuesRequired;
102         return this;
103     }
104 
105     /** Returns SQL statement to perform read operation. */
106     @NonNull
getReadCommand()107     public String getReadCommand() {
108         StringBuilder builder = new StringBuilder("SELECT ");
109         if (mDistinct) {
110             builder.append("DISTINCT ");
111             builder.append(getColumnsToFetch());
112         } else {
113             builder.append(getColumnsToFetch());
114         }
115         builder.append(" FROM ");
116         builder.append(mTableName);
117 
118         builder.append(mWhereClauses.get(/* withWhereKeyword */ true));
119         builder.append(mOrderByClause.getOrderBy());
120         builder.append(mLimitClause);
121 
122         String readQuery = builder.toString();
123         if (mJoinClause != null) {
124             readQuery = mJoinClause.getJoinWithQueryCommand(readQuery);
125         }
126 
127         if (Constants.DEBUG) {
128             Slog.d(TAG, "read query: " + readQuery);
129         }
130 
131         if (mUnionReadRequests != null && !mUnionReadRequests.isEmpty()) {
132             builder = new StringBuilder();
133             for (ReadTableRequest unionReadRequest : mUnionReadRequests) {
134                 builder.append("SELECT * FROM (");
135                 builder.append(unionReadRequest.getReadCommand());
136                 builder.append(")");
137                 builder.append(UNION_ALL);
138             }
139 
140             builder.append(readQuery);
141 
142             return builder.toString();
143         }
144 
145         return readQuery;
146     }
147 
148     /** Get requests for populating extra data */
149     @Nullable
getExtraReadRequests()150     public List<ReadTableRequest> getExtraReadRequests() {
151         return mExtraReadRequests;
152     }
153 
154     /** Sets requests to populate extra data */
setExtraReadRequests(List<ReadTableRequest> extraDataReadRequests)155     public ReadTableRequest setExtraReadRequests(List<ReadTableRequest> extraDataReadRequests) {
156         mExtraReadRequests = new ArrayList<>(extraDataReadRequests);
157         return this;
158     }
159 
160     /** Get table name of the request */
getTableName()161     public String getTableName() {
162         return mTableName;
163     }
164 
165     /** Sets order by clause for the read query */
166     @NonNull
setOrderBy(OrderByClause orderBy)167     public ReadTableRequest setOrderBy(OrderByClause orderBy) {
168         mOrderByClause = orderBy;
169         return this;
170     }
171 
172     /** Sets LIMIT size for the read query */
173     @NonNull
setLimit(int limit)174     public ReadTableRequest setLimit(int limit) {
175         mLimitClause = LIMIT_SIZE + limit;
176         return this;
177     }
178 
getColumnsToFetch()179     private String getColumnsToFetch() {
180         if (mColumnNames == null || mColumnNames.isEmpty()) {
181             return "*";
182         }
183 
184         return String.join(DELIMITER, mColumnNames);
185     }
186 
187     /** Sets union read requests. */
188     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
setUnionReadRequests( @ullable List<ReadTableRequest> unionReadRequests)189     public ReadTableRequest setUnionReadRequests(
190             @Nullable List<ReadTableRequest> unionReadRequests) {
191         mUnionReadRequests = unionReadRequests;
192 
193         return this;
194     }
195 }
196