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.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
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.OptimizedJsonMapper
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.store.sqlite.sql.where
|
import com.vitorpamplona.quartz.nip01Core.store.sqlite.sql.where
|
||||||
@@ -36,47 +34,66 @@ class QueryBuilder(
|
|||||||
val fts: FullTextSearchModule,
|
val fts: FullTextSearchModule,
|
||||||
val hasher: (db: SQLiteDatabase) -> TagNameValueHasher,
|
val hasher: (db: SQLiteDatabase) -> TagNameValueHasher,
|
||||||
) {
|
) {
|
||||||
fun planQuery(
|
// ------------
|
||||||
filter: Filter,
|
// Main methods
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Event> query(
|
fun <T : Event> query(
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
db: SQLiteDatabase,
|
db: SQLiteDatabase,
|
||||||
): List<T> {
|
): List<T> = db.runQuery(toSql(filter, hasher(db)))
|
||||||
val rowIdSubQuery = prepareRowIDSubQueries(filter, hasher(db))
|
|
||||||
|
|
||||||
return if (rowIdSubQuery == null) {
|
|
||||||
db.runQuery(makeEverythingQuery())
|
|
||||||
} else {
|
|
||||||
db.runQuery(makeQueryIn(rowIdSubQuery.sql), rowIdSubQuery.args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Event> query(
|
fun <T : Event> query(
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
db: SQLiteDatabase,
|
db: SQLiteDatabase,
|
||||||
onEach: (T) -> Unit,
|
onEach: (T) -> Unit,
|
||||||
) {
|
) = db.runQuery(toSql(filter, hasher(db)), onEach)
|
||||||
val rowIdSubQuery = prepareRowIDSubQueries(filter, hasher(db))
|
|
||||||
|
|
||||||
return if (rowIdSubQuery == null) {
|
fun <T : Event> query(
|
||||||
db.runQuery(makeEverythingQuery(), onEach = onEach)
|
filters: List<Filter>,
|
||||||
} else {
|
db: SQLiteDatabase,
|
||||||
db.runQuery(makeQueryIn(rowIdSubQuery.sql), rowIdSubQuery.args, onEach)
|
): 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(
|
fun planQuery(
|
||||||
@@ -84,36 +101,45 @@ class QueryBuilder(
|
|||||||
hasher: TagNameValueHasher,
|
hasher: TagNameValueHasher,
|
||||||
db: SQLiteDatabase,
|
db: SQLiteDatabase,
|
||||||
): String {
|
): String {
|
||||||
val rowIdSubQuery = unionSubqueriesIfNeeded(filters, hasher)
|
val query = toSql(filters, hasher)
|
||||||
|
return db.explainQuery(query.sql, query.args.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
return if (rowIdSubQuery == null) {
|
fun toSql(
|
||||||
val query = makeEverythingQuery()
|
filter: Filter,
|
||||||
db.explainQuery(query)
|
hasher: TagNameValueHasher,
|
||||||
|
): QuerySpec {
|
||||||
|
val rowIdSubqueries = prepareRowIDSubQueries(filter, hasher)
|
||||||
|
|
||||||
|
return if (rowIdSubqueries == null) {
|
||||||
|
QuerySpec(
|
||||||
|
makeEverythingQuery(),
|
||||||
|
emptyList(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val query = makeQueryIn(rowIdSubQuery.sql)
|
QuerySpec(
|
||||||
db.explainQuery(query, rowIdSubQuery.args.toTypedArray())
|
makeQueryIn(rowIdSubqueries.sql),
|
||||||
|
rowIdSubqueries.args,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Event> query(
|
fun toSql(
|
||||||
filters: List<Filter>,
|
filters: List<Filter>,
|
||||||
db: SQLiteDatabase,
|
hasher: TagNameValueHasher,
|
||||||
): List<T> {
|
): QuerySpec {
|
||||||
val rowIdSubqueries = unionSubqueriesIfNeeded(filters, hasher(db)) ?: return db.runQuery(makeEverythingQuery())
|
val rowIdSubqueries = unionSubqueriesIfNeeded(filters, hasher)
|
||||||
return db.runQuery(makeQueryIn(rowIdSubqueries.sql), rowIdSubqueries.args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Event> query(
|
return if (rowIdSubqueries == null) {
|
||||||
filters: List<Filter>,
|
QuerySpec(
|
||||||
db: SQLiteDatabase,
|
makeEverythingQuery(),
|
||||||
onEach: (T) -> Unit,
|
emptyList(),
|
||||||
) {
|
)
|
||||||
val rowIdSubqueries = unionSubqueriesIfNeeded(filters, hasher(db))
|
|
||||||
|
|
||||||
if (rowIdSubqueries == null) {
|
|
||||||
db.runQuery(makeEverythingQuery(), onEach = onEach)
|
|
||||||
} else {
|
} 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
|
ORDER BY created_at DESC, id
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
private fun <T : Event> SQLiteDatabase.runQuery(
|
private fun <T : Event> SQLiteDatabase.runQuery(query: QuerySpec): List<T> =
|
||||||
sql: String,
|
rawQuery(query.sql, query.args.toTypedArray()).use { cursor ->
|
||||||
args: List<String> = emptyList(),
|
|
||||||
): List<T> =
|
|
||||||
rawQuery(sql, args.toTypedArray()).use { cursor ->
|
|
||||||
ArrayList<T>(cursor.count).apply {
|
ArrayList<T>(cursor.count).apply {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
add(cursor.toEvent())
|
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(
|
private inline fun <T : Event> SQLiteDatabase.runQuery(
|
||||||
sql: String,
|
query: QuerySpec,
|
||||||
args: List<String> = emptyList(),
|
|
||||||
onEach: (T) -> Unit,
|
onEach: (T) -> Unit,
|
||||||
) = rawQuery(sql, args.toTypedArray()).use { cursor ->
|
) = rawQuery(query.sql, query.args.toTypedArray()).use { cursor ->
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
onEach(cursor.toEvent())
|
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() =
|
private fun <T : Event> Cursor.toEvent() =
|
||||||
EventFactory.create<T>(
|
EventFactory.create<T>(
|
||||||
getString(0).intern(),
|
getString(0).intern(),
|
||||||
@@ -162,27 +202,6 @@ class QueryBuilder(
|
|||||||
getString(6),
|
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() =
|
private fun Cursor.toRawEvent() =
|
||||||
RawEvent(
|
RawEvent(
|
||||||
getString(0),
|
getString(0),
|
||||||
@@ -271,7 +290,7 @@ class QueryBuilder(
|
|||||||
fun unionSubqueriesIfNeeded(
|
fun unionSubqueriesIfNeeded(
|
||||||
filters: List<Filter>,
|
filters: List<Filter>,
|
||||||
hasher: TagNameValueHasher,
|
hasher: TagNameValueHasher,
|
||||||
): RowIdSubQuery? {
|
): QuerySpec? {
|
||||||
val inner =
|
val inner =
|
||||||
filters.mapNotNull { filter ->
|
filters.mapNotNull { filter ->
|
||||||
prepareRowIDSubQueries(filter, hasher)
|
prepareRowIDSubQueries(filter, hasher)
|
||||||
@@ -282,7 +301,7 @@ class QueryBuilder(
|
|||||||
return if (inner.size == 1) {
|
return if (inner.size == 1) {
|
||||||
inner.first()
|
inner.first()
|
||||||
} else {
|
} else {
|
||||||
RowIdSubQuery(
|
QuerySpec(
|
||||||
sql = inner.joinToString("\n UNION\n ") { "SELECT row_id FROM (${it.sql})" },
|
sql = inner.joinToString("\n UNION\n ") { "SELECT row_id FROM (${it.sql})" },
|
||||||
args = inner.flatMap { it.args },
|
args = inner.flatMap { it.args },
|
||||||
)
|
)
|
||||||
@@ -306,7 +325,7 @@ class QueryBuilder(
|
|||||||
fun prepareRowIDSubQueries(
|
fun prepareRowIDSubQueries(
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
hasher: TagNameValueHasher,
|
hasher: TagNameValueHasher,
|
||||||
): RowIdSubQuery? {
|
): QuerySpec? {
|
||||||
if (!filter.isFilledFilter()) return null
|
if (!filter.isFilledFilter()) return null
|
||||||
|
|
||||||
val mustJoinSearch = (filter.search != null)
|
val mustJoinSearch = (filter.search != null)
|
||||||
@@ -457,10 +476,10 @@ class QueryBuilder(
|
|||||||
clause.conditions
|
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 sql: String,
|
||||||
val args: List<String>,
|
val args: List<String>,
|
||||||
)
|
)
|
||||||
|
|||||||
+38
@@ -27,10 +27,13 @@ import android.database.sqlite.SQLiteOpenHelper
|
|||||||
import androidx.core.database.sqlite.transaction
|
import androidx.core.database.sqlite.transaction
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
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.core.isEphemeral
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||||
|
import com.vitorpamplona.quartz.utils.EventFactory
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -187,6 +190,20 @@ class SQLiteEventStore(
|
|||||||
onEach: (T) -> Unit,
|
onEach: (T) -> Unit,
|
||||||
) = queryBuilder.query(filters, readableDatabase, onEach)
|
) = 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(filter: Filter): Int = queryBuilder.count(filter, readableDatabase)
|
||||||
|
|
||||||
fun count(filters: List<Filter>): Int = queryBuilder.count(filters, readableDatabase)
|
fun count(filters: List<Filter>): Int = queryBuilder.count(filters, readableDatabase)
|
||||||
@@ -203,3 +220,24 @@ class SQLiteEventStore(
|
|||||||
|
|
||||||
fun deleteExpiredEvents() = expirationModule.deleteExpiredEvents(writableDatabase)
|
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