Merge branch 'ndb' into 'main'

EventsDB: migrate tables to match NDatabase

See merge request soapbox-pub/ditto!257
This commit is contained in:
Alex Gleason 2024-05-15 02:02:52 +00:00
commit 998d0851df
4 changed files with 62 additions and 37 deletions

View File

@ -1,7 +1,7 @@
export interface DittoTables { export interface DittoTables {
events: EventRow; nostr_events: EventRow;
events_fts: EventFTSRow; nostr_tags: TagRow;
tags: TagRow; nostr_fts5: EventFTSRow;
unattached_media: UnattachedMediaRow; unattached_media: UnattachedMediaRow;
author_stats: AuthorStatsRow; author_stats: AuthorStatsRow;
event_stats: EventStatsRow; event_stats: EventStatsRow;
@ -34,14 +34,14 @@ interface EventRow {
} }
interface EventFTSRow { interface EventFTSRow {
id: string; event_id: string;
content: string; content: string;
} }
interface TagRow { interface TagRow {
tag: string;
value: string;
event_id: string; event_id: string;
name: string;
value: string;
} }
interface UnattachedMediaRow { interface UnattachedMediaRow {

View File

@ -0,0 +1,25 @@
import { Kysely, sql } from 'kysely';
import { Conf } from '@/config.ts';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema.alterTable('events').renameTo('nostr_events').execute();
await db.schema.alterTable('tags').renameTo('nostr_tags').execute();
await db.schema.alterTable('nostr_tags').renameColumn('tag', 'name').execute();
if (Conf.databaseUrl.protocol === 'sqlite:') {
await db.schema.dropTable('events_fts').execute();
await sql`CREATE VIRTUAL TABLE nostr_fts5 USING fts5(event_id, content)`.execute(db);
}
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.alterTable('nostr_events').renameTo('events').execute();
await db.schema.alterTable('nostr_tags').renameTo('tags').execute();
await db.schema.alterTable('tags').renameColumn('name', 'tag').execute();
if (Conf.databaseUrl.protocol === 'sqlite:') {
await db.schema.dropTable('nostr_fts5').execute();
await sql`CREATE VIRTUAL TABLE events_fts USING fts5(id, content)`.execute(db);
}
}

View File

@ -44,7 +44,7 @@ async function selectUnattachedMediaQuery() {
async function getUnattachedMedia(until: Date) { async function getUnattachedMedia(until: Date) {
const query = await selectUnattachedMediaQuery(); const query = await selectUnattachedMediaQuery();
return query return query
.leftJoin('tags', 'unattached_media.url', 'tags.value') .leftJoin('nostr_tags', 'unattached_media.url', 'nostr_tags.value')
.where('uploaded_at', '<', until.getTime()) .where('uploaded_at', '<', until.getTime())
.execute(); .execute();
} }

View File

@ -35,7 +35,7 @@ const tagConditions: Record<string, TagCondition> = {
'role': ({ event, count }) => event.kind === 30361 && count === 0, 'role': ({ event, count }) => event.kind === 30361 && count === 0,
}; };
type EventQuery = SelectQueryBuilder<DittoTables, 'events', { type EventQuery = SelectQueryBuilder<DittoTables, 'nostr_events', {
id: string; id: string;
tags: string; tags: string;
kind: number; kind: number;
@ -80,7 +80,7 @@ class EventsDB implements NStore {
return await this.#db.transaction().execute(async (trx) => { return await this.#db.transaction().execute(async (trx) => {
/** Insert the event into the database. */ /** Insert the event into the database. */
async function addEvent() { async function addEvent() {
await trx.insertInto('events') await trx.insertInto('nostr_events')
.values({ ...event, tags: JSON.stringify(event.tags) }) .values({ ...event, tags: JSON.stringify(event.tags) })
.execute(); .execute();
} }
@ -91,18 +91,18 @@ class EventsDB implements NStore {
if (protocol !== 'sqlite:') return; if (protocol !== 'sqlite:') return;
const searchContent = buildSearchContent(event); const searchContent = buildSearchContent(event);
if (!searchContent) return; if (!searchContent) return;
await trx.insertInto('events_fts') await trx.insertInto('nostr_fts5')
.values({ id: event.id, content: searchContent.substring(0, 1000) }) .values({ event_id: event.id, content: searchContent.substring(0, 1000) })
.execute(); .execute();
} }
/** Index event tags depending on the conditions defined above. */ /** Index event tags depending on the conditions defined above. */
async function indexTags() { async function indexTags() {
const tags = filterIndexableTags(event); const tags = filterIndexableTags(event);
const rows = tags.map(([tag, value]) => ({ event_id: event.id, tag, value })); const rows = tags.map(([name, value]) => ({ event_id: event.id, name, value }));
if (!tags.length) return; if (!tags.length) return;
await trx.insertInto('tags') await trx.insertInto('nostr_tags')
.values(rows) .values(rows)
.execute(); .execute();
} }
@ -150,17 +150,17 @@ class EventsDB implements NStore {
/** Build the query for a filter. */ /** Build the query for a filter. */
getFilterQuery(db: Kysely<DittoTables>, filter: NostrFilter): EventQuery { getFilterQuery(db: Kysely<DittoTables>, filter: NostrFilter): EventQuery {
let query = db let query = db
.selectFrom('events') .selectFrom('nostr_events')
.select([ .select([
'events.id', 'nostr_events.id',
'events.kind', 'nostr_events.kind',
'events.pubkey', 'nostr_events.pubkey',
'events.content', 'nostr_events.content',
'events.tags', 'nostr_events.tags',
'events.created_at', 'nostr_events.created_at',
'events.sig', 'nostr_events.sig',
]) ])
.where('events.deleted_at', 'is', null); .where('nostr_events.deleted_at', 'is', null);
/** Whether we are querying for replaceable events by author. */ /** Whether we are querying for replaceable events by author. */
const isAddrQuery = filter.authors && const isAddrQuery = filter.authors &&
@ -169,7 +169,7 @@ class EventsDB implements NStore {
// Avoid ORDER BY when querying for replaceable events by author. // Avoid ORDER BY when querying for replaceable events by author.
if (!isAddrQuery) { if (!isAddrQuery) {
query = query.orderBy('events.created_at', 'desc'); query = query.orderBy('nostr_events.created_at', 'desc');
} }
for (const [key, value] of Object.entries(filter)) { for (const [key, value] of Object.entries(filter)) {
@ -177,19 +177,19 @@ class EventsDB implements NStore {
switch (key as keyof NostrFilter) { switch (key as keyof NostrFilter) {
case 'ids': case 'ids':
query = query.where('events.id', 'in', filter.ids!); query = query.where('nostr_events.id', 'in', filter.ids!);
break; break;
case 'kinds': case 'kinds':
query = query.where('events.kind', 'in', filter.kinds!); query = query.where('nostr_events.kind', 'in', filter.kinds!);
break; break;
case 'authors': case 'authors':
query = query.where('events.pubkey', 'in', filter.authors!); query = query.where('nostr_events.pubkey', 'in', filter.authors!);
break; break;
case 'since': case 'since':
query = query.where('events.created_at', '>=', filter.since!); query = query.where('nostr_events.created_at', '>=', filter.since!);
break; break;
case 'until': case 'until':
query = query.where('events.created_at', '<=', filter.until!); query = query.where('nostr_events.created_at', '<=', filter.until!);
break; break;
case 'limit': case 'limit':
query = query.limit(filter.limit!); query = query.limit(filter.limit!);
@ -197,21 +197,21 @@ class EventsDB implements NStore {
} }
} }
const joinedQuery = query.leftJoin('tags', 'tags.event_id', 'events.id'); const joinedQuery = query.leftJoin('nostr_tags', 'nostr_tags.event_id', 'nostr_events.id');
for (const [key, value] of Object.entries(filter)) { for (const [key, value] of Object.entries(filter)) {
if (key.startsWith('#') && Array.isArray(value)) { if (key.startsWith('#') && Array.isArray(value)) {
const name = key.replace(/^#/, ''); const name = key.replace(/^#/, '');
query = joinedQuery query = joinedQuery
.where('tags.tag', '=', name) .where('nostr_tags.name', '=', name)
.where('tags.value', 'in', value); .where('nostr_tags.value', 'in', value);
} }
} }
if (filter.search && this.protocol === 'sqlite:') { if (filter.search && this.protocol === 'sqlite:') {
query = query query = query
.innerJoin('events_fts', 'events_fts.id', 'events.id') .innerJoin('nostr_fts5', 'nostr_fts5.event_id', 'nostr_events.id')
.where('events_fts.content', 'match', JSON.stringify(filter.search)); .where('nostr_fts5.content', 'match', JSON.stringify(filter.search));
} }
return query; return query;
@ -227,9 +227,9 @@ class EventsDB implements NStore {
/** Query to get user events, joined by tags. */ /** Query to get user events, joined by tags. */
usersQuery() { usersQuery() {
return this.getFilterQuery(this.#db, { kinds: [30361], authors: [Conf.pubkey] }) return this.getFilterQuery(this.#db, { kinds: [30361], authors: [Conf.pubkey] })
.leftJoin('tags', 'tags.event_id', 'events.id') .leftJoin('nostr_tags', 'nostr_tags.event_id', 'nostr_events.id')
.where('tags.tag', '=', 'd') .where('nostr_tags.name', '=', 'd')
.select('tags.value as d_tag') .select('nostr_tags.value as d_tag')
.as('users'); .as('users');
} }
@ -335,7 +335,7 @@ class EventsDB implements NStore {
const query = this.getEventsQuery(filters).clearSelect().select('id'); const query = this.getEventsQuery(filters).clearSelect().select('id');
return await db.updateTable('events') return await db.updateTable('nostr_events')
.where('id', 'in', () => query) .where('id', 'in', () => query)
.set({ deleted_at: Math.floor(Date.now() / 1000) }) .set({ deleted_at: Math.floor(Date.now() / 1000) })
.execute(); .execute();