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)
}
fun query(
fun <T : Event> query(
filter: Filter,
db: SQLiteDatabase,
): List<Event> {
): List<T> {
val rowIdSubQuery = prepareRowIDSubQueries(filter)
return if (rowIdSubQuery == null) {
@@ -188,10 +188,10 @@ class EventIndexesModule(
}
}
fun query(
fun <T : Event> query(
filter: Filter,
db: SQLiteDatabase,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) {
val rowIdSubQuery = prepareRowIDSubQueries(filter) ?: return db.runQueryEmitting(makeEverythingQuery(), onEach = onEach)
@@ -205,10 +205,10 @@ class EventIndexesModule(
return makeQueryIn(unions)
}
fun query(
fun <T : Event> query(
filters: List<Filter>,
db: SQLiteDatabase,
): List<Event> {
): List<T> {
val rowIdSubQueries = filters.mapNotNull { prepareRowIDSubQueries(it) }
if (rowIdSubQueries.isEmpty()) return db.runQuery(makeEverythingQuery())
@@ -219,10 +219,10 @@ class EventIndexesModule(
return db.runQuery(makeQueryIn(unions), args)
}
fun query(
fun <T : Event> query(
filters: List<Filter>,
db: SQLiteDatabase,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) {
val rowIdSubQueries = filters.mapNotNull { prepareRowIDSubQueries(it) }
@@ -244,20 +244,20 @@ class EventIndexesModule(
ORDER BY created_at DESC, id
""".trimIndent()
private fun SQLiteDatabase.runQuery(
private fun <T : Event> SQLiteDatabase.runQuery(
sql: String,
args: List<String> = emptyList(),
): List<Event> =
): List<T> =
rawQuery(sql, args.toTypedArray()).use { cursor ->
parseResults(cursor)
}
private fun parseResults(cursor: Cursor): List<Event> {
val events = ArrayList<Event>()
private fun <T : Event> parseResults(cursor: Cursor): List<T> {
val events = ArrayList<T>()
while (cursor.moveToNext()) {
events.add(
EventFactory.create(
EventFactory.create<T>(
cursor.getString(0).intern(),
cursor.getString(1).intern(),
cursor.getLong(2),
@@ -272,21 +272,21 @@ class EventIndexesModule(
return events
}
private fun SQLiteDatabase.runQueryEmitting(
private fun <T : Event> SQLiteDatabase.runQueryEmitting(
sql: String,
args: List<String> = emptyList(),
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) = rawQuery(sql, args.toTypedArray()).use { cursor ->
emitResults(cursor, onEach)
}
private fun emitResults(
private fun <T : Event> emitResults(
cursor: Cursor,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) {
while (cursor.moveToNext()) {
onEach(
EventFactory.create(
EventFactory.create<T>(
cursor.getString(0).intern(),
cursor.getString(1).intern(),
cursor.getLong(2),
@@ -347,18 +347,18 @@ class EventIndexesModule(
fun delete(
filter: Filter,
db: SQLiteDatabase,
): Int? {
val rowIdQuery = prepareRowIDSubQueries(filter) ?: return null
): Int {
val rowIdQuery = prepareRowIDSubQueries(filter) ?: return 0
return db.runDelete(rowIdQuery.sql, rowIdQuery.args)
}
fun delete(
filters: List<Filter>,
db: SQLiteDatabase,
): Int? {
): Int {
val rowIdSubqueries = filters.mapNotNull { prepareRowIDSubQueries(it) }
if (rowIdSubqueries.isEmpty()) return null
if (rowIdSubqueries.isEmpty()) return 0
val unions = rowIdSubqueries.joinToString(" UNION ") { it.sql }
val args = rowIdSubqueries.flatMap { it.args }
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip01Core.store.sqlite
import android.content.Context
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventIndexesModule.IndexingStrategy
class EventStore(
@@ -30,40 +31,36 @@ class EventStore(
dbName: String? = "events.db",
val relayUrl: String? = "wss://quartz.local",
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
) {
) : IEventStore {
val store = SQLiteEventStore(context, dbName, relayUrl, tagIndexStrategy)
fun insert(event: Event) = store.insertEvent(event)
override fun insert(event: Event) = store.insertEvent(event)
interface ITransaction {
fun insert(event: Event): Boolean
}
override fun transaction(body: IEventStore.ITransaction.() -> Unit) = store.transaction(body)
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)
fun query(
override fun <T : Event> query(
filter: Filter,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) = store.query(filter, onEach)
fun query(
override fun <T : Event> query(
filters: List<Filter>,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) = 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.isEphemeral
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.nip40Expiration.isExpired
@@ -117,7 +118,7 @@ class SQLiteEventStore(
inner class Transaction(
val db: SQLiteDatabase,
) : EventStore.ITransaction {
) : IEventStore.ITransaction {
override fun insert(event: Event): Boolean {
if (event.isExpired()) throw SQLiteConstraintException("blocked: Cannot insert an expired event")
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,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) = eventIndexModule.query(filter, readableDatabase, onEach)
fun query(
fun <T : Event> query(
filters: List<Filter>,
onEach: (Event) -> Unit,
onEach: (T) -> Unit,
) = eventIndexModule.query(filters, readableDatabase, onEach)
fun count(filter: Filter): Int = eventIndexModule.count(filter, 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))