1 /*
<lambda>null2  * Copyright (C) 2016 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 androidx.room.writer
18 
19 import androidx.room.compiler.codegen.CodeLanguage
20 import androidx.room.compiler.codegen.XCodeBlock
21 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.applyTo
22 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
23 import androidx.room.compiler.codegen.XTypeName
24 import androidx.room.ext.CommonTypeNames
25 import androidx.room.ext.KotlinTypeNames
26 import androidx.room.ext.RoomMemberNames
27 import androidx.room.ext.RoomTypeNames
28 import androidx.room.parser.ParsedQuery
29 import androidx.room.parser.Section
30 import androidx.room.solver.CodeGenScope
31 import androidx.room.vo.QueryFunction
32 import androidx.room.vo.QueryParameter
33 
34 /** Writes the SQL query and arguments for a QueryMethod. */
35 class QueryWriter(
36     val parameters: List<QueryParameter>,
37     val sectionToParamMapping: List<Pair<Section, QueryParameter?>>,
38     val query: ParsedQuery
39 ) {
40 
41     constructor(
42         queryFunction: QueryFunction
43     ) : this(queryFunction.parameters, queryFunction.sectionToParamMapping, queryFunction.query)
44 
45     fun prepareReadAndBind(
46         outSqlQueryName: String,
47         outRoomSQLiteQueryVar: String,
48         scope: CodeGenScope
49     ) {
50         val listSizeVars = createSqlQueryAndArgs(outSqlQueryName, outRoomSQLiteQueryVar, scope)
51         bindArgs(outRoomSQLiteQueryVar, listSizeVars, scope)
52     }
53 
54     fun prepareQuery(
55         outSqlQueryName: String,
56         scope: CodeGenScope
57     ): List<Pair<QueryParameter, String>> {
58         return createSqlQueryAndArgs(outSqlQueryName, null, scope)
59     }
60 
61     private fun createSqlQueryAndArgs(
62         outSqlQueryName: String,
63         outArgsName: String?,
64         scope: CodeGenScope
65     ): List<Pair<QueryParameter, String>> {
66         val listSizeVars = arrayListOf<Pair<QueryParameter, String>>()
67         val varargParams = parameters.filter { it.queryParamAdapter?.isMultiple ?: false }
68         val sectionToParamMapping = sectionToParamMapping
69         val knownQueryArgsCount =
70             sectionToParamMapping
71                 .filterNot { it.second?.queryParamAdapter?.isMultiple ?: false }
72                 .size
73         scope.builder.apply {
74             if (varargParams.isNotEmpty()) {
75                 val stringBuilderVar = scope.getTmpVar("_stringBuilder")
76                 applyTo { language ->
77                     val stringBuilderType =
78                         when (language) {
79                             CodeLanguage.JAVA -> CommonTypeNames.STRING_BUILDER
80                             CodeLanguage.KOTLIN -> KotlinTypeNames.STRING_BUILDER
81                         }
82                     addLocalVariable(
83                         name = stringBuilderVar,
84                         typeName = stringBuilderType,
85                         assignExpr = XCodeBlock.ofNewInstance(stringBuilderType)
86                     )
87                 }
88                 query.sections.forEach { section ->
89                     when (section) {
90                         is Section.Text ->
91                             addStatement("%L.append(%S)", stringBuilderVar, section.text)
92                         is Section.NewLine -> addStatement("%L.append(%S)", stringBuilderVar, "\n")
93                         is Section.BindVar -> {
94                             // If it is null, will be reported as error before. We just try out
95                             // best to generate as much code as possible.
96                             sectionToParamMapping
97                                 .firstOrNull { section == it.first }
98                                 ?.let { (_, param) ->
99                                     if (param?.queryParamAdapter?.isMultiple == true) {
100                                         val tmpCount = scope.getTmpVar("_inputSize")
101                                         listSizeVars.add(param to tmpCount)
102                                         param.queryParamAdapter.getArgCount(
103                                             param.name,
104                                             tmpCount,
105                                             scope
106                                         )
107                                         addStatement(
108                                             "%M(%L, %L)",
109                                             RoomTypeNames.STRING_UTIL.packageMember(
110                                                 "appendPlaceholders"
111                                             ),
112                                             stringBuilderVar,
113                                             tmpCount
114                                         )
115                                     } else {
116                                         addStatement("%L.append(%S)", stringBuilderVar, "?")
117                                     }
118                                 }
119                         }
120                     }
121                 }
122                 addLocalVal(
123                     outSqlQueryName,
124                     CommonTypeNames.STRING,
125                     "%L.toString()",
126                     stringBuilderVar
127                 )
128                 if (outArgsName != null) {
129                     val argCount = scope.getTmpVar("_argCount")
130                     addLocalVal(
131                         argCount,
132                         XTypeName.PRIMITIVE_INT,
133                         "%L%L",
134                         knownQueryArgsCount,
135                         listSizeVars.joinToString("") { " + ${it.second}" }
136                     )
137                     addLocalVariable(
138                         name = outArgsName,
139                         typeName = RoomTypeNames.ROOM_SQL_QUERY,
140                         assignExpr =
141                             XCodeBlock.of(
142                                 "%M(%L, %L)",
143                                 RoomMemberNames.ROOM_SQL_QUERY_ACQUIRE,
144                                 outSqlQueryName,
145                                 argCount
146                             )
147                     )
148                 }
149             } else {
150                 addLocalVal(
151                     outSqlQueryName,
152                     CommonTypeNames.STRING,
153                     "%S",
154                     query.queryWithReplacedBindParams
155                 )
156                 if (outArgsName != null) {
157                     addLocalVariable(
158                         name = outArgsName,
159                         typeName = RoomTypeNames.ROOM_SQL_QUERY,
160                         assignExpr =
161                             XCodeBlock.of(
162                                 "%M(%L, %L)",
163                                 RoomMemberNames.ROOM_SQL_QUERY_ACQUIRE,
164                                 outSqlQueryName,
165                                 knownQueryArgsCount
166                             )
167                     )
168                 }
169             }
170         }
171         return listSizeVars
172     }
173 
174     fun bindArgs(
175         outArgsName: String,
176         listSizeVars: List<Pair<QueryParameter, String>>,
177         scope: CodeGenScope
178     ) {
179         if (parameters.isEmpty()) {
180             return
181         }
182         scope.builder.apply {
183             val argIndex = scope.getTmpVar("_argIndex")
184             addLocalVariable(
185                 name = argIndex,
186                 typeName = XTypeName.PRIMITIVE_INT,
187                 isMutable = true,
188                 assignExpr = XCodeBlock.of("%L", 1)
189             )
190             // # of bindings with 1 placeholder
191             var constInputs = 0
192             // variable names for size of the bindings that have multiple  args
193             val varInputs = arrayListOf<String>()
194             sectionToParamMapping.forEach { (_, param) ->
195                 // reset the argIndex to the correct start index
196                 if (constInputs > 0 || varInputs.isNotEmpty()) {
197                     addStatement(
198                         "%L = %L%L",
199                         argIndex,
200                         if (constInputs > 0) {
201                             1 + constInputs
202                         } else {
203                             "1"
204                         },
205                         varInputs.joinToString("") { " + $it" }
206                     )
207                 }
208                 param?.let {
209                     it.queryParamAdapter?.bindToStmt(it.name, outArgsName, argIndex, scope)
210                 }
211                 // add these to the list so that we can use them to calculate the next count.
212                 val sizeVar = listSizeVars.firstOrNull { it.first == param }
213                 if (sizeVar == null) {
214                     constInputs++
215                 } else {
216                     varInputs.add(sizeVar.second)
217                 }
218             }
219         }
220     }
221 }
222