/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.room.parser.expansion import androidx.room.parser.ParserErrors import androidx.room.parser.QueryType import androidx.room.parser.Table import androidx.room.verifier.QueryResultInfo /** * ExpandableSqlParser parses the query in more detail such that we know when an ALL projection is * used (select *). This is information is only ever used if `room.expandProjection` is set to * true and that feature will be deprecated. * * This file stays for backwards compatibility purposes. */ sealed class ExpandableSection { abstract val text: String data class Text(val content: String) : ExpandableSection() { override val text: String get() = content } object Newline : ExpandableSection() { override val text: String get() = "\n" } data class BindVar(val symbol: String) : ExpandableSection() { override val text: String get() = symbol } sealed class Projection : ExpandableSection() { object All : Projection() { override val text: String get() = "*" } data class Table( val tableAlias: String, override val text: String ) : Projection() } } data class Position(val line: Int, val charInLine: Int) : Comparable { override fun compareTo(other: Position): Int { return if (line == other.line) { charInLine - other.charInLine } else { line - other.line } } } data class SectionInfo( val start: Position, val end: Position, val section: ExpandableSection ) data class ExpandableParsedQuery( // original query as written by user, code writers should use transformed query that has been // processed and optimized. val original: String, val type: QueryType, val inputs: List, val projections: List, val explicitColumns: List, val tables: Set, val syntaxErrors: List, val runtimeQueryPlaceholder: Boolean ) { companion object { val STARTS_WITH_NUMBER = "^\\?[0-9]".toRegex() val MISSING = ExpandableParsedQuery( original = "missing query", type = QueryType.UNKNOWN, projections = emptyList(), explicitColumns = emptyList(), inputs = emptyList(), tables = emptySet(), syntaxErrors = emptyList(), runtimeQueryPlaceholder = false ) } /** * Optional data that might be assigned when the query is parsed inside an annotation processor. * User may turn this off or it might be disabled for any reason so generated code should * always handle not having it. */ var resultInfo: QueryResultInfo? = null /** * The transformed query when it is interpreted and rewritten by QueryInterpreter. */ var transformed: String = original private set val sections by lazy { val specialSections: List = (inputs + projections).sortedBy { it.start } val lines = original.lines() val sections = arrayListOf() var index = 0 var charInLine = 0 while (index < lines.size) { val line = lines[index] var multipleLineSection = false specialSections .filter { it.start.line == index } .forEach { (start, end, section) -> if (charInLine < start.charInLine) { sections.add( ExpandableSection.Text( line.substring( charInLine, start.charInLine ) ) ) } sections.add(section) charInLine = end.charInLine if (index < end.line) { index = end.line multipleLineSection = true } } if (!multipleLineSection) { if (charInLine < line.length) { sections.add(ExpandableSection.Text(line.substring(charInLine))) } if (index + 1 < lines.size) { sections.add(ExpandableSection.Newline) } index++ charInLine = 0 } } sections } val bindSections by lazy { sections.filterIsInstance() } private fun unnamedVariableErrors(): List { val anonymousBindError = if (inputs.any { it.section.text == "?" }) { arrayListOf(ParserErrors.ANONYMOUS_BIND_ARGUMENT) } else { emptyList() } return anonymousBindError + inputs.filter { it.section.text.matches(STARTS_WITH_NUMBER) }.map { ParserErrors.cannotUseVariableIndices(it.section.text, it.start.charInLine) } } private fun unknownQueryTypeErrors(): List { return if (QueryType.SUPPORTED.contains(type)) { emptyList() } else { listOf(ParserErrors.invalidQueryType(type)) } } val errors by lazy { if (syntaxErrors.isNotEmpty()) { // if there is a syntax error, don't report others since they might be misleading. syntaxErrors } else { unnamedVariableErrors() + unknownQueryTypeErrors() } } }