From eef3f42d3f1ef21ed6101516b8a58e3286992242 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 16 Dec 2025 08:31:17 -0500 Subject: [PATCH] Creating interfaces for multiple EventStores --- .../quartz/nip01Core/store/IEventStore.kt | 60 +++++++++++++++++++ .../store/sqlite/EventIndexesModule.kt | 44 +++++++------- .../nip01Core/store/sqlite/EventStore.kt | 35 +++++------ .../store/sqlite/SQLiteEventStore.kt | 23 ++++--- 4 files changed, 112 insertions(+), 50 deletions(-) create mode 100644 quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt new file mode 100644 index 000000000..0f347b91b --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt @@ -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 query(filter: Filter): List + + fun query(filters: List): List + + fun query( + filter: Filter, + onEach: (T) -> Unit, + ) + + fun query( + filters: List, + onEach: (T) -> Unit, + ) + + fun count(filter: Filter): Int + + fun count(filters: List): Int + + fun delete(filter: Filter) + + fun delete(filters: List) + + fun deleteExpiredEvents() + + fun close() +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt index 961acbbcd..beb407154 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt @@ -175,10 +175,10 @@ class EventIndexesModule( return makeQueryIn(rowIdSubQuery.sql) } - fun query( + fun query( filter: Filter, db: SQLiteDatabase, - ): List { + ): List { val rowIdSubQuery = prepareRowIDSubQueries(filter) return if (rowIdSubQuery == null) { @@ -188,10 +188,10 @@ class EventIndexesModule( } } - fun query( + fun 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 query( filters: List, db: SQLiteDatabase, - ): List { + ): List { 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 query( filters: List, 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 SQLiteDatabase.runQuery( sql: String, args: List = emptyList(), - ): List = + ): List = rawQuery(sql, args.toTypedArray()).use { cursor -> parseResults(cursor) } - private fun parseResults(cursor: Cursor): List { - val events = ArrayList() + private fun parseResults(cursor: Cursor): List { + val events = ArrayList() while (cursor.moveToNext()) { events.add( - EventFactory.create( + EventFactory.create( 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 SQLiteDatabase.runQueryEmitting( sql: String, args: List = emptyList(), - onEach: (Event) -> Unit, + onEach: (T) -> Unit, ) = rawQuery(sql, args.toTypedArray()).use { cursor -> emitResults(cursor, onEach) } - private fun emitResults( + private fun emitResults( cursor: Cursor, - onEach: (Event) -> Unit, + onEach: (T) -> Unit, ) { while (cursor.moveToNext()) { onEach( - EventFactory.create( + EventFactory.create( 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, 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 } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt index 383d4423a..1a9a3e57b 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt @@ -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 query(filter: Filter) = store.query(filter) - fun query(filter: Filter) = store.query(filter) + override fun query(filters: List) = store.query(filters) - fun query(filters: List) = store.query(filters) - - fun query( + override fun query( filter: Filter, - onEach: (Event) -> Unit, + onEach: (T) -> Unit, ) = store.query(filter, onEach) - fun query( + override fun query( filters: List, - 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) = store.count(filters) + override fun count(filters: List) = store.count(filters) - fun delete(filter: Filter) = store.delete(filter) + override fun delete(filter: Filter) = store.delete(filter) - fun delete(filters: List) = store.delete(filters) + override fun delete(filters: List) = store.delete(filters) - fun deleteExpiredEvents() = store.deleteExpiredEvents() + override fun deleteExpiredEvents() = store.deleteExpiredEvents() - fun close() = store.close() + override fun close() = store.close() } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt index b046bb7e9..79680a7bf 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt @@ -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 = eventIndexModule.query(filter, readableDatabase) + fun query(filter: Filter): List = eventIndexModule.query(filter, readableDatabase) - fun query(filters: List): List = eventIndexModule.query(filters, readableDatabase) + fun query(filters: List): List = eventIndexModule.query(filters, readableDatabase) - fun query( + fun query( filter: Filter, - onEach: (Event) -> Unit, + onEach: (T) -> Unit, ) = eventIndexModule.query(filter, readableDatabase, onEach) - fun query( + fun query( filters: List, - onEach: (Event) -> Unit, + onEach: (T) -> Unit, ) = eventIndexModule.query(filters, readableDatabase, onEach) fun count(filter: Filter): Int = eventIndexModule.count(filter, readableDatabase) fun count(filters: List): 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): Int? = eventIndexModule.delete(filters, writableDatabase) + fun delete(filters: List) { + eventIndexModule.delete(filters, writableDatabase) + } fun delete(id: HexKey): Int = writableDatabase.delete("event_headers", "id = ?", arrayOf(id))