Creating interfaces for multiple EventStores

This commit is contained in:
Vitor Pamplona
2025-12-16 08:31:17 -05:00
parent aa9ac205c1
commit eef3f42d3f
4 changed files with 112 additions and 50 deletions
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.store
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
interface IEventStore {
fun insert(event: Event): Boolean
interface ITransaction {
fun insert(event: Event): Boolean
}
fun transaction(body: ITransaction.() -> Unit)
fun <T : Event> query(filter: Filter): List<T>
fun <T : Event> query(filters: List<Filter>): List<T>
fun <T : Event> query(
filter: Filter,
onEach: (T) -> Unit,
)
fun <T : Event> query(
filters: List<Filter>,
onEach: (T) -> Unit,
)
fun count(filter: Filter): Int
fun count(filters: List<Filter>): Int
fun delete(filter: Filter)
fun delete(filters: List<Filter>)
fun deleteExpiredEvents()
fun close()
}
@@ -175,10 +175,10 @@ class EventIndexesModule(
return makeQueryIn(rowIdSubQuery.sql) return makeQueryIn(rowIdSubQuery.sql)
} }
fun query( fun <T : Event> query(
filter: Filter, filter: Filter,
db: SQLiteDatabase, db: SQLiteDatabase,
): List<Event> { ): List<T> {
val rowIdSubQuery = prepareRowIDSubQueries(filter) val rowIdSubQuery = prepareRowIDSubQueries(filter)
return if (rowIdSubQuery == null) { return if (rowIdSubQuery == null) {
@@ -188,10 +188,10 @@ class EventIndexesModule(
} }
} }
fun query( fun <T : Event> query(
filter: Filter, filter: Filter,
db: SQLiteDatabase, db: SQLiteDatabase,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) { ) {
val rowIdSubQuery = prepareRowIDSubQueries(filter) ?: return db.runQueryEmitting(makeEverythingQuery(), onEach = onEach) val rowIdSubQuery = prepareRowIDSubQueries(filter) ?: return db.runQueryEmitting(makeEverythingQuery(), onEach = onEach)
@@ -205,10 +205,10 @@ class EventIndexesModule(
return makeQueryIn(unions) return makeQueryIn(unions)
} }
fun query( fun <T : Event> query(
filters: List<Filter>, filters: List<Filter>,
db: SQLiteDatabase, db: SQLiteDatabase,
): List<Event> { ): List<T> {
val rowIdSubQueries = filters.mapNotNull { prepareRowIDSubQueries(it) } val rowIdSubQueries = filters.mapNotNull { prepareRowIDSubQueries(it) }
if (rowIdSubQueries.isEmpty()) return db.runQuery(makeEverythingQuery()) if (rowIdSubQueries.isEmpty()) return db.runQuery(makeEverythingQuery())
@@ -219,10 +219,10 @@ class EventIndexesModule(
return db.runQuery(makeQueryIn(unions), args) return db.runQuery(makeQueryIn(unions), args)
} }
fun query( fun <T : Event> query(
filters: List<Filter>, filters: List<Filter>,
db: SQLiteDatabase, db: SQLiteDatabase,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) { ) {
val rowIdSubQueries = filters.mapNotNull { prepareRowIDSubQueries(it) } val rowIdSubQueries = filters.mapNotNull { prepareRowIDSubQueries(it) }
@@ -244,20 +244,20 @@ class EventIndexesModule(
ORDER BY created_at DESC, id ORDER BY created_at DESC, id
""".trimIndent() """.trimIndent()
private fun SQLiteDatabase.runQuery( private fun <T : Event> SQLiteDatabase.runQuery(
sql: String, sql: String,
args: List<String> = emptyList(), args: List<String> = emptyList(),
): List<Event> = ): List<T> =
rawQuery(sql, args.toTypedArray()).use { cursor -> rawQuery(sql, args.toTypedArray()).use { cursor ->
parseResults(cursor) parseResults(cursor)
} }
private fun parseResults(cursor: Cursor): List<Event> { private fun <T : Event> parseResults(cursor: Cursor): List<T> {
val events = ArrayList<Event>() val events = ArrayList<T>()
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
events.add( events.add(
EventFactory.create( EventFactory.create<T>(
cursor.getString(0).intern(), cursor.getString(0).intern(),
cursor.getString(1).intern(), cursor.getString(1).intern(),
cursor.getLong(2), cursor.getLong(2),
@@ -272,21 +272,21 @@ class EventIndexesModule(
return events return events
} }
private fun SQLiteDatabase.runQueryEmitting( private fun <T : Event> SQLiteDatabase.runQueryEmitting(
sql: String, sql: String,
args: List<String> = emptyList(), args: List<String> = emptyList(),
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) = rawQuery(sql, args.toTypedArray()).use { cursor -> ) = rawQuery(sql, args.toTypedArray()).use { cursor ->
emitResults(cursor, onEach) emitResults(cursor, onEach)
} }
private fun emitResults( private fun <T : Event> emitResults(
cursor: Cursor, cursor: Cursor,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) { ) {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
onEach( onEach(
EventFactory.create( EventFactory.create<T>(
cursor.getString(0).intern(), cursor.getString(0).intern(),
cursor.getString(1).intern(), cursor.getString(1).intern(),
cursor.getLong(2), cursor.getLong(2),
@@ -347,18 +347,18 @@ class EventIndexesModule(
fun delete( fun delete(
filter: Filter, filter: Filter,
db: SQLiteDatabase, db: SQLiteDatabase,
): Int? { ): Int {
val rowIdQuery = prepareRowIDSubQueries(filter) ?: return null val rowIdQuery = prepareRowIDSubQueries(filter) ?: return 0
return db.runDelete(rowIdQuery.sql, rowIdQuery.args) return db.runDelete(rowIdQuery.sql, rowIdQuery.args)
} }
fun delete( fun delete(
filters: List<Filter>, filters: List<Filter>,
db: SQLiteDatabase, db: SQLiteDatabase,
): Int? { ): Int {
val rowIdSubqueries = filters.mapNotNull { prepareRowIDSubQueries(it) } val rowIdSubqueries = filters.mapNotNull { prepareRowIDSubQueries(it) }
if (rowIdSubqueries.isEmpty()) return null if (rowIdSubqueries.isEmpty()) return 0
val unions = rowIdSubqueries.joinToString(" UNION ") { it.sql } val unions = rowIdSubqueries.joinToString(" UNION ") { it.sql }
val args = rowIdSubqueries.flatMap { it.args } val args = rowIdSubqueries.flatMap { it.args }
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip01Core.store.sqlite
import android.content.Context import android.content.Context
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
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.sqlite.EventIndexesModule.IndexingStrategy import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventIndexesModule.IndexingStrategy
class EventStore( class EventStore(
@@ -30,40 +31,36 @@ class EventStore(
dbName: String? = "events.db", dbName: String? = "events.db",
val relayUrl: String? = "wss://quartz.local", val relayUrl: String? = "wss://quartz.local",
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(), val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
) { ) : IEventStore {
val store = SQLiteEventStore(context, dbName, relayUrl, tagIndexStrategy) val store = SQLiteEventStore(context, dbName, relayUrl, tagIndexStrategy)
fun insert(event: Event) = store.insertEvent(event) override fun insert(event: Event) = store.insertEvent(event)
interface ITransaction { override fun transaction(body: IEventStore.ITransaction.() -> Unit) = store.transaction(body)
fun insert(event: Event): Boolean
}
fun transaction(body: ITransaction.() -> Unit) = store.transaction(body) override fun <T : Event> query(filter: Filter) = store.query<T>(filter)
fun query(filter: Filter) = store.query(filter) override fun <T : Event> query(filters: List<Filter>) = store.query<T>(filters)
fun query(filters: List<Filter>) = store.query(filters) override fun <T : Event> query(
fun query(
filter: Filter, filter: Filter,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) = store.query(filter, onEach) ) = store.query(filter, onEach)
fun query( override fun <T : Event> query(
filters: List<Filter>, filters: List<Filter>,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) = store.query(filters, onEach) ) = store.query(filters, onEach)
fun count(filter: Filter) = store.count(filter) override fun count(filter: Filter) = store.count(filter)
fun count(filters: List<Filter>) = store.count(filters) override fun count(filters: List<Filter>) = store.count(filters)
fun delete(filter: Filter) = store.delete(filter) override fun delete(filter: Filter) = store.delete(filter)
fun delete(filters: List<Filter>) = store.delete(filters) override fun delete(filters: List<Filter>) = store.delete(filters)
fun deleteExpiredEvents() = store.deleteExpiredEvents() override fun deleteExpiredEvents() = store.deleteExpiredEvents()
fun close() = store.close() override fun close() = store.close()
} }
@@ -29,6 +29,7 @@ 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.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.sqlite.EventIndexesModule.IndexingStrategy import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventIndexesModule.IndexingStrategy
import com.vitorpamplona.quartz.nip40Expiration.isExpired import com.vitorpamplona.quartz.nip40Expiration.isExpired
@@ -117,7 +118,7 @@ class SQLiteEventStore(
inner class Transaction( inner class Transaction(
val db: SQLiteDatabase, val db: SQLiteDatabase,
) : EventStore.ITransaction { ) : IEventStore.ITransaction {
override fun insert(event: Event): Boolean { override fun insert(event: Event): Boolean {
if (event.isExpired()) throw SQLiteConstraintException("blocked: Cannot insert an expired event") if (event.isExpired()) throw SQLiteConstraintException("blocked: Cannot insert an expired event")
if (event.kind.isEphemeral()) return false if (event.kind.isEphemeral()) return false
@@ -139,27 +140,31 @@ class SQLiteEventStore(
} }
} }
fun query(filter: Filter): List<Event> = eventIndexModule.query(filter, readableDatabase) fun <T : Event> query(filter: Filter): List<T> = eventIndexModule.query(filter, readableDatabase)
fun query(filters: List<Filter>): List<Event> = eventIndexModule.query(filters, readableDatabase) fun <T : Event> query(filters: List<Filter>): List<T> = eventIndexModule.query(filters, readableDatabase)
fun query( fun <T : Event> query(
filter: Filter, filter: Filter,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) = eventIndexModule.query(filter, readableDatabase, onEach) ) = eventIndexModule.query(filter, readableDatabase, onEach)
fun query( fun <T : Event> query(
filters: List<Filter>, filters: List<Filter>,
onEach: (Event) -> Unit, onEach: (T) -> Unit,
) = eventIndexModule.query(filters, readableDatabase, onEach) ) = eventIndexModule.query(filters, readableDatabase, onEach)
fun count(filter: Filter): Int = eventIndexModule.count(filter, readableDatabase) fun count(filter: Filter): Int = eventIndexModule.count(filter, readableDatabase)
fun count(filters: List<Filter>): Int = eventIndexModule.count(filters, readableDatabase) fun count(filters: List<Filter>): Int = eventIndexModule.count(filters, readableDatabase)
fun delete(filter: Filter): Int? = eventIndexModule.delete(filter, writableDatabase) fun delete(filter: Filter) {
eventIndexModule.delete(filter, writableDatabase)
}
fun delete(filters: List<Filter>): Int? = eventIndexModule.delete(filters, writableDatabase) fun delete(filters: List<Filter>) {
eventIndexModule.delete(filters, writableDatabase)
}
fun delete(id: HexKey): Int = writableDatabase.delete("event_headers", "id = ?", arrayOf(id)) fun delete(id: HexKey): Int = writableDatabase.delete("event_headers", "id = ?", arrayOf(id))