Merge branch 'ndb' into 'main'
EventsDB: migrate tables to match NDatabase See merge request soapbox-pub/ditto!257
This commit is contained in:
commit
998d0851df
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue