Twelve: Refactor Query
diff --git a/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt b/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt
index 9e22001..32d668c 100644
--- a/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt
+++ b/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt
@@ -34,9 +34,9 @@
import org.lineageos.twelve.models.RequestStatus
import org.lineageos.twelve.query.Query
import org.lineageos.twelve.query.and
+import org.lineageos.twelve.query.query
import org.lineageos.twelve.query.eq
import org.lineageos.twelve.query.`in`
-import org.lineageos.twelve.query.join
import org.lineageos.twelve.query.like
import org.lineageos.twelve.query.neq
@@ -201,8 +201,9 @@
albumsUri,
albumsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AlbumColumns.ALBUM like Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AlbumColumns.ALBUM like Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(query),
)
).mapEachRow(albumsProjection, mapAlbum),
@@ -210,8 +211,9 @@
artistsUri,
artistsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.ArtistColumns.ARTIST like Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.ArtistColumns.ARTIST like Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(query),
)
).mapEachRow(artistsProjection, mapArtist),
@@ -219,8 +221,9 @@
audiosUri,
audiosProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns.TITLE like Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns.TITLE like Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(query),
)
).mapEachRow(audiosProjection, mapAudio),
@@ -228,8 +231,9 @@
genresUri,
genresProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.GenresColumns.NAME like Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.GenresColumns.NAME like Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(query),
)
).mapEachRow(genresProjection, mapGenre),
@@ -241,8 +245,9 @@
audiosUri,
audiosProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns._ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns._ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(audioUri).toString(),
),
@@ -258,8 +263,9 @@
albumsUri,
albumsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns._ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns._ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(albumUri).toString(),
),
@@ -269,8 +275,9 @@
audiosUri,
audiosProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns.ALBUM_ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns.ALBUM_ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(albumUri).toString(),
),
@@ -287,8 +294,9 @@
artistsUri,
artistsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns._ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns._ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(artistUri).toString(),
),
@@ -298,8 +306,9 @@
albumsUri,
albumsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AlbumColumns.ARTIST_ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AlbumColumns.ARTIST_ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(artistUri).toString(),
),
@@ -309,8 +318,9 @@
audiosUri,
audioAlbumIdsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns.ARTIST_ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns.ARTIST_ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(artistUri).toString(),
),
@@ -324,12 +334,12 @@
albumsUri,
albumsProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to listOf(
- MediaStore.Audio.AudioColumns.ARTIST_ID neq Query.ARG,
- MediaStore.Audio.AudioColumns._ID `in` List(albumIds.size) {
- Query.ARG
- },
- ).join(Query::and).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ (MediaStore.Audio.AudioColumns.ARTIST_ID neq Query.ARG) and
+ (MediaStore.Audio.AudioColumns._ID `in` List(albumIds.size) {
+ Query.ARG
+ })
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(artistUri).toString(),
*albumIds
@@ -356,8 +366,9 @@
genresUri,
genresProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns._ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns._ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(genreUri).toString(),
),
@@ -367,8 +378,9 @@
audiosUri,
audiosProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns.GENRE_ID eq Query.ARG).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns.GENRE_ID eq Query.ARG
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to arrayOf(
ContentUris.parseId(genreUri).toString(),
),
@@ -455,10 +467,11 @@
audiosUri,
audiosProjection,
bundleOf(
- ContentResolver.QUERY_ARG_SQL_SELECTION to
- (MediaStore.Audio.AudioColumns._ID `in` List(audioUris.size) {
- Query.ARG
- }).build(),
+ ContentResolver.QUERY_ARG_SQL_SELECTION to query {
+ MediaStore.Audio.AudioColumns._ID `in` List(audioUris.size) {
+ Query.ARG
+ }
+ },
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to audioUris.map {
ContentUris.parseId(it).toString()
}.toTypedArray(),
diff --git a/app/src/main/java/org/lineageos/twelve/query/Query.kt b/app/src/main/java/org/lineageos/twelve/query/Query.kt
index e9f7fa6..99ad2c7 100644
--- a/app/src/main/java/org/lineageos/twelve/query/Query.kt
+++ b/app/src/main/java/org/lineageos/twelve/query/Query.kt
@@ -7,49 +7,36 @@
typealias Column = String
-sealed interface Node {
- fun build(): String = when (this) {
- is And -> "(${lhs.build()}) AND (${rhs.build()})"
- is Eq -> "${lhs.build()} = ${rhs.build()}"
- is Neq -> "${lhs.build()} != ${rhs.build()}"
- is In<*> -> "$value IN (${values.joinToString(", ")})"
- is Like -> "${lhs.build()} LIKE ${rhs.build()}"
- is Literal<*> -> "$`val`"
- is Or -> "(${lhs.build()}) OR (${rhs.build()})"
- }
-}
-
-private class And(val lhs: Node, val rhs: Node) : Node
-private class Eq(val lhs: Node, val rhs: Node) : Node
-private class Neq(val lhs: Node, val rhs: Node) : Node
-private class In<T>(val value: T, val values: Collection<T>) : Node
-private class Like(val lhs: Node, val rhs: Node) : Node
-private class Literal<T>(val `val`: T) : Node
-private class Or(val lhs: Node, val rhs: Node) : Node
-
-class Query(val root: Node) {
- fun build() = root.build()
+sealed interface Query {
+ fun build(): String
companion object {
const val ARG = "?"
}
}
-infix fun Query.and(other: Query) = Query(And(this.root, other.root))
-infix fun Query.eq(other: Query) = Query(Eq(this.root, other.root))
-infix fun Query.neq(other: Query) = Query(Neq(this.root, other.root))
-infix fun Query.like(other: Query) = Query(Like(this.root, other.root))
-infix fun Query.or(other: Query) = Query(Or(this.root, other.root))
+enum class Operator(val symbol: String) {
+ AND("AND"), OR("OR"), EQUALS("="), NOT_EQUALS("!="), LIKE("LIKE"),
+}
-infix fun <T> Column.eq(other: T) = Query(Literal(this)) eq Query(Literal(other))
-infix fun <T> Column.neq(other: T) = Query(Literal(this)) neq Query(Literal(other))
-infix fun <T> Column.`in`(values: Collection<T>) = Query(In(this, values))
-infix fun <T> Column.like(other: T) = Query(Literal(this)) like Query(Literal(other))
+class LogicalOp(private val lhs: Query, private val op: Operator, private val rhs: Query) : Query {
+ override fun build() = "(${lhs.build()}) ${op.symbol} (${rhs.build()})"
+}
-fun Iterable<Query>.join(
- func: Query.(other: Query) -> Query,
-) = reduce(func)
+class StringOp<T>(private val lhs: Column, private val op: Operator, private val rhs: T) : Query {
+ override fun build() = "$lhs ${op.symbol} $rhs"
+}
-fun Iterable<Query>.joinNullable(
- func: Query.(other: Query) -> Query,
-) = reduceOrNull(func)
+class In<T>(private val value: T, private val values: Collection<T>) : Query {
+ override fun build() = "$value IN (${values.joinToString(", ")})"
+}
+
+infix fun Query.and(other: Query) = LogicalOp(this, Operator.AND, other)
+infix fun Query.or(other: Query) = LogicalOp(this, Operator.OR, other)
+
+infix fun Column.eq(other: String) = StringOp(this, Operator.EQUALS, other)
+infix fun Column.neq(other: String) = StringOp(this, Operator.NOT_EQUALS, other)
+infix fun Column.like(other: String) = StringOp(this, Operator.LIKE, other)
+infix fun <T> Column.`in`(values: Collection<T>) = In(this, values)
+
+inline fun query(block: () -> Query) = block().build()