1 /*
2  * Copyright (C) 2017 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 package androidx.sqlite.db
17 
18 import java.util.regex.Pattern
19 
20 /** A simple query builder to create SQL SELECT queries. */
21 public class SupportSQLiteQueryBuilder private constructor(private val table: String) {
22     private var distinct = false
23     private var columns: Array<out String>? = null
24     private var selection: String? = null
25     private var bindArgs: Array<out Any?>? = null
26     private var groupBy: String? = null
27     private var having: String? = null
28     private var orderBy: String? = null
29     private var limit: String? = null
30 
31     /**
32      * Adds DISTINCT keyword to the query.
33      *
34      * @return this
35      */
<lambda>null36     public fun distinct(): SupportSQLiteQueryBuilder = apply { this.distinct = true }
37 
38     /**
39      * Sets the given list of columns as the columns that will be returned.
40      *
41      * @param columns The list of column names that should be returned.
42      * @return this
43      */
<lambda>null44     public fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
45         this.columns = columns
46     }
47 
48     /**
49      * Sets the arguments for the WHERE clause.
50      *
51      * @param selection The list of selection columns
52      * @param bindArgs The list of bind arguments to match against these columns
53      * @return this
54      */
selectionnull55     public fun selection(
56         selection: String?,
57         bindArgs: Array<out Any?>?
58     ): SupportSQLiteQueryBuilder = apply {
59         this.selection = selection
60         this.bindArgs = bindArgs
61     }
62 
63     /**
64      * Adds a GROUP BY statement.
65      *
66      * @param groupBy The value of the GROUP BY statement.
67      * @return this
68      */
<lambda>null69     public fun groupBy(groupBy: String?): SupportSQLiteQueryBuilder = apply {
70         this.groupBy = groupBy
71     }
72 
73     /**
74      * Adds a HAVING statement. You must also provide [groupBy] for this to work.
75      *
76      * @param having The having clause.
77      * @return this
78      */
<lambda>null79     public fun having(having: String?): SupportSQLiteQueryBuilder = apply { this.having = having }
80 
81     /**
82      * Adds an ORDER BY statement.
83      *
84      * @param orderBy The order clause.
85      * @return this
86      */
<lambda>null87     public fun orderBy(orderBy: String?): SupportSQLiteQueryBuilder = apply {
88         this.orderBy = orderBy
89     }
90 
91     /**
92      * Adds a LIMIT statement.
93      *
94      * @param limit The limit value.
95      * @return this
96      */
<lambda>null97     public fun limit(limit: String): SupportSQLiteQueryBuilder = apply {
98         val patternMatches = limitPattern.matcher(limit).matches()
99         require(limit.isEmpty() || patternMatches) { "invalid LIMIT clauses:$limit" }
100         this.limit = limit
101     }
102 
103     /**
104      * Creates the [SupportSQLiteQuery] that can be passed into [SupportSQLiteDatabase.query].
105      *
106      * @return a new query
107      */
createnull108     public fun create(): SupportSQLiteQuery {
109         require(!groupBy.isNullOrEmpty() || having.isNullOrEmpty()) {
110             "HAVING clauses are only permitted when using a groupBy clause"
111         }
112         val query =
113             buildString(120) {
114                 append("SELECT ")
115                 if (distinct) {
116                     append("DISTINCT ")
117                 }
118                 if (!columns.isNullOrEmpty()) {
119                     appendColumns(columns!!)
120                 } else {
121                     append("* ")
122                 }
123                 append("FROM ")
124                 append(table)
125                 appendClause(" WHERE ", selection)
126                 appendClause(" GROUP BY ", groupBy)
127                 appendClause(" HAVING ", having)
128                 appendClause(" ORDER BY ", orderBy)
129                 appendClause(" LIMIT ", limit)
130             }
131         return SimpleSQLiteQuery(query, bindArgs)
132     }
133 
StringBuildernull134     private fun StringBuilder.appendClause(name: String, clause: String?) {
135         if (!clause.isNullOrEmpty()) {
136             append(name)
137             append(clause)
138         }
139     }
140 
141     /** Add the names that are non-null in columns to string, separating them with commas. */
StringBuildernull142     private fun StringBuilder.appendColumns(columns: Array<out String>) {
143         val n = columns.size
144         for (i in 0 until n) {
145             val column = columns[i]
146             if (i > 0) {
147                 append(", ")
148             }
149             append(column)
150         }
151         append(' ')
152     }
153 
154     public companion object {
155         private val limitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?")
156 
157         /**
158          * Creates a query for the given table name.
159          *
160          * @param tableName The table name(s) to query.
161          * @return A builder to create a query.
162          */
163         @JvmStatic
buildernull164         public fun builder(tableName: String): SupportSQLiteQueryBuilder {
165             return SupportSQLiteQueryBuilder(tableName)
166         }
167     }
168 }
169