fix: search compatibility

This commit is contained in:
Namekuji 2023-08-30 18:38:54 -04:00
parent ed26870807
commit 4f05dbbc9b
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
5 changed files with 49 additions and 77 deletions

View file

@ -6,6 +6,8 @@ import { Note } from "@/models/entities/note.js";
import * as url from "url"; import * as url from "url";
import { ILocalUser } from "@/models/entities/user.js"; import { ILocalUser } from "@/models/entities/user.js";
import { Followings, Users } from "@/models/index.js"; import { Followings, Users } from "@/models/index.js";
import { userByIdCache } from "@/services/user-cache.js";
import { ScyllaNote } from "./scylla.js";
const logger = dbLogger.createSubLogger("meilisearch", "gray", false); const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
@ -347,20 +349,14 @@ export default hasConfig
sort: sortRules, sort: sortRules,
}); });
}, },
ingestNote: async (ingestNotes: Note | Note[]) => { ingestNote: async (ingestNotes: Note[] | ScyllaNote[]) => {
if (ingestNotes instanceof Note) {
ingestNotes = [ingestNotes];
}
const indexingBatch: MeilisearchNote[] = []; const indexingBatch: MeilisearchNote[] = [];
for (const note of ingestNotes) { for (const note of ingestNotes) {
if (note.user === undefined) { if (!note.user) {
note.user = await Users.findOne({ note.user = await userByIdCache.fetch(note.userId, () =>
where: { Users.findOneByOrFail({ id: note.userId }),
id: note.userId, );
},
});
} }
let attachmentType = ""; let attachmentType = "";
@ -408,40 +404,15 @@ export default hasConfig
return { return {
health: health.status, health: health.status,
size: stats.databaseSize, size: stats.databaseSize,
indexed_count: stats.indexes["posts"].numberOfDocuments, indexed_count: stats.indexes.posts.numberOfDocuments,
}; };
}, },
deleteNotes: async (note: Note | Note[] | string | string[]) => { deleteNotes: async (notes: Note[] | ScyllaNote[] | string[]) => {
if (note instanceof Note) { const deletionBatch = notes
note = [note]; .map((n) => (typeof n === "string" ? n : n.id))
} .filter((id) => !!id);
if (typeof note === "string") {
note = [note];
}
const deletionBatch = note await posts.deleteDocuments(deletionBatch).then(() => {
.map((n) => {
if (n instanceof Note) {
return n.id;
}
if (n.length > 0) return n;
logger.error(
`Failed to delete note from Meilisearch, invalid post ID: ${JSON.stringify(
n,
)}`,
);
throw new Error(
`Invalid note ID passed to meilisearch deleteNote: ${JSON.stringify(
n,
)}`,
);
})
.filter((el) => el !== null);
await posts.deleteDocuments(deletionBatch as string[]).then(() => {
logger.info( logger.info(
`submitted ${deletionBatch.length} large batch for deletion`, `submitted ${deletionBatch.length} large batch for deletion`,
); );

View file

@ -414,12 +414,11 @@ export const NoteRepository = db.getRepository(Note).extend({
const targets = [...notes.map((n) => n.id), ...renoteIds]; const targets = [...notes.map((n) => n.id), ...renoteIds];
let myReactions: NoteReaction[] = []; let myReactions: NoteReaction[] = [];
if (scyllaClient) { if (scyllaClient) {
const result = await scyllaClient.execute( myReactions = await scyllaClient
prepared.reaction.select.byNoteAndUser, .execute(prepared.reaction.select.byNoteAndUser, [targets, [meId]], {
[targets, [meId]], prepare: true,
{ prepare: true }, })
); .then((result) => result.rows.map(parseScyllaReaction));
myReactions = result.rows.map(parseScyllaReaction);
} else { } else {
myReactions = await NoteReactions.findBy({ myReactions = await NoteReactions.findBy({
userId: meId, userId: meId,

View file

@ -11,6 +11,7 @@ import { generateVisibilityQuery } from "../../common/generate-visibility-query.
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
import { sqlLikeEscape } from "@/misc/sql-like-escape.js"; import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -68,7 +69,7 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
if (es == null && sonic == null && meilisearch == null) { if (!es && !sonic && !meilisearch && !scyllaClient) {
const query = makePaginationQuery( const query = makePaginationQuery(
Notes.createQueryBuilder("note"), Notes.createQueryBuilder("note"),
ps.sinceId, ps.sinceId,
@ -86,17 +87,8 @@ export default define(meta, paramDef, async (ps, me) => {
query query
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` }) .andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` })
.andWhere("note.visibility = 'public'") .andWhere("note.visibility = 'public'")
.innerJoinAndSelect("note.user", "user")
.leftJoinAndSelect("user.avatar", "avatar")
.leftJoinAndSelect("user.banner", "banner")
.leftJoinAndSelect("note.reply", "reply") .leftJoinAndSelect("note.reply", "reply")
.leftJoinAndSelect("note.renote", "renote") .leftJoinAndSelect("note.renote", "renote");
.leftJoinAndSelect("reply.user", "replyUser")
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
.leftJoinAndSelect("renote.user", "renoteUser")
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
generateVisibilityQuery(query, me); generateVisibilityQuery(query, me);
if (me) generateMutedUserQuery(query, me); if (me) generateMutedUserQuery(query, me);
@ -110,7 +102,7 @@ export default define(meta, paramDef, async (ps, me) => {
const chunkSize = 100; const chunkSize = 100;
// Use sonic to fetch and step through all search results that could match the requirements // Use sonic to fetch and step through all search results that could match the requirements
const ids = []; const ids: string[] = [];
while (true) { while (true) {
const results = await sonic.search.query( const results = await sonic.search.query(
sonic.collection, sonic.collection,
@ -151,18 +143,25 @@ export default define(meta, paramDef, async (ps, me) => {
} }
// Sort all the results by note id DESC (newest first) // Sort all the results by note id DESC (newest first)
ids.sort((a, b) => b - a); ids.sort().reverse();
// Fetch the notes from the database until we have enough to satisfy the limit // Fetch the notes from the database until we have enough to satisfy the limit
start = 0; start = 0;
const found = []; const found = [];
while (found.length < ps.limit && start < ids.length) { while (found.length < ps.limit && start < ids.length) {
const chunk = ids.slice(start, start + chunkSize); const chunk = ids.slice(start, start + chunkSize);
const notes: Note[] = await Notes.find({ let notes: Note[] = [];
where: { if (scyllaClient) {
id: In(chunk), notes = await scyllaClient
}, .execute(prepared.note.select.byIds, [chunk], { prepare: true })
}); .then((result) => result.rows.map(parseScyllaNote));
} else {
notes = await Notes.find({
where: {
id: In(chunk),
},
});
}
// The notes are checked for visibility and muted/blocked users when packed // The notes are checked for visibility and muted/blocked users when packed
found.push(...(await Notes.packMany(notes, me))); found.push(...(await Notes.packMany(notes, me)));
@ -239,13 +238,14 @@ export default define(meta, paramDef, async (ps, me) => {
while (found.length < ps.limit && start < noteIDs.length) { while (found.length < ps.limit && start < noteIDs.length) {
const chunk = noteIDs.slice(start, start + chunkSize); const chunk = noteIDs.slice(start, start + chunkSize);
let query: FindManyOptions = { let notes: Note[] = [];
where: { if (scyllaClient) {
id: In(chunk), notes = await scyllaClient
}, .execute(prepared.note.select.byIds, [chunk], { prepare: true })
}; .then((result) => result.rows.map(parseScyllaNote));
} else {
const notes: Note[] = await Notes.find(query); notes = await Notes.findBy({ id: In(chunk) });
}
// Re-order the note result according to the noteIDs array (cannot be undefined, we map this earlier) // Re-order the note result according to the noteIDs array (cannot be undefined, we map this earlier)
// @ts-ignore // @ts-ignore
@ -262,7 +262,7 @@ export default define(meta, paramDef, async (ps, me) => {
} }
return found; return found;
} else { } else if (es && !scyllaClient) {
const userQuery = const userQuery =
ps.userId != null ps.userId != null
? [ ? [
@ -329,7 +329,7 @@ export default define(meta, paramDef, async (ps, me) => {
const hits = result.body.hits.hits.map((hit: any) => hit._id); const hits = result.body.hits.hits.map((hit: any) => hit._id);
if (hits.length === 0) return []; if (hits.length === 0) return await Notes.packMany([]);
// Fetch found notes // Fetch found notes
const notes = await Notes.find({ const notes = await Notes.find({
@ -343,4 +343,6 @@ export default define(meta, paramDef, async (ps, me) => {
return await Notes.packMany(notes, me); return await Notes.packMany(notes, me);
} }
return await Notes.packMany([]);
}); });

View file

@ -1007,7 +1007,7 @@ export async function index(note: Note, reindexing: boolean): Promise<void> {
} }
if (meilisearch && !reindexing) { if (meilisearch && !reindexing) {
await meilisearch.ingestNote(note); await meilisearch.ingestNote([note]);
} }
} }

View file

@ -326,7 +326,7 @@ export default async function (
} }
if (meilisearch) { if (meilisearch) {
await meilisearch.deleteNotes(note.id); await meilisearch.deleteNotes([note.id]);
} }
} }