Offers support for external transactions
Offers support for indexing strategies for tags
This commit is contained in:
+8
-9
@@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.utils.EventFactory
|
||||
|
||||
class EventIndexesModule(
|
||||
val fts: FullTextSearchModule,
|
||||
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
|
||||
) {
|
||||
fun create(db: SQLiteDatabase) {
|
||||
db.execSQL(
|
||||
@@ -126,14 +127,14 @@ class EventIndexesModule(
|
||||
}
|
||||
val headerId = stmt.executeInsert()
|
||||
|
||||
val tagsToIndex = event.indexableTags()
|
||||
val tagsToIndex = event.tags.filter(tagIndexStrategy::shouldIndex)
|
||||
|
||||
val reuseStatements = mutableMapOf<Int, SQLiteStatement>()
|
||||
|
||||
for (chunk in tagsToIndex.chunked(300)) {
|
||||
if (chunk.isNotEmpty()) {
|
||||
val stmtTags =
|
||||
reuseStatements.get(chunk.size - 1) ?: run {
|
||||
reuseStatements[chunk.size - 1] ?: run {
|
||||
val sql =
|
||||
buildString {
|
||||
append(sqlInsertTags)
|
||||
@@ -161,13 +162,11 @@ class EventIndexesModule(
|
||||
return headerId
|
||||
}
|
||||
|
||||
fun Event.indexableTags(): List<Tag> {
|
||||
val indexableTagNames = extraIndexableTagNames()
|
||||
return if (indexableTagNames.isNotEmpty()) {
|
||||
tags.filter { it.size >= 2 && (it[0].length == 1 || it[0] in indexableTagNames) }
|
||||
} else {
|
||||
tags.filter { it.size >= 2 && it[0].length == 1 }
|
||||
}
|
||||
/**
|
||||
* By default, we index all tags that have a single letter name and some value
|
||||
*/
|
||||
class IndexingStrategy {
|
||||
fun shouldIndex(tag: Tag) = tag.size >= 2 && tag[0].length == 1
|
||||
}
|
||||
|
||||
fun planQuery(filter: Filter): String {
|
||||
|
||||
+9
-1
@@ -23,16 +23,24 @@ 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.sqlite.EventIndexesModule.IndexingStrategy
|
||||
|
||||
class EventStore(
|
||||
context: Context,
|
||||
dbName: String? = "events.db",
|
||||
val relayUrl: String? = "wss://quartz.local",
|
||||
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
|
||||
) {
|
||||
val store = SQLiteEventStore(context, dbName, relayUrl)
|
||||
val store = SQLiteEventStore(context, dbName, relayUrl, tagIndexStrategy)
|
||||
|
||||
fun insert(event: Event) = store.insertEvent(event)
|
||||
|
||||
interface ITransaction {
|
||||
fun insert(event: Event): Boolean
|
||||
}
|
||||
|
||||
fun transaction(body: ITransaction.() -> Unit) = store.transaction(body)
|
||||
|
||||
fun query(filter: Filter) = store.query(filter)
|
||||
|
||||
fun query(filters: List<Filter>) = store.query(filters)
|
||||
|
||||
+39
-6
@@ -29,19 +29,21 @@ 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.sqlite.EventIndexesModule.IndexingStrategy
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
|
||||
class SQLiteEventStore(
|
||||
val context: Context,
|
||||
val dbName: String? = "events.db",
|
||||
val relayUrl: String? = null,
|
||||
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
|
||||
) : SQLiteOpenHelper(context, dbName, null, DATABASE_VERSION) {
|
||||
companion object {
|
||||
const val DATABASE_VERSION = 1
|
||||
}
|
||||
|
||||
val fullTextSearchModule = FullTextSearchModule()
|
||||
val eventIndexModule = EventIndexesModule(fullTextSearchModule)
|
||||
val eventIndexModule = EventIndexesModule(fullTextSearchModule, tagIndexStrategy)
|
||||
|
||||
val replaceableModule = ReplaceableModule()
|
||||
val addressableModule = AddressableModule()
|
||||
@@ -87,21 +89,52 @@ class SQLiteEventStore(
|
||||
context.deleteDatabase(dbName)
|
||||
}
|
||||
|
||||
private fun innerInsertEvent(
|
||||
event: Event,
|
||||
db: SQLiteDatabase,
|
||||
) {
|
||||
val headerId = eventIndexModule.insert(event, db)
|
||||
deletionModule.insert(event, headerId, db)
|
||||
expirationModule.insert(event, headerId, db)
|
||||
fullTextSearchModule.insert(event, headerId, db)
|
||||
rightToVanishModule.insert(event, relayUrl, headerId, db)
|
||||
}
|
||||
|
||||
fun insertEvent(event: Event): Boolean {
|
||||
if (event.isExpired()) throw SQLiteConstraintException("blocked: Cannot insert an expired event")
|
||||
if (event.kind.isEphemeral()) return false
|
||||
|
||||
val db = writableDatabase
|
||||
db.transaction {
|
||||
val headerId = eventIndexModule.insert(event, db)
|
||||
deletionModule.insert(event, headerId, db)
|
||||
expirationModule.insert(event, headerId, db)
|
||||
fullTextSearchModule.insert(event, headerId, db)
|
||||
rightToVanishModule.insert(event, relayUrl, headerId, db)
|
||||
innerInsertEvent(event, this)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
inner class Transaction(
|
||||
val db: SQLiteDatabase,
|
||||
) : EventStore.ITransaction {
|
||||
override fun insert(event: Event): Boolean {
|
||||
if (event.isExpired()) throw SQLiteConstraintException("blocked: Cannot insert an expired event")
|
||||
if (event.kind.isEphemeral()) return false
|
||||
|
||||
innerInsertEvent(event, db)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun transaction(body: Transaction.() -> Unit) {
|
||||
val db = writableDatabase
|
||||
db.beginTransaction()
|
||||
try {
|
||||
with(Transaction(db)) {
|
||||
body()
|
||||
}
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
fun query(filter: Filter): List<Event> = eventIndexModule.query(filter, readableDatabase)
|
||||
|
||||
fun query(filters: List<Filter>): List<Event> = eventIndexModule.query(filters, readableDatabase)
|
||||
|
||||
Reference in New Issue
Block a user