duplicates kinds and pubkey on the tags table to take complex queries under 100 microseconds
This commit is contained in:
+3
-5
@@ -51,13 +51,11 @@ class DeletionRequestModule(
|
||||
SELECT RAISE(ABORT, 'blocked: a deletion event exists')
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM event_tags
|
||||
INNER JOIN event_headers
|
||||
ON event_headers.row_id = event_tags.event_header_row_id
|
||||
WHERE
|
||||
event_tags.tag_hash IN (NEW.etag_hash, NEW.atag_hash) AND
|
||||
event_tags.created_at >= NEW.created_at AND
|
||||
event_headers.kind = 5 AND
|
||||
event_headers.pubkey_owner_hash = NEW.pubkey_owner_hash
|
||||
event_tags.kind = 5 AND
|
||||
event_tags.pubkey_hash = NEW.pubkey_owner_hash AND
|
||||
event_tags.created_at >= NEW.created_at
|
||||
);
|
||||
END;
|
||||
""".trimIndent(),
|
||||
|
||||
+10
-4
@@ -57,6 +57,8 @@ class EventIndexesModule(
|
||||
event_header_row_id INTEGER NOT NULL,
|
||||
tag_hash INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
kind INTEGER NOT NULL,
|
||||
pubkey_hash INTEGER NOT NULL,
|
||||
FOREIGN KEY (event_header_row_id) REFERENCES event_headers(row_id) ON DELETE CASCADE
|
||||
)
|
||||
""".trimIndent(),
|
||||
@@ -74,8 +76,10 @@ class EventIndexesModule(
|
||||
// makes deletions on the event_header fast
|
||||
db.execSQL("CREATE INDEX fk_event_tags_header_id ON event_tags (event_header_row_id)")
|
||||
|
||||
// This is a very slow index to build (half the insert time goes here) but it is extremely effective.
|
||||
db.execSQL("CREATE INDEX query_by_tags_hash ON event_tags (tag_hash, created_at DESC)")
|
||||
// This is a very slow index to build (80% of the insert time goes here) but it is extremely effective.
|
||||
db.execSQL("CREATE INDEX query_by_tags_hash ON event_tags (tag_hash, created_at DESC)")
|
||||
db.execSQL("CREATE INDEX query_by_tags_hash_kind ON event_tags (tag_hash, kind, created_at DESC)")
|
||||
db.execSQL("CREATE INDEX query_by_tags_hash_kind_pubkey ON event_tags (tag_hash, kind, pubkey_hash, created_at DESC)")
|
||||
|
||||
// Prevent updates to maintain immutability
|
||||
db.execSQL(
|
||||
@@ -117,9 +121,9 @@ class EventIndexesModule(
|
||||
val sqlInsertTags =
|
||||
"""
|
||||
INSERT OR ROLLBACK INTO event_tags
|
||||
(event_header_row_id, tag_hash, created_at)
|
||||
(event_header_row_id, tag_hash, created_at, kind, pubkey_hash)
|
||||
VALUES
|
||||
(?,?,?)
|
||||
(?,?,?,?,?)
|
||||
""".trimIndent()
|
||||
|
||||
fun insert(
|
||||
@@ -178,6 +182,8 @@ class EventIndexesModule(
|
||||
stmtTags.bindLong(1, headerId)
|
||||
stmtTags.bindLong(2, it)
|
||||
stmtTags.bindLong(3, event.createdAt)
|
||||
stmtTags.bindLong(4, kindLong)
|
||||
stmtTags.bindLong(5, pubkeyHash)
|
||||
stmtTags.executeInsert()
|
||||
}
|
||||
|
||||
|
||||
+27
-12
@@ -24,6 +24,7 @@ import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isAddressable
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.store.sqlite.sql.where
|
||||
import com.vitorpamplona.quartz.utils.EventFactory
|
||||
@@ -338,10 +339,7 @@ class QueryBuilder(
|
||||
|
||||
val needHeaders =
|
||||
with(filter) {
|
||||
(ids != null) ||
|
||||
(authors != null && authors.isNotEmpty()) ||
|
||||
(kinds != null && kinds.isNotEmpty()) ||
|
||||
(tags != null && tags.containsKey("d"))
|
||||
(ids != null) || (tags != null && tags.containsKey("d"))
|
||||
}
|
||||
|
||||
val hasHeaders =
|
||||
@@ -440,20 +438,37 @@ class QueryBuilder(
|
||||
|
||||
// range search is bad but most of the time these are up the top with few elements.
|
||||
if (reverseLookup) {
|
||||
filter.kinds?.let { equalsOrIn("event_tags.kind", it) }
|
||||
filter.authors?.let { equalsOrIn("event_tags.pubkey_hash", it.map { hasher.hash(it) }) }
|
||||
|
||||
filter.since?.let { greaterThanOrEquals("event_tags.created_at", it) }
|
||||
filter.until?.let { lessThanOrEquals("event_tags.created_at", it) }
|
||||
|
||||
// there are indexes for these, starting with tags.
|
||||
filter.tags?.forEach { (tagName, tagValues) ->
|
||||
if (tagName == "d") {
|
||||
equalsOrIn("event_headers.d_tag", tagValues)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filter.kinds?.let { equalsOrIn("event_headers.kind", it) }
|
||||
filter.authors?.let { equalsOrIn("event_headers.pubkey", it) }
|
||||
|
||||
// there are indexes for these, starting with tags.
|
||||
filter.tags?.forEach { (tagName, tagValues) ->
|
||||
if (tagName == "d") {
|
||||
equalsOrIn("event_headers.d_tag", tagValues)
|
||||
}
|
||||
}
|
||||
|
||||
filter.since?.let { greaterThanOrEquals("event_headers.created_at", it) }
|
||||
filter.until?.let { lessThanOrEquals("event_headers.created_at", it) }
|
||||
}
|
||||
|
||||
filter.kinds?.let { equalsOrIn("event_headers.kind", it) }
|
||||
filter.authors?.let { equalsOrIn("event_headers.pubkey", it) }
|
||||
|
||||
// there are indexes for these, starting with tags.
|
||||
filter.tags?.forEach { (tagName, tagValues) ->
|
||||
if (tagName == "d") {
|
||||
equalsOrIn("event_headers.d_tag", tagValues)
|
||||
// no need to add the replaceable because query_by_kind_pubkey_created already covers it
|
||||
val isAllAddressable = filter.kinds?.all { it.isAddressable() } ?: false
|
||||
if (isAllAddressable) {
|
||||
// matches unique index kind >= 30000 AND kind < 40000
|
||||
raw("(event_headers.kind >= 30000 AND event_headers.kind < 40000)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user