Adds performance and flexibility by exposing Raw methods
This commit is contained in:
+109
-90
@@ -23,8 +23,6 @@ package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.store.sqlite.sql.where
|
||||
@@ -36,47 +34,66 @@ class QueryBuilder(
|
||||
val fts: FullTextSearchModule,
|
||||
val hasher: (db: SQLiteDatabase) -> TagNameValueHasher,
|
||||
) {
|
||||
fun planQuery(
|
||||
filter: Filter,
|
||||
hasher: TagNameValueHasher,
|
||||
db: SQLiteDatabase,
|
||||
): String {
|
||||
val rowIdSubQuery = prepareRowIDSubQueries(filter, hasher)
|
||||
|
||||
return if (rowIdSubQuery == null) {
|
||||
val query = makeEverythingQuery()
|
||||
db.explainQuery(query)
|
||||
} else {
|
||||
val query = makeQueryIn(rowIdSubQuery.sql)
|
||||
db.explainQuery(query, rowIdSubQuery.args.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
// ------------
|
||||
// Main methods
|
||||
// ------------
|
||||
fun <T : Event> query(
|
||||
filter: Filter,
|
||||
db: SQLiteDatabase,
|
||||
): List<T> {
|
||||
val rowIdSubQuery = prepareRowIDSubQueries(filter, hasher(db))
|
||||
|
||||
return if (rowIdSubQuery == null) {
|
||||
db.runQuery(makeEverythingQuery())
|
||||
} else {
|
||||
db.runQuery(makeQueryIn(rowIdSubQuery.sql), rowIdSubQuery.args)
|
||||
}
|
||||
}
|
||||
): List<T> = db.runQuery(toSql(filter, hasher(db)))
|
||||
|
||||
fun <T : Event> query(
|
||||
filter: Filter,
|
||||
db: SQLiteDatabase,
|
||||
onEach: (T) -> Unit,
|
||||
) {
|
||||
val rowIdSubQuery = prepareRowIDSubQueries(filter, hasher(db))
|
||||
) = db.runQuery(toSql(filter, hasher(db)), onEach)
|
||||
|
||||
return if (rowIdSubQuery == null) {
|
||||
db.runQuery(makeEverythingQuery(), onEach = onEach)
|
||||
} else {
|
||||
db.runQuery(makeQueryIn(rowIdSubQuery.sql), rowIdSubQuery.args, onEach)
|
||||
}
|
||||
fun <T : Event> query(
|
||||
filters: List<Filter>,
|
||||
db: SQLiteDatabase,
|
||||
): List<T> = db.runQuery(toSql(filters, hasher(db)))
|
||||
|
||||
fun <T : Event> query(
|
||||
filters: List<Filter>,
|
||||
db: SQLiteDatabase,
|
||||
onEach: (T) -> Unit,
|
||||
) = db.runQuery(toSql(filters, hasher(db)), onEach)
|
||||
|
||||
// ---------------------------
|
||||
// Raw methods for performance
|
||||
// ---------------------------
|
||||
fun rawQuery(
|
||||
filter: Filter,
|
||||
db: SQLiteDatabase,
|
||||
): List<RawEvent> = db.runRawQuery(toSql(filter, hasher(db)))
|
||||
|
||||
fun rawQuery(
|
||||
filter: Filter,
|
||||
db: SQLiteDatabase,
|
||||
onEach: (RawEvent) -> Unit,
|
||||
) = db.runRawQuery(toSql(filter, hasher(db)), onEach)
|
||||
|
||||
fun rawQuery(
|
||||
filters: List<Filter>,
|
||||
db: SQLiteDatabase,
|
||||
): List<RawEvent> = db.runRawQuery(toSql(filters, hasher(db)))
|
||||
|
||||
fun rawQuery(
|
||||
filters: List<Filter>,
|
||||
db: SQLiteDatabase,
|
||||
onEach: (RawEvent) -> Unit,
|
||||
) = db.runRawQuery(toSql(filters, hasher(db)), onEach)
|
||||
|
||||
// -----------
|
||||
// Debug Tools
|
||||
// -----------
|
||||
fun planQuery(
|
||||
filter: Filter,
|
||||
hasher: TagNameValueHasher,
|
||||
db: SQLiteDatabase,
|
||||
): String {
|
||||
val query = toSql(filter, hasher)
|
||||
return db.explainQuery(query.sql, query.args.toTypedArray())
|
||||
}
|
||||
|
||||
fun planQuery(
|
||||
@@ -84,36 +101,45 @@ class QueryBuilder(
|
||||
hasher: TagNameValueHasher,
|
||||
db: SQLiteDatabase,
|
||||
): String {
|
||||
val rowIdSubQuery = unionSubqueriesIfNeeded(filters, hasher)
|
||||
val query = toSql(filters, hasher)
|
||||
return db.explainQuery(query.sql, query.args.toTypedArray())
|
||||
}
|
||||
|
||||
return if (rowIdSubQuery == null) {
|
||||
val query = makeEverythingQuery()
|
||||
db.explainQuery(query)
|
||||
fun toSql(
|
||||
filter: Filter,
|
||||
hasher: TagNameValueHasher,
|
||||
): QuerySpec {
|
||||
val rowIdSubqueries = prepareRowIDSubQueries(filter, hasher)
|
||||
|
||||
return if (rowIdSubqueries == null) {
|
||||
QuerySpec(
|
||||
makeEverythingQuery(),
|
||||
emptyList(),
|
||||
)
|
||||
} else {
|
||||
val query = makeQueryIn(rowIdSubQuery.sql)
|
||||
db.explainQuery(query, rowIdSubQuery.args.toTypedArray())
|
||||
QuerySpec(
|
||||
makeQueryIn(rowIdSubqueries.sql),
|
||||
rowIdSubqueries.args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> query(
|
||||
fun toSql(
|
||||
filters: List<Filter>,
|
||||
db: SQLiteDatabase,
|
||||
): List<T> {
|
||||
val rowIdSubqueries = unionSubqueriesIfNeeded(filters, hasher(db)) ?: return db.runQuery(makeEverythingQuery())
|
||||
return db.runQuery(makeQueryIn(rowIdSubqueries.sql), rowIdSubqueries.args)
|
||||
}
|
||||
hasher: TagNameValueHasher,
|
||||
): QuerySpec {
|
||||
val rowIdSubqueries = unionSubqueriesIfNeeded(filters, hasher)
|
||||
|
||||
fun <T : Event> query(
|
||||
filters: List<Filter>,
|
||||
db: SQLiteDatabase,
|
||||
onEach: (T) -> Unit,
|
||||
) {
|
||||
val rowIdSubqueries = unionSubqueriesIfNeeded(filters, hasher(db))
|
||||
|
||||
if (rowIdSubqueries == null) {
|
||||
db.runQuery(makeEverythingQuery(), onEach = onEach)
|
||||
return if (rowIdSubqueries == null) {
|
||||
QuerySpec(
|
||||
makeEverythingQuery(),
|
||||
emptyList(),
|
||||
)
|
||||
} else {
|
||||
db.runQuery(makeQueryIn(rowIdSubqueries.sql), rowIdSubqueries.args, onEach)
|
||||
QuerySpec(
|
||||
makeQueryIn(rowIdSubqueries.sql),
|
||||
rowIdSubqueries.args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,11 +155,8 @@ class QueryBuilder(
|
||||
ORDER BY created_at DESC, id
|
||||
""".trimIndent()
|
||||
|
||||
private fun <T : Event> SQLiteDatabase.runQuery(
|
||||
sql: String,
|
||||
args: List<String> = emptyList(),
|
||||
): List<T> =
|
||||
rawQuery(sql, args.toTypedArray()).use { cursor ->
|
||||
private fun <T : Event> SQLiteDatabase.runQuery(query: QuerySpec): List<T> =
|
||||
rawQuery(query.sql, query.args.toTypedArray()).use { cursor ->
|
||||
ArrayList<T>(cursor.count).apply {
|
||||
while (cursor.moveToNext()) {
|
||||
add(cursor.toEvent())
|
||||
@@ -141,16 +164,33 @@ class QueryBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.runRawQuery(query: QuerySpec): List<RawEvent> =
|
||||
rawQuery(query.sql, query.args.toTypedArray()).use { cursor ->
|
||||
ArrayList<RawEvent>(cursor.count).apply {
|
||||
while (cursor.moveToNext()) {
|
||||
add(cursor.toRawEvent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T : Event> SQLiteDatabase.runQuery(
|
||||
sql: String,
|
||||
args: List<String> = emptyList(),
|
||||
query: QuerySpec,
|
||||
onEach: (T) -> Unit,
|
||||
) = rawQuery(sql, args.toTypedArray()).use { cursor ->
|
||||
) = rawQuery(query.sql, query.args.toTypedArray()).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
onEach(cursor.toEvent())
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun SQLiteDatabase.runRawQuery(
|
||||
query: QuerySpec,
|
||||
onEach: (RawEvent) -> Unit,
|
||||
) = rawQuery(query.sql, query.args.toTypedArray()).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
onEach(cursor.toRawEvent())
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Event> Cursor.toEvent() =
|
||||
EventFactory.create<T>(
|
||||
getString(0).intern(),
|
||||
@@ -162,27 +202,6 @@ class QueryBuilder(
|
||||
getString(6),
|
||||
)
|
||||
|
||||
class RawEvent(
|
||||
val id: HexKey,
|
||||
val pubKey: HexKey,
|
||||
val createdAt: Long,
|
||||
val kind: Kind,
|
||||
val jsonTags: String,
|
||||
val content: String,
|
||||
val sig: HexKey,
|
||||
) {
|
||||
fun <T : Event> toEvent() =
|
||||
EventFactory.create<T>(
|
||||
id.intern(),
|
||||
pubKey.intern(),
|
||||
createdAt,
|
||||
kind,
|
||||
OptimizedJsonMapper.fromJsonToTagArray(jsonTags),
|
||||
content,
|
||||
sig,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Cursor.toRawEvent() =
|
||||
RawEvent(
|
||||
getString(0),
|
||||
@@ -271,7 +290,7 @@ class QueryBuilder(
|
||||
fun unionSubqueriesIfNeeded(
|
||||
filters: List<Filter>,
|
||||
hasher: TagNameValueHasher,
|
||||
): RowIdSubQuery? {
|
||||
): QuerySpec? {
|
||||
val inner =
|
||||
filters.mapNotNull { filter ->
|
||||
prepareRowIDSubQueries(filter, hasher)
|
||||
@@ -282,7 +301,7 @@ class QueryBuilder(
|
||||
return if (inner.size == 1) {
|
||||
inner.first()
|
||||
} else {
|
||||
RowIdSubQuery(
|
||||
QuerySpec(
|
||||
sql = inner.joinToString("\n UNION\n ") { "SELECT row_id FROM (${it.sql})" },
|
||||
args = inner.flatMap { it.args },
|
||||
)
|
||||
@@ -306,7 +325,7 @@ class QueryBuilder(
|
||||
fun prepareRowIDSubQueries(
|
||||
filter: Filter,
|
||||
hasher: TagNameValueHasher,
|
||||
): RowIdSubQuery? {
|
||||
): QuerySpec? {
|
||||
if (!filter.isFilledFilter()) return null
|
||||
|
||||
val mustJoinSearch = (filter.search != null)
|
||||
@@ -457,10 +476,10 @@ class QueryBuilder(
|
||||
clause.conditions
|
||||
}
|
||||
|
||||
return RowIdSubQuery("$projection WHERE $whereClause", clause.args)
|
||||
return QuerySpec("$projection WHERE $whereClause", clause.args)
|
||||
}
|
||||
|
||||
data class RowIdSubQuery(
|
||||
data class QuerySpec(
|
||||
val sql: String,
|
||||
val args: List<String>,
|
||||
)
|
||||
|
||||
+38
@@ -27,10 +27,13 @@ import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.database.sqlite.transaction
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isEphemeral
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
import com.vitorpamplona.quartz.utils.EventFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -187,6 +190,20 @@ class SQLiteEventStore(
|
||||
onEach: (T) -> Unit,
|
||||
) = queryBuilder.query(filters, readableDatabase, onEach)
|
||||
|
||||
fun rawQuery(filter: Filter): List<RawEvent> = queryBuilder.rawQuery(filter, readableDatabase)
|
||||
|
||||
fun rawQuery(filters: List<Filter>): List<RawEvent> = queryBuilder.rawQuery(filters, readableDatabase)
|
||||
|
||||
fun rawQuery(
|
||||
filter: Filter,
|
||||
onEach: (RawEvent) -> Unit,
|
||||
) = queryBuilder.rawQuery(filter, readableDatabase, onEach)
|
||||
|
||||
fun rawQuery(
|
||||
filters: List<Filter>,
|
||||
onEach: (RawEvent) -> Unit,
|
||||
) = queryBuilder.rawQuery(filters, readableDatabase, onEach)
|
||||
|
||||
fun count(filter: Filter): Int = queryBuilder.count(filter, readableDatabase)
|
||||
|
||||
fun count(filters: List<Filter>): Int = queryBuilder.count(filters, readableDatabase)
|
||||
@@ -203,3 +220,24 @@ class SQLiteEventStore(
|
||||
|
||||
fun deleteExpiredEvents() = expirationModule.deleteExpiredEvents(writableDatabase)
|
||||
}
|
||||
|
||||
class RawEvent(
|
||||
val id: HexKey,
|
||||
val pubKey: HexKey,
|
||||
val createdAt: Long,
|
||||
val kind: Kind,
|
||||
val jsonTags: String,
|
||||
val content: String,
|
||||
val sig: HexKey,
|
||||
) {
|
||||
fun <T : Event> toEvent() =
|
||||
EventFactory.create<T>(
|
||||
id.intern(),
|
||||
pubKey.intern(),
|
||||
createdAt,
|
||||
kind,
|
||||
OptimizedJsonMapper.fromJsonToTagArray(jsonTags),
|
||||
content,
|
||||
sig,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user