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.ext.L
20 import androidx.room.ext.RoomTypeNames.ROOM_SQL_QUERY
21 import androidx.room.ext.RoomTypeNames.STRING_UTIL
22 import androidx.room.ext.S
23 import androidx.room.ext.T
24 import androidx.room.ext.typeName
25 import androidx.room.parser.ParsedQuery
26 import androidx.room.parser.Section
27 import androidx.room.parser.SectionType.BIND_VAR
28 import androidx.room.parser.SectionType.NEWLINE
29 import androidx.room.parser.SectionType.TEXT
30 import androidx.room.solver.CodeGenScope
31 import androidx.room.vo.QueryMethod
32 import androidx.room.vo.QueryParameter
33 import com.squareup.javapoet.ClassName
34 import com.squareup.javapoet.TypeName
35 
36 /**
37  * Writes the SQL query and arguments for a QueryMethod.
38  */
39 class QueryWriter constructor(val parameters: List<QueryParameter>,
40                               val sectionToParamMapping: List<Pair<Section, QueryParameter?>>,
41                               val query: ParsedQuery) {
42 
43     constructor(queryMethod: QueryMethod) : this(queryMethod.parameters,
44             queryMethod.sectionToParamMapping, queryMethod.query)
45 
46     fun prepareReadAndBind(outSqlQueryName: String, outRoomSQLiteQueryVar: String,
47                            scope: CodeGenScope) {
48         val listSizeVars = createSqlQueryAndArgs(outSqlQueryName, outRoomSQLiteQueryVar, scope)
49         bindArgs(outRoomSQLiteQueryVar, listSizeVars, scope)
50     }
51 
52     fun prepareQuery(
53             outSqlQueryName: String, scope: CodeGenScope): List<Pair<QueryParameter, String>> {
54         return createSqlQueryAndArgs(outSqlQueryName, null, scope)
55     }
56 
57     private fun createSqlQueryAndArgs(
58             outSqlQueryName: String,
59             outArgsName: String?,
60             scope: CodeGenScope
61     ): List<Pair<QueryParameter, String>> {
62         val listSizeVars = arrayListOf<Pair<QueryParameter, String>>()
63         val varargParams = parameters
64                 .filter { it.queryParamAdapter?.isMultiple ?: false }
65         val sectionToParamMapping = sectionToParamMapping
66         val knownQueryArgsCount = sectionToParamMapping.filterNot {
67             it.second?.queryParamAdapter?.isMultiple ?: false
68         }.size
69         scope.builder().apply {
70             if (varargParams.isNotEmpty()) {
71                 val stringBuilderVar = scope.getTmpVar("_stringBuilder")
72                 addStatement("$T $L = $T.newStringBuilder()",
73                         ClassName.get(StringBuilder::class.java), stringBuilderVar, STRING_UTIL)
74                 query.sections.forEach {
75                     when (it.type) {
76                         TEXT -> addStatement("$L.append($S)", stringBuilderVar, it.text)
77                         NEWLINE -> addStatement("$L.append($S)", stringBuilderVar, "\n")
78                         BIND_VAR -> {
79                             // If it is null, will be reported as error before. We just try out
80                             // best to generate as much code as possible.
81                             sectionToParamMapping.firstOrNull { mapping ->
82                                 mapping.first == it
83                             }?.let { pair ->
84                                 if (pair.second?.queryParamAdapter?.isMultiple ?: false) {
85                                     val tmpCount = scope.getTmpVar("_inputSize")
86                                     listSizeVars.add(Pair(pair.second!!, tmpCount))
87                                     pair.second
88                                             ?.queryParamAdapter
89                                             ?.getArgCount(pair.second!!.name, tmpCount, scope)
90                                     addStatement("$T.appendPlaceholders($L, $L)",
91                                             STRING_UTIL, stringBuilderVar, tmpCount)
92                                 } else {
93                                     addStatement("$L.append($S)", stringBuilderVar, "?")
94                                 }
95                             }
96                         }
97                     }
98                 }
99 
100                 addStatement("final $T $L = $L.toString()", String::class.typeName(),
101                         outSqlQueryName, stringBuilderVar)
102                 if (outArgsName != null) {
103                     val argCount = scope.getTmpVar("_argCount")
104                     addStatement("final $T $L = $L$L", TypeName.INT, argCount, knownQueryArgsCount,
105                             listSizeVars.joinToString("") { " + ${it.second}" })
106                     addStatement("final $T $L = $T.acquire($L, $L)",
107                             ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
108                             argCount)
109                 }
110             } else {
111                 addStatement("final $T $L = $S", String::class.typeName(),
112                         outSqlQueryName, query.queryWithReplacedBindParams)
113                 if (outArgsName != null) {
114                     addStatement("final $T $L = $T.acquire($L, $L)",
115                             ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
116                             knownQueryArgsCount)
117                 }
118             }
119         }
120         return listSizeVars
121     }
122 
123     fun bindArgs(
124             outArgsName: String,
125             listSizeVars: List<Pair<QueryParameter, String>>,
126             scope: CodeGenScope
127     ) {
128         if (parameters.isEmpty()) {
129             return
130         }
131         scope.builder().apply {
132             val argIndex = scope.getTmpVar("_argIndex")
133             addStatement("$T $L = $L", TypeName.INT, argIndex, 1)
134             // # of bindings with 1 placeholder
135             var constInputs = 0
136             // variable names for size of the bindings that have multiple  args
137             val varInputs = arrayListOf<String>()
138             sectionToParamMapping.forEach { pair ->
139                 // reset the argIndex to the correct start index
140                 if (constInputs > 0 || varInputs.isNotEmpty()) {
141                     addStatement("$L = $L$L", argIndex,
142                             if (constInputs > 0) (1 + constInputs) else "1",
143                             varInputs.joinToString("") { " + $it" })
144                 }
145                 val param = pair.second
146                 param?.let {
147                     param.queryParamAdapter?.bindToStmt(param.name, outArgsName, argIndex, scope)
148                 }
149                 // add these to the list so that we can use them to calculate the next count.
150                 val sizeVar = listSizeVars.firstOrNull { it.first == param }
151                 if (sizeVar == null) {
152                     constInputs ++
153                 } else {
154                     varInputs.add(sizeVar.second)
155                 }
156             }
157         }
158     }
159 }
160