fix(search): expand keyword storage for rebuild safety

This commit is contained in:
vsxd 2026-03-19 18:24:59 +08:00 committed by Xudong Sun
parent cc4782c16d
commit a7f8b0afcf
4 changed files with 110 additions and 24 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE skill_search_document
ALTER COLUMN keywords TYPE TEXT;

View file

@ -20,7 +20,7 @@ public class SkillSearchDocumentEntity {
@Column(name = "namespace_slug", nullable = false, length = 64)
private String namespaceSlug;
@Column(name = "owner_id", nullable = false)
@Column(name = "owner_id", nullable = false, length = 128)
private String ownerId;
@Column(length = 256)
@ -38,10 +38,10 @@ public class SkillSearchDocumentEntity {
@Column(name = "semantic_vector", columnDefinition = "TEXT")
private String semanticVector;
@Column(nullable = false, length = 20)
@Column(nullable = false, length = 32)
private String visibility;
@Column(nullable = false, length = 20)
@Column(nullable = false, length = 32)
private String status;
@Column(name = "updated_at", nullable = false)

View file

@ -16,6 +16,11 @@ import java.util.Optional;
*/
@Service
public class PostgresFullTextIndexService implements SearchIndexService {
private static final int NAMESPACE_SLUG_MAX_LENGTH = 64;
private static final int OWNER_ID_MAX_LENGTH = 128;
private static final int TITLE_MAX_LENGTH = 256;
private static final int VISIBILITY_MAX_LENGTH = 32;
private static final int STATUS_MAX_LENGTH = 32;
private final SkillSearchDocumentJpaRepository repository;
private final SearchEmbeddingService searchEmbeddingService;
@ -29,34 +34,35 @@ public class PostgresFullTextIndexService implements SearchIndexService {
@Override
@Transactional
public void index(SkillSearchDocument document) {
SkillSearchDocument normalizedDocument = normalize(document);
Optional<SkillSearchDocumentEntity> existing = repository.findBySkillId(document.skillId());
if (existing.isPresent()) {
SkillSearchDocumentEntity entity = existing.get();
entity.setNamespaceId(document.namespaceId());
entity.setNamespaceSlug(document.namespaceSlug());
entity.setOwnerId(document.ownerId());
entity.setTitle(document.title());
entity.setSummary(document.summary());
entity.setKeywords(document.keywords());
entity.setSearchText(document.searchText());
entity.setSemanticVector(buildSemanticVector(document));
entity.setVisibility(document.visibility());
entity.setStatus(document.status());
entity.setNamespaceId(normalizedDocument.namespaceId());
entity.setNamespaceSlug(normalizedDocument.namespaceSlug());
entity.setOwnerId(normalizedDocument.ownerId());
entity.setTitle(normalizedDocument.title());
entity.setSummary(normalizedDocument.summary());
entity.setKeywords(normalizedDocument.keywords());
entity.setSearchText(normalizedDocument.searchText());
entity.setSemanticVector(buildSemanticVector(normalizedDocument));
entity.setVisibility(normalizedDocument.visibility());
entity.setStatus(normalizedDocument.status());
repository.save(entity);
} else {
SkillSearchDocumentEntity entity = new SkillSearchDocumentEntity(
document.skillId(),
document.namespaceId(),
document.namespaceSlug(),
document.ownerId(),
document.title(),
document.summary(),
document.keywords(),
document.searchText(),
buildSemanticVector(document),
document.visibility(),
document.status()
normalizedDocument.skillId(),
normalizedDocument.namespaceId(),
normalizedDocument.namespaceSlug(),
normalizedDocument.ownerId(),
normalizedDocument.title(),
normalizedDocument.summary(),
normalizedDocument.keywords(),
normalizedDocument.searchText(),
buildSemanticVector(normalizedDocument),
normalizedDocument.visibility(),
normalizedDocument.status()
);
repository.save(entity);
}
@ -88,4 +94,27 @@ public class PostgresFullTextIndexService implements SearchIndexService {
private String safe(String value) {
return value == null ? "" : value;
}
private SkillSearchDocument normalize(SkillSearchDocument document) {
return new SkillSearchDocument(
document.skillId(),
document.namespaceId(),
truncate(document.namespaceSlug(), NAMESPACE_SLUG_MAX_LENGTH),
truncate(document.ownerId(), OWNER_ID_MAX_LENGTH),
truncate(document.title(), TITLE_MAX_LENGTH),
document.summary(),
document.keywords(),
document.searchText(),
document.semanticVector(),
truncate(document.visibility(), VISIBILITY_MAX_LENGTH),
truncate(document.status(), STATUS_MAX_LENGTH)
);
}
private String truncate(String value, int maxLength) {
if (value == null || value.length() <= maxLength) {
return value;
}
return value.substring(0, maxLength);
}
}

View file

@ -0,0 +1,55 @@
package com.iflytek.skillhub.search.postgres;
import com.iflytek.skillhub.infra.jpa.SkillSearchDocumentEntity;
import com.iflytek.skillhub.infra.jpa.SkillSearchDocumentJpaRepository;
import com.iflytek.skillhub.search.HashingSearchEmbeddingService;
import com.iflytek.skillhub.search.SkillSearchDocument;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class PostgresFullTextIndexServiceTest {
@Test
void indexShouldTruncateOnlyColumnsThatStillHaveDatabaseLimits() {
SkillSearchDocumentJpaRepository repository = mock(SkillSearchDocumentJpaRepository.class);
when(repository.findBySkillId(1L)).thenReturn(Optional.empty());
PostgresFullTextIndexService service = new PostgresFullTextIndexService(
repository,
new HashingSearchEmbeddingService()
);
SkillSearchDocument document = new SkillSearchDocument(
1L,
2L,
"n".repeat(80),
"o".repeat(140),
"t".repeat(300),
"summary",
"k".repeat(700),
"search text",
null,
"PUBLIC",
"ACTIVE"
);
service.index(document);
ArgumentCaptor<SkillSearchDocumentEntity> captor = ArgumentCaptor.forClass(SkillSearchDocumentEntity.class);
verify(repository).save(captor.capture());
SkillSearchDocumentEntity entity = captor.getValue();
assertThat(entity.getNamespaceSlug()).hasSize(64);
assertThat(entity.getOwnerId()).hasSize(128);
assertThat(entity.getTitle()).hasSize(256);
assertThat(entity.getKeywords()).hasSize(700);
assertThat(entity.getSearchText()).isEqualTo("search text");
}
}