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