diff --git a/.config/example.yml b/.config/example.yml index 07c613f62d..d199544589 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -222,6 +222,19 @@ fulltextSearch: # You need to install pgroonga and configure it as a PostgreSQL extension. # In addition to the above, you need to create a pgroonga index on the text column of the note table. # see: https://pgroonga.github.io/tutorial/ + # - sqlTsvector + # Use Postgres tsvectors. + # You need to create a generated column and index on the note table to use this, followed by an ANALYZE on the table. Beware, this will take a while to be created and the database will remain locked during this process. + # This also enables advanced search syntax, see documentation of websearch_to_tsquery: https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES + # Support for multiple languages is currently rather poor and will be improved once post languages become a feature. + # + # Example to set up tsvectors for an English instance: + # ALTER TABLE note ADD COLUMN tsvector_embedding tsvector GENERATED ALWAYS AS ( to_tsvector('english', COALESCE(text, '') || ' ' || COALESCE(cw, '') || ' ' || COALESCE(name, ''))) STORED; + # CREATE INDEX vector_idx ON note USING GIN (tsvector_embedding); + # ANALYZE note; + # + # Note: You can opt to use a different dictionary for better results if your main instance language is not English. + # To get a list, use "SELECT cfgname FROM pg_ts_config;" and replace 'english' with the desired dictionary name. # - meilisearch # Use Meilisearch. # You need to install Meilisearch and configure. diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 3c76c76469..c571c227a1 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' | 'sqlTsvector'; 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..4782a6c7b0 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -240,7 +240,8 @@ export class SearchService { ): Promise { switch (this.provider) { case 'sqlLike': - case 'sqlPgroonga': { + case 'sqlPgroonga': + case 'sqlTsvector': { // ほとんど内容に差がないのでsqlLikeとsqlPgroongaを同じ処理にしている. // 今後の拡張で差が出る用であれば関数を分ける. return this.searchNoteByLike(q, me, opts, pagination); @@ -280,6 +281,8 @@ export class SearchService { if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { query.andWhere('note.text &@~ :q', { q }); + } else if (this.config.fulltextSearch?.provider === 'sqlTsvector') { + query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); } else { query.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }); }