summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/config.ts2
-rw-r--r--packages/backend/src/core/SearchService.ts54
2 files changed, 55 insertions, 1 deletions
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 3c76c76469..938f44c024 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -254,7 +254,7 @@ export type Config = {
};
};
-export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch';
+export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch' | 'tsvector';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
index 6e46fb798c..efef87cfb7 100644
--- a/packages/backend/src/core/SearchService.ts
+++ b/packages/backend/src/core/SearchService.ts
@@ -248,6 +248,9 @@ export class SearchService {
case 'meilisearch': {
return this.searchNoteByMeiliSearch(q, me, opts, pagination);
}
+ case 'tsvector': {
+ return this.searchNoteByTsvector(q, me, opts, pagination);
+ }
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const typeCheck: never = this.provider;
@@ -257,6 +260,57 @@ export class SearchService {
}
@bindThis
+ private async searchNoteByTsvector(q: string,
+ me: MiUser | null,
+ opts: SearchOpts,
+ pagination: SearchPagination,
+ ): Promise<MiNote[]> {
+ const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
+
+ if (opts.userId) {
+ query.andWhere('note.userId = :userId', { userId: opts.userId });
+ } else if (opts.channelId) {
+ query.andWhere('note.channelId = :channelId', { channelId: opts.channelId });
+ }
+
+ query
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('renote.user', 'renoteUser');
+
+ query.andWhere('note.tsvector @@ websearch_to_tsquery(:q)', { q });
+
+ if (opts.order === 'asc') {
+ query
+ .addSelect('ts_rank_cd(note.tsvector_embedding, websearch_to_tsquery(:q))', 'rank')
+ .orderBy('rank', 'DESC');
+ } else {
+ query
+ .orderBy('note.created_at', 'DESC');
+ }
+
+ if (opts.host) {
+ if (opts.host === '.') {
+ query.andWhere('note.userHost IS NULL');
+ } else {
+ query.andWhere('note.userHost = :host', { host: opts.host });
+ }
+ }
+
+ if (opts.filetype) {
+ query.andWhere('note."attachedFileTypes" && :types', { types: fileTypes[opts.filetype] });
+ }
+
+ this.queryService.generateVisibilityQuery(query, me);
+ if (me) this.queryService.generateMutedUserQuery(query, me);
+ if (me) this.queryService.generateBlockedUserQuery(query, me);
+
+ return await query.limit(pagination.limit).getMany();
+ }
+
+ @bindThis
private async searchNoteByLike(
q: string,
me: MiUser | null,