• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.server.healthconnect.storage.utils;
18 
19 import static com.android.server.healthconnect.storage.utils.StorageUtils.SELECT_ALL;
20 
21 import android.annotation.StringDef;
22 
23 import java.lang.annotation.Retention;
24 import java.lang.annotation.RetentionPolicy;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Objects;
28 
29 /**
30  * Represents SQL join. Default join type is INNER join.
31  *
32  * @hide
33  */
34 public final class SqlJoin {
35     public static final String SQL_JOIN_INNER = "INNER";
36     public static final String SQL_JOIN_LEFT = "LEFT";
37 
38     public static final String INNER_QUERY_ALIAS = "inner_query_result";
39 
40     /** @hide */
41     @StringDef(
42             value = {
43                 SQL_JOIN_INNER,
44                 SQL_JOIN_LEFT,
45             })
46     @Retention(RetentionPolicy.SOURCE)
47     public @interface JoinType {}
48 
49     private final String mSelfTableName;
50     private final String mTableNameToJoinOn;
51     private final String mSelfColumnNameToMatch;
52     private final String mJoiningColumnNameToMatch;
53 
54     private List<SqlJoin> mAttachedJoins;
55     private String mJoinType = SQL_JOIN_INNER;
56 
57     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
58     private WhereClauses mTableToJoinWhereClause = null;
59 
60     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
SqlJoin( String selfTableName, String tableNameToJoinOn, String selfColumnNameToMatch, String joiningColumnNameToMatch)61     public SqlJoin(
62             String selfTableName,
63             String tableNameToJoinOn,
64             String selfColumnNameToMatch,
65             String joiningColumnNameToMatch) {
66         mSelfTableName = selfTableName;
67         mTableNameToJoinOn = tableNameToJoinOn;
68         mSelfColumnNameToMatch = selfColumnNameToMatch;
69         mJoiningColumnNameToMatch = joiningColumnNameToMatch;
70     }
71 
72     /**
73      * Sets join type to the current joint, default value is inner join. Returns class with join
74      * type set.
75      */
setJoinType(@oinType String joinType)76     public SqlJoin setJoinType(@JoinType String joinType) {
77         Objects.requireNonNull(joinType);
78         mJoinType = joinType;
79         return this;
80     }
81 
82     /**
83      * Returns query by applying JOIN condition on the innerQuery
84      *
85      * @param innerQuery An inner query to be used for the JOIN
86      * @param selectStatement the outer select statement where you can specify column names,
87      *     DISTINCT, etc.
88      * @return Final query with JOIN condition
89      */
getJoinWithQueryCommand(String selectStatement, String innerQuery)90     public String getJoinWithQueryCommand(String selectStatement, String innerQuery) {
91         if (innerQuery == null) {
92             throw new IllegalArgumentException("Inner query cannot be null");
93         }
94         return selectStatement
95                 + "( "
96                 + innerQuery
97                 + " ) AS "
98                 + INNER_QUERY_ALIAS
99                 + " "
100                 + getJoinCommand(/* withInnerQuery= */ true);
101     }
102 
103     /** Returns join command. */
getJoinCommand()104     public String getJoinCommand() {
105         return getJoinCommand(/* withInnerQuery= */ false);
106     }
107 
108     /** Attaches another join to this join. Returns this class with another join attached. */
attachJoin(SqlJoin join)109     public SqlJoin attachJoin(SqlJoin join) {
110         Objects.requireNonNull(join);
111 
112         if (mAttachedJoins == null) {
113             mAttachedJoins = new ArrayList<>();
114         }
115 
116         mAttachedJoins.add(join);
117         return this;
118     }
119 
120     /** Sets the {@link WhereClauses} for the second table and returns this {@link SqlJoin}. */
setSecondTableWhereClause(WhereClauses whereClause)121     public SqlJoin setSecondTableWhereClause(WhereClauses whereClause) {
122         mTableToJoinWhereClause = whereClause;
123         return this;
124     }
125 
getJoinCommand(boolean withInnerQuery)126     private String getJoinCommand(boolean withInnerQuery) {
127         String selfColumnPrefix = withInnerQuery ? INNER_QUERY_ALIAS + "." : mSelfTableName + ".";
128         return " "
129                 + mJoinType
130                 + " JOIN "
131                 + (mTableToJoinWhereClause == null ? "" : "( " + buildFilterQuery() + ") ")
132                 + mTableNameToJoinOn
133                 + " ON "
134                 + selfColumnPrefix
135                 + mSelfColumnNameToMatch
136                 + " = "
137                 + mTableNameToJoinOn
138                 + "."
139                 + mJoiningColumnNameToMatch
140                 + buildAttachedJoinsCommand(withInnerQuery);
141     }
142 
buildFilterQuery()143     private String buildFilterQuery() {
144         return SELECT_ALL + mTableNameToJoinOn + mTableToJoinWhereClause.get(true);
145     }
146 
buildAttachedJoinsCommand(boolean withInnerQuery)147     private String buildAttachedJoinsCommand(boolean withInnerQuery) {
148         if (mAttachedJoins == null) {
149             return "";
150         }
151 
152         StringBuilder command = new StringBuilder();
153         for (SqlJoin join : mAttachedJoins) {
154             if (withInnerQuery && join.mSelfTableName.equals(mSelfTableName)) {
155                 // When we're joining from the top level table, and there is an inner query, use the
156                 // inner query prefix.
157                 command.append(" ").append(join.getJoinCommand(true));
158             } else {
159                 // Otherwise use the table name itself.
160                 command.append(" ").append(join.getJoinCommand(false));
161             }
162         }
163 
164         return command.toString();
165     }
166 }
167