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(
|
class EventIndexesModule(
|
||||||
val fts: FullTextSearchModule,
|
val fts: FullTextSearchModule,
|
||||||
|
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
|
||||||
) {
|
) {
|
||||||
fun create(db: SQLiteDatabase) {
|
fun create(db: SQLiteDatabase) {
|
||||||
db.execSQL(
|
db.execSQL(
|
||||||
@@ -126,14 +127,14 @@ class EventIndexesModule(
|
|||||||
}
|
}
|
||||||
val headerId = stmt.executeInsert()
|
val headerId = stmt.executeInsert()
|
||||||
|
|
||||||
val tagsToIndex = event.indexableTags()
|
val tagsToIndex = event.tags.filter(tagIndexStrategy::shouldIndex)
|
||||||
|
|
||||||
val reuseStatements = mutableMapOf<Int, SQLiteStatement>()
|
val reuseStatements = mutableMapOf<Int, SQLiteStatement>()
|
||||||
|
|
||||||
for (chunk in tagsToIndex.chunked(300)) {
|
for (chunk in tagsToIndex.chunked(300)) {
|
||||||
if (chunk.isNotEmpty()) {
|
if (chunk.isNotEmpty()) {
|
||||||
val stmtTags =
|
val stmtTags =
|
||||||
reuseStatements.get(chunk.size - 1) ?: run {
|
reuseStatements[chunk.size - 1] ?: run {
|
||||||
val sql =
|
val sql =
|
||||||
buildString {
|
buildString {
|
||||||
append(sqlInsertTags)
|
append(sqlInsertTags)
|
||||||
@@ -161,13 +162,11 @@ class EventIndexesModule(
|
|||||||
return headerId
|
return headerId
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Event.indexableTags(): List<Tag> {
|
/**
|
||||||
val indexableTagNames = extraIndexableTagNames()
|
* By default, we index all tags that have a single letter name and some value
|
||||||
return if (indexableTagNames.isNotEmpty()) {
|
*/
|
||||||
tags.filter { it.size >= 2 && (it[0].length == 1 || it[0] in indexableTagNames) }
|
class IndexingStrategy {
|
||||||
} else {
|
fun shouldIndex(tag: Tag) = tag.size >= 2 && tag[0].length == 1
|
||||||
tags.filter { it.size >= 2 && it[0].length == 1 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun planQuery(filter: Filter): String {
|
fun planQuery(filter: Filter): String {
|
||||||
|
|||||||
+9
-1
@@ -23,16 +23,24 @@ 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.sqlite.EventIndexesModule.IndexingStrategy
|
||||||
|
|
||||||
class EventStore(
|
class EventStore(
|
||||||
context: Context,
|
context: Context,
|
||||||
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 store = SQLiteEventStore(context, dbName, relayUrl)
|
val store = SQLiteEventStore(context, dbName, relayUrl, tagIndexStrategy)
|
||||||
|
|
||||||
fun insert(event: Event) = store.insertEvent(event)
|
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(filter: Filter) = store.query(filter)
|
||||||
|
|
||||||
fun query(filters: List<Filter>) = store.query(filters)
|
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.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.sqlite.EventIndexesModule.IndexingStrategy
|
||||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||||
|
|
||||||
class SQLiteEventStore(
|
class SQLiteEventStore(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val dbName: String? = "events.db",
|
val dbName: String? = "events.db",
|
||||||
val relayUrl: String? = null,
|
val relayUrl: String? = null,
|
||||||
|
val tagIndexStrategy: IndexingStrategy = IndexingStrategy(),
|
||||||
) : SQLiteOpenHelper(context, dbName, null, DATABASE_VERSION) {
|
) : SQLiteOpenHelper(context, dbName, null, DATABASE_VERSION) {
|
||||||
companion object {
|
companion object {
|
||||||
const val DATABASE_VERSION = 1
|
const val DATABASE_VERSION = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullTextSearchModule = FullTextSearchModule()
|
val fullTextSearchModule = FullTextSearchModule()
|
||||||
val eventIndexModule = EventIndexesModule(fullTextSearchModule)
|
val eventIndexModule = EventIndexesModule(fullTextSearchModule, tagIndexStrategy)
|
||||||
|
|
||||||
val replaceableModule = ReplaceableModule()
|
val replaceableModule = ReplaceableModule()
|
||||||
val addressableModule = AddressableModule()
|
val addressableModule = AddressableModule()
|
||||||
@@ -87,21 +89,52 @@ class SQLiteEventStore(
|
|||||||
context.deleteDatabase(dbName)
|
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 {
|
fun insertEvent(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
|
||||||
|
|
||||||
val db = writableDatabase
|
val db = writableDatabase
|
||||||
db.transaction {
|
db.transaction {
|
||||||
val headerId = eventIndexModule.insert(event, db)
|
innerInsertEvent(event, this)
|
||||||
deletionModule.insert(event, headerId, db)
|
|
||||||
expirationModule.insert(event, headerId, db)
|
|
||||||
fullTextSearchModule.insert(event, headerId, db)
|
|
||||||
rightToVanishModule.insert(event, relayUrl, headerId, db)
|
|
||||||
}
|
}
|
||||||
return true
|
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(filter: Filter): List<Event> = eventIndexModule.query(filter, readableDatabase)
|
||||||
|
|
||||||
fun query(filters: List<Filter>): List<Event> = eventIndexModule.query(filters, readableDatabase)
|
fun query(filters: List<Filter>): List<Event> = eventIndexModule.query(filters, readableDatabase)
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ open class Event(
|
|||||||
*/
|
*/
|
||||||
open fun isContentEncoded() = false
|
open fun isContentEncoded() = false
|
||||||
|
|
||||||
open fun extraIndexableTagNames() = emptySet<String>()
|
|
||||||
|
|
||||||
open fun countMemory(): Int =
|
open fun countMemory(): Int =
|
||||||
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||||
12 + // createdAt + kind
|
12 + // createdAt + kind
|
||||||
|
|||||||
Reference in New Issue
Block a user