From b3985e740b3283410dd4d68e77b73c78868a5a1b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 May 2024 20:54:09 -0500 Subject: [PATCH 1/2] EventsDB: migrate tables to match NDatabase --- src/db/DittoTables.ts | 12 ++--- src/db/migrations/019_ndatabase_schema.ts | 25 ++++++++++ src/storages/events-db.ts | 60 +++++++++++------------ 3 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 src/db/migrations/019_ndatabase_schema.ts diff --git a/src/db/DittoTables.ts b/src/db/DittoTables.ts index d71f48a..77e0b80 100644 --- a/src/db/DittoTables.ts +++ b/src/db/DittoTables.ts @@ -1,7 +1,7 @@ export interface DittoTables { - events: EventRow; - events_fts: EventFTSRow; - tags: TagRow; + nostr_events: EventRow; + nostr_tags: TagRow; + nostr_fts5: EventFTSRow; unattached_media: UnattachedMediaRow; author_stats: AuthorStatsRow; event_stats: EventStatsRow; @@ -34,14 +34,14 @@ interface EventRow { } interface EventFTSRow { - id: string; + event_id: string; content: string; } interface TagRow { - tag: string; - value: string; event_id: string; + name: string; + value: string; } interface UnattachedMediaRow { diff --git a/src/db/migrations/019_ndatabase_schema.ts b/src/db/migrations/019_ndatabase_schema.ts new file mode 100644 index 0000000..94378f0 --- /dev/null +++ b/src/db/migrations/019_ndatabase_schema.ts @@ -0,0 +1,25 @@ +import { Kysely, sql } from 'kysely'; + +import { Conf } from '@/config.ts'; + +export async function up(db: Kysely): Promise { + 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): Promise { + 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); + } +} diff --git a/src/storages/events-db.ts b/src/storages/events-db.ts index de1ec4a..19caf5a 100644 --- a/src/storages/events-db.ts +++ b/src/storages/events-db.ts @@ -35,7 +35,7 @@ const tagConditions: Record = { 'role': ({ event, count }) => event.kind === 30361 && count === 0, }; -type EventQuery = SelectQueryBuilder { /** Insert the event into the database. */ async function addEvent() { - await trx.insertInto('events') + await trx.insertInto('nostr_events') .values({ ...event, tags: JSON.stringify(event.tags) }) .execute(); } @@ -91,18 +91,18 @@ class EventsDB implements NStore { if (protocol !== 'sqlite:') return; const searchContent = buildSearchContent(event); if (!searchContent) return; - await trx.insertInto('events_fts') - .values({ id: event.id, content: searchContent.substring(0, 1000) }) + await trx.insertInto('nostr_fts5') + .values({ event_id: event.id, content: searchContent.substring(0, 1000) }) .execute(); } /** Index event tags depending on the conditions defined above. */ async function indexTags() { 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; - await trx.insertInto('tags') + await trx.insertInto('nostr_tags') .values(rows) .execute(); } @@ -150,17 +150,17 @@ class EventsDB implements NStore { /** Build the query for a filter. */ getFilterQuery(db: Kysely, filter: NostrFilter): EventQuery { let query = db - .selectFrom('events') + .selectFrom('nostr_events') .select([ - 'events.id', - 'events.kind', - 'events.pubkey', - 'events.content', - 'events.tags', - 'events.created_at', - 'events.sig', + 'nostr_events.id', + 'nostr_events.kind', + 'nostr_events.pubkey', + 'nostr_events.content', + 'nostr_events.tags', + 'nostr_events.created_at', + '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. */ const isAddrQuery = filter.authors && @@ -169,7 +169,7 @@ class EventsDB implements NStore { // Avoid ORDER BY when querying for replaceable events by author. 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)) { @@ -177,19 +177,19 @@ class EventsDB implements NStore { switch (key as keyof NostrFilter) { case 'ids': - query = query.where('events.id', 'in', filter.ids!); + query = query.where('nostr_events.id', 'in', filter.ids!); break; case 'kinds': - query = query.where('events.kind', 'in', filter.kinds!); + query = query.where('nostr_events.kind', 'in', filter.kinds!); break; case 'authors': - query = query.where('events.pubkey', 'in', filter.authors!); + query = query.where('nostr_events.pubkey', 'in', filter.authors!); break; case 'since': - query = query.where('events.created_at', '>=', filter.since!); + query = query.where('nostr_events.created_at', '>=', filter.since!); break; case 'until': - query = query.where('events.created_at', '<=', filter.until!); + query = query.where('nostr_events.created_at', '<=', filter.until!); break; case '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)) { if (key.startsWith('#') && Array.isArray(value)) { const name = key.replace(/^#/, ''); query = joinedQuery - .where('tags.tag', '=', name) - .where('tags.value', 'in', value); + .where('nostr_tags.name', '=', name) + .where('nostr_tags.value', 'in', value); } } if (filter.search && this.protocol === 'sqlite:') { query = query - .innerJoin('events_fts', 'events_fts.id', 'events.id') - .where('events_fts.content', 'match', JSON.stringify(filter.search)); + .innerJoin('nostr_fts5', 'nostr_fts5.event_id', 'nostr_events.id') + .where('nostr_fts5.content', 'match', JSON.stringify(filter.search)); } return query; @@ -227,9 +227,9 @@ class EventsDB implements NStore { /** Query to get user events, joined by tags. */ usersQuery() { return this.getFilterQuery(this.#db, { kinds: [30361], authors: [Conf.pubkey] }) - .leftJoin('tags', 'tags.event_id', 'events.id') - .where('tags.tag', '=', 'd') - .select('tags.value as d_tag') + .leftJoin('nostr_tags', 'nostr_tags.event_id', 'nostr_events.id') + .where('nostr_tags.name', '=', 'd') + .select('nostr_tags.value as d_tag') .as('users'); } @@ -335,7 +335,7 @@ class EventsDB implements NStore { const query = this.getEventsQuery(filters).clearSelect().select('id'); - return await db.updateTable('events') + return await db.updateTable('nostr_events') .where('id', 'in', () => query) .set({ deleted_at: Math.floor(Date.now() / 1000) }) .execute(); From 69108c0375a4c98f31b1367b6aa4b140b359c441 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 May 2024 20:58:29 -0500 Subject: [PATCH 2/2] UnattachedMedia: point to new EventsDB tables --- src/db/unattached-media.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/unattached-media.ts b/src/db/unattached-media.ts index 708e2f9..8063628 100644 --- a/src/db/unattached-media.ts +++ b/src/db/unattached-media.ts @@ -44,7 +44,7 @@ async function selectUnattachedMediaQuery() { async function getUnattachedMedia(until: Date) { const query = await selectUnattachedMediaQuery(); return query - .leftJoin('tags', 'unattached_media.url', 'tags.value') + .leftJoin('nostr_tags', 'unattached_media.url', 'nostr_tags.value') .where('uploaded_at', '<', until.getTime()) .execute(); }