diff --git a/README.md b/README.md index 7a7f292..e775dd5 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Kotlin](https://img.shields.io/badge/Kotlin-2.2.21+-purple.svg?style=flat&logo=kotlin)](https://kotlinlang.org) [![Android](https://img.shields.io/badge/Android-API%2021+-green.svg?style=flat&logo=android)](https://developer.android.com) [![Room](https://img.shields.io/badge/Room-Compatible-blue.svg?style=flat)](https://developer.android.com/training/data-storage/room) +[![Retrofit](https://img.shields.io/badge/Retrofit-Compatible-red.svg?style=flat)]([https://developer.android.com/training/data-storage/room](https://square.github.io/retrofit/)) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/LeandroLCD/android-sql-query) [![Run Query Unit Tests](https://github.com/LeandroLCD/android-sql-query/actions/workflows/run-query-tests.yml/badge.svg)](https://github.com/LeandroLCD/android-sql-query/actions/workflows/run-query-tests.yml) @@ -35,7 +36,7 @@ DiseƱada para integrarse perfectamente con la base de datos de Android y Room a - [DELETE](#-delete) - [INNER JOIN](#-inner-join) - [šŸ”— Integración con Room](#-integración-con-room) -- [šŸ“” RepeatedQueryParameters](#-repeatedqueryparameters) +- [šŸ“” Integración con retrofit -> RepeatedQueryParameters](#-repeatedqueryparameters) - [šŸ“ Ejemplos Avanzados](#-ejemplos-avanzados) - [šŸ¤ Contribuir](#-contribuir) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ec804f..cb082f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.13.1" +agp = "8.13.2" kotlin = "2.2.21" coreKtx = "1.17.0" junit = "4.13.2" diff --git a/query/src/main/java/com/blipblipcode/query/QuerySelect.kt b/query/src/main/java/com/blipblipcode/query/QuerySelect.kt index 59c98ee..f8455f4 100644 --- a/query/src/main/java/com/blipblipcode/query/QuerySelect.kt +++ b/query/src/main/java/com/blipblipcode/query/QuerySelect.kt @@ -17,7 +17,7 @@ import com.blipblipcode.query.operator.SQLOperator * @property fields The list of columns to be returned in the result set. Defaults to "*". */ class QuerySelect private constructor( - private var where: SQLOperator<*>?, + private var where: Pair>?, private val table: String, private val operations: LinkedHashMap, private val fields: List @@ -37,6 +37,21 @@ class QuerySelect private constructor( return QueryBuilder(table, LinkedHashMap()) } } + /** + * Creates a new `QueryBuilder` instance initialized with the current state of this `QuerySelect`. + * @param consumer A lambda that receives the `QueryBuilder` to customize the new query. + * @return A new `QueryBuilder` instance. + */ + fun newBuilder(consumer:(QueryBuilder)-> Unit): QueryBuilder { + val builder = QueryBuilder(table, operations) + where?.let { builder.where(it.first, it.second) } + builder.setFields(*fields.toTypedArray()) + orderBy?.let { builder.orderBy(it) } + limit?.let { builder.limit(it) } + return builder.apply { + consumer.invoke(this) + } + } /** * Removes a logical operation by its key. @@ -64,7 +79,15 @@ class QuerySelect private constructor( * @return The current `QuerySelect` instance for chaining. */ fun setWhere(operator: SQLOperator<*>): QuerySelect { - where = operator + where = operator.column to operator + return this + }/** + * Sets or replaces the main WHERE clause of the query. + * @param operator The new SQL operator for the WHERE clause. + * @return The current `QuerySelect` instance for chaining. + */ + fun setWhere(key: String, operator: SQLOperator<*>): QuerySelect { + where = key to operator return this } @@ -100,13 +123,20 @@ class QuerySelect private constructor( override fun getSqlOperators(): List> { return buildList { - where?.let { add(it) } + where?.let { add(it.second) } operations.values.forEach { add(it.operator) } orderBy?.let { add(it) } limit?.let { add(it) } } } + fun getOperations(): Map> { + return buildMap { + where?.let { put(it.first, it.second) } + operations.forEach { put(it.key, it.value.operator) } + } + } + override fun getTableName(): String { return table } @@ -126,7 +156,7 @@ class QuerySelect private constructor( if (where == null) { append("SELECT $fieldStr FROM $table") }else{ - append("SELECT $fieldStr FROM $table WHERE ${where?.toSQLString()} $operationsStr".trim()) + append("SELECT $fieldStr FROM $table WHERE ${where?.second?.toSQLString()} $operationsStr".trim()) } if (orderBy != null) { append(" ") @@ -150,7 +180,7 @@ class QuerySelect private constructor( if (where == null) { append("SELECT $fieldStr FROM $table") }else{ - append("SELECT $fieldStr FROM $table WHERE ${where?.toSQLString()} $operationsStr".trim()) + append("SELECT $fieldStr FROM $table WHERE ${where?.second?.toSQLString()} $operationsStr".trim()) } if (orderBy != null) { append(" ") @@ -211,7 +241,7 @@ class QuerySelect private constructor( private val table: String, private val operations: LinkedHashMap ) { - private var where: SQLOperator<*>? = null + private var where: Pair>? = null private var fields: List = listOf("*") private var orderBy: OrderBy? = null private var limit: Limit? = null @@ -324,7 +354,17 @@ class QuerySelect private constructor( * @return The `QueryBuilder` instance for chaining. */ fun where(operator: SQLOperator<*>): QueryBuilder { - where = operator + where = operator.column to operator + return this + } + + /** + * Sets the main WHERE clause for the query. + * @param operator The SQL operator for the WHERE clause. + * @return The `QueryBuilder` instance for chaining. + */ + fun where(key: String, operator: SQLOperator<*>): QueryBuilder { + where = key to operator return this } @@ -376,6 +416,27 @@ class QuerySelect private constructor( return this } + /** + * Transforms an existing logical operation by its key. + * @param key The key of the logical operation to transform. + * @param transform A lambda that takes the existing LogicalOperation and returns a new one. + * @return The `QueryBuilder` instance for chaining. + */ + fun transformOperation(key:String, transform: (LogicalOperation) -> LogicalOperation): QueryBuilder { + when { + operations.containsKey(key) -> { + val operator = operations[key] + val newOperations = transform(operator!!) + operations[key] = newOperations + } + where?.first == key -> { + val newOperations = transform(LogicalOperation(LogicalType.AND, where!!.second)) + where = where?.copy(second = newOperations.operator) + } + else -> Unit + } + return this + } /** * Builds the `QuerySelect` instance. * @return A new `QuerySelect` object. diff --git a/query/src/main/java/com/blipblipcode/query/UnionQuery.kt b/query/src/main/java/com/blipblipcode/query/UnionQuery.kt index 6e5bd8e..603f8ee 100644 --- a/query/src/main/java/com/blipblipcode/query/UnionQuery.kt +++ b/query/src/main/java/com/blipblipcode/query/UnionQuery.kt @@ -1,5 +1,6 @@ package com.blipblipcode.query +import com.blipblipcode.query.operator.LogicalOperation import com.blipblipcode.query.operator.OrderBy import com.blipblipcode.query.operator.SQLOperator @@ -94,21 +95,55 @@ class UnionQuery private constructor( fun getOrderBy(): OrderBy? { return orderBy } - + /** + * Creates a new `QueryBuilder` initialized with the current state of this `UnionQuery`. + * This allows for further modifications or additions to the existing union query. + * + * @param consumer A lambda that receives the `QueryBuilder` for further configuration. + * @return A new `QueryBuilder` instance initialized with the current queries and union type. + */ + fun newBuilder(consumer:(QueryBuilder)-> Unit): QueryBuilder { + val builder = QueryBuilder() + builder.addQueries(queries) + if(useUnionAll){ + builder.unionAll() + }else{ + builder.union() + } + consumer(builder) + return builder + } /** * A builder for creating `UnionQuery` instances. * This class provides a fluent API to construct a UNION query. */ - class Builder { + class QueryBuilder { private val queries = mutableListOf() private var useUnionAll = false + + /** + * Transforms an existing logical operation by its key. + * @param key The key of the logical operation to transform. + * @param transform A lambda that takes the existing LogicalOperation and returns a new one. + * @return The `QueryBuilder` instance for chaining. + */ + fun transformOperation(key:String, transform: (LogicalOperation) -> LogicalOperation): QueryBuilder { + queries.forEachIndexed { index, querySelect -> + val newQuery = querySelect.newBuilder { qb -> + qb.transformOperation(key, transform) + }.build() + queries[index] = newQuery + } + return this + } + /** * Adds a query to the union. * @param query The `QuerySelect` to add. * @return The `Builder` instance for chaining. */ - fun addQuery(query: QuerySelect): Builder { + fun addQuery(query: QuerySelect): QueryBuilder { queries.add(query) return this } @@ -118,7 +153,7 @@ class UnionQuery private constructor( * @param queries The list of `QuerySelect` objects to add. * @return The `Builder` instance for chaining. */ - fun addQueries(queries: List): Builder { + fun addQueries(queries: List): QueryBuilder { this.queries.addAll(queries) return this } @@ -127,7 +162,7 @@ class UnionQuery private constructor( * Sets the union type to UNION ALL. * @return The `Builder` instance for chaining. */ - fun unionAll(): Builder { + fun unionAll(): QueryBuilder { useUnionAll = true return this } @@ -136,7 +171,7 @@ class UnionQuery private constructor( * Sets the union type to UNION (default). * @return The `Builder` instance for chaining. */ - fun union(): Builder { + fun union(): QueryBuilder { useUnionAll = false return this } @@ -158,8 +193,8 @@ class UnionQuery private constructor( * @param baseQuery The first `QuerySelect` in the union. * @return A new `Builder` instance. */ - fun builder(baseQuery: QuerySelect): Builder { - return Builder().also { builder -> + fun builder(baseQuery: QuerySelect): QueryBuilder { + return QueryBuilder().also { builder -> builder.addQuery(baseQuery) } } @@ -169,10 +204,29 @@ class UnionQuery private constructor( * @param baseQuery The first `QuerySelect` in the union. * @return A new `Builder` instance configured for UNION ALL. */ - fun builderAll(baseQuery: QuerySelect): Builder { - return Builder().also { builder -> + fun builderAll(baseQuery: QuerySelect): QueryBuilder { + return QueryBuilder().also { builder -> builder.addQuery(baseQuery).unionAll() } } } } +fun main() { + // Example usage: + val query1 = QuerySelect.builder("users") + .where(SQLOperator.Equals("age", 30)) + .and(SQLOperator.Equals("id", 30)).orderBy( + OrderBy.Asc("name")).build() + val query2 = QuerySelect.builder("admins") + .where(SQLOperator.Equals("edad", 30)) + .and(SQLOperator.Equals("ege", 30)) + .limit(10) + .orderBy(OrderBy.Desc("id")).build() + + val unionQuery = UnionQuery.builder(query1) + .addQuery(query2) + .unionAll() + .build() + + println(unionQuery.asSql()) +} \ No newline at end of file diff --git a/query/src/main/java/com/blipblipcode/query/operator/SQLOperator.kt b/query/src/main/java/com/blipblipcode/query/operator/SQLOperator.kt index b4c4368..6896c22 100644 --- a/query/src/main/java/com/blipblipcode/query/operator/SQLOperator.kt +++ b/query/src/main/java/com/blipblipcode/query/operator/SQLOperator.kt @@ -152,8 +152,8 @@ sealed interface SQLOperator { override fun toSQLString(): String { val startStr = caseConversion.asSqlFunction(start.toString()) val endStr = caseConversion.asSqlFunction(end.toString()) - return "${caseConversion.asSqlFunction(column)} $symbol $startStr AND $endStr" + return "${caseConversion.asSqlFunction(column)} $symbol '$startStr' AND '$endStr'" } - override fun asString(): String = "${caseConversion.asSqlFunction(column)} $symbol ${caseConversion.asSqlFunction(start.toString())} AND ${caseConversion.asSqlFunction(start.toString())}" + override fun asString(): String = "${caseConversion.asSqlFunction(column)} $symbol '${caseConversion.asSqlFunction(start.toString())}' AND '${caseConversion.asSqlFunction(start.toString())}'" } } diff --git a/query/src/main/java/com/blipblipcode/query/utils/Extensions.kt b/query/src/main/java/com/blipblipcode/query/utils/Extensions.kt index f01b7b7..b8a1aac 100644 --- a/query/src/main/java/com/blipblipcode/query/utils/Extensions.kt +++ b/query/src/main/java/com/blipblipcode/query/utils/Extensions.kt @@ -81,7 +81,7 @@ fun QuerySelect.unionAll(other: QuerySelect): UnionQuery { * @return A new `UnionQuery` instance containing the new query. */ fun UnionQuery.addQuery(query: QuerySelect): UnionQuery { - return UnionQuery.Builder() + return UnionQuery.QueryBuilder() .addQueries(this.queries) .addQuery(query) .apply { if (this@addQuery.useUnionAll) unionAll() else union() }