diff --git a/packages/backend/migration/1652210810723-PGroonga.js b/packages/backend/migration/1652210810723-PGroonga.js new file mode 100644 index 000000000..bd3fee34e --- /dev/null +++ b/packages/backend/migration/1652210810723-PGroonga.js @@ -0,0 +1,11 @@ +export class PGroonga1652210810723 { + name = 'PGroonga1652210810723' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_f27f5d88941e57442be75ba9c8" ON "note" USING "pgroonga" ("text")`); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_f27f5d88941e57442be75ba9c8"`); + } +} diff --git a/packages/backend/migration/1652213168020-PGroongaUserName.js b/packages/backend/migration/1652213168020-PGroongaUserName.js new file mode 100644 index 000000000..9e1e75ece --- /dev/null +++ b/packages/backend/migration/1652213168020-PGroongaUserName.js @@ -0,0 +1,11 @@ +export class PGroongaUserName1652213168020 { + name = 'PGroongaUserName1652213168020' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_065d4d8f3b5adb4a08841eae3c" ON "user" USING "pgroonga" ("name" pgroonga_varchar_full_text_search_ops_v2)`); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_065d4d8f3b5adb4a08841eae3c"`); + } +} diff --git a/packages/backend/migration/1652213556290-PGroongaUserDescription.js b/packages/backend/migration/1652213556290-PGroongaUserDescription.js new file mode 100644 index 000000000..7216438ab --- /dev/null +++ b/packages/backend/migration/1652213556290-PGroongaUserDescription.js @@ -0,0 +1,11 @@ +export class PGroongaUserDescription1652213556290 { + name = 'PGroongaUserDescription1652213556290' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_fcb770976ff8240af5799e3ffc" ON "user_profile" USING "pgroonga" ("description" pgroonga_varchar_full_text_search_ops_v2) `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_fcb770976ff8240af5799e3ffc"`); + } +} diff --git a/packages/backend/migration/1727542814489-Pgroonga.js b/packages/backend/migration/1727542814489-Pgroonga.js new file mode 100644 index 000000000..b8e3e4a46 --- /dev/null +++ b/packages/backend/migration/1727542814489-Pgroonga.js @@ -0,0 +1,16 @@ +export class Pgroonga1727542814489 { + name = 'Pgroonga1727542814489' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_f27f5d88941e57442be75ba9c8" ON "note" USING "pgroonga" ("text")`); + await queryRunner.query(`CREATE INDEX "IDX_065d4d8f3b5adb4a08841eae3c" ON "user" USING "pgroonga" ("name" pgroonga_varchar_full_text_search_ops_v2)`); + await queryRunner.query(`CREATE INDEX "IDX_fcb770976ff8240af5799e3ffc" ON "user_profile" USING "pgroonga" ("description" pgroonga_varchar_full_text_search_ops_v2) `); + + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_f27f5d88941e57442be75ba9c8"`); + await queryRunner.query(`DROP INDEX "public"."IDX_065d4d8f3b5adb4a08841eae3c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_fcb770976ff8240af5799e3ffc"`); + } +} \ No newline at end of file diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index edfc47037..42a32220d 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -215,7 +215,7 @@ export class SearchService { } query - .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) + .andWhere('note.text &@~ :q', { q: sqlLikeEscape(q) }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 9a95c6faa..e21048dee 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -50,6 +50,7 @@ export class MiNote { public threadId: string | null; // TODO: varcharにしたい + @Index() // USING pgroonga @Column('text', { nullable: true, }) diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 805a1e75a..76337dab4 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -49,6 +49,7 @@ export class MiUser { }) public usernameLower: string; + @Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2 @Column('varchar', { length: 128, nullable: true, comment: 'The name of the User.', diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 554455529..5f1a7a7d1 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -36,6 +36,7 @@ export class MiUserProfile { }) public birthday: string | null; + @Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2 @Column('varchar', { length: 2048, nullable: true, comment: 'The description (bio) of the User.', diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 3fe19806e..a26c49c94 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -31,6 +31,11 @@ export const meta = { code: 'UNAVAILABLE', id: '0b44998d-77aa-4427-80d0-d2c9b8523011', }, + noSuchNote: { + message: 'Query is empty.', + code: 'QUERY_IS_EMPTY', + id: 'd0410b51-f409-4667-8118-cfe999e453c3', + }, }, } as const; @@ -62,6 +67,7 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + if (ps.query.trim().length === 0) throw new ApiError(meta.errors.noSuchNote); const policies = await this.roleService.getUserPolicies(me ? me.id : null); if (!policies.canSearchNotes) { throw new ApiError(meta.errors.unavailable); diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 0b0136066..6c686a8eb 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -63,12 +63,12 @@ export default class extends Endpoint { // eslint- const nameQuery = this.usersRepository.createQueryBuilder('user') .where(new Brackets(qb => { - qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); + qb.where('user.name &@~ :query', { query: ps.query }); if (isUsername) { qb.orWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' }); - } else if (this.userEntityService.validateLocalUsername(ps.query)) { // Also search username if it qualifies as username - qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' }); + } else if (userEntityService.validateLocalUsername(ps.query)) { // Also search username if it qualifies as username + qb.orWhere('user.usernameLower LIKE :username', { username: ps.query.toLowerCase() + '%' }); } })) .andWhere(new Brackets(qb => { @@ -93,7 +93,7 @@ export default class extends Endpoint { // eslint- if (users.length < ps.limit) { const profQuery = this.userProfilesRepository.createQueryBuilder('prof') .select('prof.userId') - .where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); + .where('prof.description &@~ :query', { query: ps.query }); if (ps.origin === 'local') { profQuery.andWhere('prof.userHost IS NULL'); diff --git a/pgroonga.diff b/pgroonga.diff new file mode 100644 index 000000000..c960ba5d9 --- /dev/null +++ b/pgroonga.diff @@ -0,0 +1,51 @@ +diff --git a/packages/backend/migration/1652210810723-PGroonga.js b/packages/backend/migration/1652210810723-PGroonga.js +new file mode 100644 +index 000000000..bd3fee34e +--- /dev/null ++++ b/packages/backend/migration/1652210810723-PGroonga.js +@@ -0,0 +1,11 @@ ++export class PGroonga1652210810723 { ++ name = 'PGroonga1652210810723' ++ ++ async up(queryRunner) { ++ await queryRunner.query(`CREATE INDEX "IDX_f27f5d88941e57442be75ba9c8" ON "note" USING "pgroonga" ("text")`); ++ } ++ ++ async down(queryRunner) { ++ await queryRunner.query(`DROP INDEX "public"."IDX_f27f5d88941e57442be75ba9c8"`); ++ } ++} +diff --git a/packages/backend/migration/1652213168020-PGroongaUserName.js b/packages/backend/migration/1652213168020-PGroongaUserName.js +new file mode 100644 +index 000000000..9e1e75ece +--- /dev/null ++++ b/packages/backend/migration/1652213168020-PGroongaUserName.js +@@ -0,0 +1,11 @@ ++export class PGroongaUserName1652213168020 { ++ name = 'PGroongaUserName1652213168020' ++ ++ async up(queryRunner) { ++ await queryRunner.query(`CREATE INDEX "IDX_065d4d8f3b5adb4a08841eae3c" ON "user" USING "pgroonga" ("name" pgroonga_varchar_full_text_search_ops_v2)`); ++ } ++ ++ async down(queryRunner) { ++ await queryRunner.query(`DROP INDEX "public"."IDX_065d4d8f3b5adb4a08841eae3c"`); ++ } ++} +diff --git a/packages/backend/migration/1652213556290-PGroongaUserDescription.js b/packages/backend/migration/1652213556290-PGroongaUserDescription.js +new file mode 100644 +index 000000000..7216438ab +--- /dev/null ++++ b/packages/backend/migration/1652213556290-PGroongaUserDescription.js +@@ -0,0 +1,11 @@ ++export class PGroongaUserDescription1652213556290 { ++ name = 'PGroongaUserDescription1652213556290' ++ ++ async up(queryRunner) { ++ await queryRunner.query(`CREATE INDEX "IDX_fcb770976ff8240af5799e3ffc" ON "user_profile" USING "pgroonga" ("description" pgroonga_varchar_full_text_search_ops_v2) `); ++ } ++ ++ async down(queryRunner) { ++ await queryRunner.query(`DROP INDEX "public"."IDX_fcb770976ff8240af5799e3ffc"`); ++ } ++}