Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a595b16
feat(Query): Automatically use the first logical operation as the WHE…
LeandroLCD Nov 17, 2025
20e71ac
test(QueryDelete): Fix test for building without a WHERE clause
LeandroLCD Nov 17, 2025
6edf9d8
Update query/src/main/java/com/blipblipcode/query/QueryDelete.kt
LeandroLCD Nov 17, 2025
cd823c1
Update query/src/main/java/com/blipblipcode/query/QuerySelect.kt
LeandroLCD Nov 18, 2025
0ef87ec
feat(QuerySelect): Allow queries without a WHERE clause
LeandroLCD Nov 18, 2025
5899c58
Merge remote-tracking branch 'origin/master' into develop
LeandroLCD Nov 18, 2025
8cb9e02
Merge branch 'master' into develop
LeandroLCD Nov 18, 2025
5200a40
Merge remote-tracking branch 'origin/develop' into develop
LeandroLCD Nov 20, 2025
ac5b7fb
chore(deps): Update AGP to 8.13.1
LeandroLCD Nov 20, 2025
c0b92b6
docs(UnionQuery): Fix KDoc for orderBy parameter
LeandroLCD Nov 20, 2025
664d910
feat(Query): Add case-insensitive comparison support
LeandroLCD Nov 26, 2025
8bb0d5a
chore(deps): Update various dependencies
LeandroLCD Dec 8, 2025
c4c208f
chore(README): Update Kotlin version badge
LeandroLCD Dec 8, 2025
8b3be0d
refactor(Field): Implement SQLOperator for consistency
LeandroLCD Dec 8, 2025
8a9c3e0
feat(Queryable): Add filtered `asSql` method
LeandroLCD Dec 8, 2025
8f41579
feat(Queryable): Add predicate-based SQL generation and clear functio…
LeandroLCD Dec 8, 2025
4d6156b
refactor(SQLOperator): Standardize operator implementations
LeandroLCD Dec 8, 2025
e667492
feat(retrofit): Add RepeatedQueryParameters for Retrofit @QueryMap
LeandroLCD Dec 8, 2025
d797d6d
feat(Queryable): Add `asQueryRepeatedQueryParameters` extension
LeandroLCD Dec 8, 2025
a5d00b2
feat(QuerySelect): Include orderBy and limit in getSqlOperators
LeandroLCD Dec 8, 2025
ed4e44c
feat(Retrofit): Add `RepeatedQueryParameters` for Retrofit integration
LeandroLCD Dec 8, 2025
c1cd2e1
feat(Retrofit): Add `RepeatedQueryParameters` for Retrofit integration
LeandroLCD Dec 8, 2025
880424b
Merge branch 'master' into develop
LeandroLCD Dec 9, 2025
97bbc8b
docs(QuerySelect): Improve KDoc for getOrderBy function
LeandroLCD Dec 9, 2025
27c6476
Add Retrofit badge and update README links
LeandroLCD Dec 9, 2025
c7f40a6
fix(SQLOperator): Correctly quote values in `Between` operator
LeandroLCD Dec 10, 2025
39c0117
Merge branch 'master' into develop
LeandroLCD Dec 10, 2025
135aed3
chore(deps): Update AGP to 8.13.2
LeandroLCD Dec 26, 2025
2eac0b0
chore(UnionQuery): Remove unused imports
LeandroLCD Dec 26, 2025
b9a220e
Merge remote-tracking branch 'origin/develop' into develop
LeandroLCD Dec 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
75 changes: 68 additions & 7 deletions query/src/main/java/com/blipblipcode/query/QuerySelect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String,SQLOperator<*>>?,
private val table: String,
private val operations: LinkedHashMap<String, LogicalOperation>,
private val fields: List<String>
Expand All @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -100,13 +123,20 @@ class QuerySelect private constructor(

override fun getSqlOperators(): List<SQLOperator<*>> {
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<String, SQLOperator<*>> {
return buildMap {
where?.let { put(it.first, it.second) }
operations.forEach { put(it.key, it.value.operator) }
}
}

override fun getTableName(): String {
return table
}
Expand All @@ -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(" ")
Expand All @@ -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(" ")
Expand Down Expand Up @@ -211,7 +241,7 @@ class QuerySelect private constructor(
private val table: String,
private val operations: LinkedHashMap<String, LogicalOperation>
) {
private var where: SQLOperator<*>? = null
private var where: Pair<String, SQLOperator<*>>? = null
private var fields: List<String> = listOf("*")
private var orderBy: OrderBy? = null
private var limit: Limit? = null
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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.
Expand Down
74 changes: 64 additions & 10 deletions query/src/main/java/com/blipblipcode/query/UnionQuery.kt
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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<QuerySelect>()
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
}
Expand All @@ -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<QuerySelect>): Builder {
fun addQueries(queries: List<QuerySelect>): QueryBuilder {
this.queries.addAll(queries)
return this
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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)
}
}
Expand All @@ -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())
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ sealed interface SQLOperator<T> {
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())}'"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
Expand Down