Make tag searches case insensitive

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-10-05 20:58:44 +00:00 committed by Gabriel
parent 9adb872985
commit 5a93761af7
4 changed files with 39 additions and 26 deletions

View file

@ -9,12 +9,13 @@ import (
"fmt"
"io"
"github.com/mattn/go-sqlite3"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/util"
"github.com/mattn/go-sqlite3"
"gorm.io/gorm"
)
func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) {
@ -76,7 +77,6 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
sha256sum = hex.EncodeToString(hasher.Sum(nil))
return nil
})
if err != nil {
return params.FileObject{}, fmt.Errorf("failed to write blob: %w", err)
}
@ -104,7 +104,7 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
return s.sqlFileObjectToCommonParams(fileObj), nil
}
func (s *sqlDatabase) UpdateFileObject(ctx context.Context, objID uint, param params.UpdateFileObjectParams) (fileObjParam params.FileObject, err error) {
func (s *sqlDatabase) UpdateFileObject(_ context.Context, objID uint, param params.UpdateFileObjectParams) (fileObjParam params.FileObject, err error) {
if err := param.Validate(); err != nil {
return params.FileObject{}, fmt.Errorf("failed to validate update params: %w", err)
}
@ -160,7 +160,6 @@ func (s *sqlDatabase) UpdateFileObject(ctx context.Context, objID uint, param pa
return nil
})
if err != nil {
return params.FileObject{}, err
}
@ -168,7 +167,7 @@ func (s *sqlDatabase) UpdateFileObject(ctx context.Context, objID uint, param pa
return s.sqlFileObjectToCommonParams(fileObj), nil
}
func (s *sqlDatabase) DeleteFileObject(ctx context.Context, objID uint) (err error) {
func (s *sqlDatabase) DeleteFileObject(_ context.Context, objID uint) (err error) {
var fileObjParam params.FileObject
var noop bool
defer func() {
@ -203,7 +202,7 @@ func (s *sqlDatabase) DeleteFileObject(ctx context.Context, objID uint) (err err
return nil
}
func (s *sqlDatabase) GetFileObject(ctx context.Context, objID uint) (params.FileObject, error) {
func (s *sqlDatabase) GetFileObject(_ context.Context, objID uint) (params.FileObject, error) {
var fileObj FileObject
if err := s.conn.Preload("TagsList").Where("id = ?", objID).Omit("content").First(&fileObj).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@ -214,7 +213,7 @@ func (s *sqlDatabase) GetFileObject(ctx context.Context, objID uint) (params.Fil
return s.sqlFileObjectToCommonParams(fileObj), nil
}
func (s *sqlDatabase) SearchFileObjectByTags(ctx context.Context, tags []string, page, pageSize uint64) (params.FileObjectPaginatedResponse, error) {
func (s *sqlDatabase) SearchFileObjectByTags(_ context.Context, tags []string, page, pageSize uint64) (params.FileObjectPaginatedResponse, error) {
if page == 0 {
page = 1
}
@ -298,7 +297,6 @@ func (s *sqlDatabase) OpenFileObjectContent(ctx context.Context, objID uint) (io
}
return nil
})
if err != nil {
conn.Close()
return nil, fmt.Errorf("failed to open blob for reading: %w", err)
@ -326,7 +324,7 @@ func (b *blobReadCloser) Close() error {
return connErr
}
func (s *sqlDatabase) ListFileObjects(ctx context.Context, page, pageSize uint64) (params.FileObjectPaginatedResponse, error) {
func (s *sqlDatabase) ListFileObjects(_ context.Context, page, pageSize uint64) (params.FileObjectPaginatedResponse, error) {
if page == 0 {
page = 1
}

View file

@ -232,7 +232,7 @@ func (s *FileStoreTestSuite) TestUpdateFileObjectName() {
s.Require().Nil(err)
s.Require().Equal(newName, updated.Name)
s.Require().Equal(s.Fixtures.FileObjects[0].ID, updated.ID)
s.Require().Equal(s.Fixtures.FileObjects[0].Size, updated.Size) // Size should not change
s.Require().Equal(s.Fixtures.FileObjects[0].Size, updated.Size) // Size should not change
s.Require().Equal(s.Fixtures.FileObjects[0].SHA256, updated.SHA256) // SHA256 should not change
// Verify the change persists
@ -543,27 +543,42 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsAllTagsRequired() {
s.Require().True(foundIDs[file2.ID])
}
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsCaseSensitive() {
// Test case sensitivity of tag search
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsCaseInsensitive() {
// Test case insensitivity of tag search (COLLATE NOCASE)
content1 := []byte("File with lowercase tag")
file1, err := s.Store.CreateFileObject(s.ctx, "case-test-1.txt", int64(len(content1)), []string{"lowercase"}, bytes.NewReader(content1))
file1, err := s.Store.CreateFileObject(s.ctx, "case-test-1.txt", int64(len(content1)), []string{"TestTag"}, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with UPPERCASE tag")
file2, err := s.Store.CreateFileObject(s.ctx, "case-test-2.txt", int64(len(content2)), []string{"UPPERCASE"}, bytes.NewReader(content2))
file2, err := s.Store.CreateFileObject(s.ctx, "case-test-2.txt", int64(len(content2)), []string{"TESTTAG"}, bytes.NewReader(content2))
s.Require().Nil(err)
// Search for lowercase - should only return file1
result, err := s.Store.SearchFileObjectByTags(s.ctx, []string{"lowercase"}, 1, 10)
content3 := []byte("File with MixedCase tag")
file3, err := s.Store.CreateFileObject(s.ctx, "case-test-3.txt", int64(len(content3)), []string{"testTAG"}, bytes.NewReader(content3))
s.Require().Nil(err)
s.Require().Equal(1, len(result.Results))
s.Require().Equal(file1.ID, result.Results[0].ID)
// Search for UPPERCASE - should only return file2
result, err = s.Store.SearchFileObjectByTags(s.ctx, []string{"UPPERCASE"}, 1, 10)
// Search for lowercase - should return all files (case insensitive)
result, err := s.Store.SearchFileObjectByTags(s.ctx, []string{"testtag"}, 1, 10)
s.Require().Nil(err)
s.Require().Equal(1, len(result.Results))
s.Require().Equal(file2.ID, result.Results[0].ID)
s.Require().Equal(3, len(result.Results), "Should match all case variations")
foundIDs := make(map[uint]bool)
for _, fileObj := range result.Results {
foundIDs[fileObj.ID] = true
}
s.Require().True(foundIDs[file1.ID], "Should find file with 'TestTag'")
s.Require().True(foundIDs[file2.ID], "Should find file with 'TESTTAG'")
s.Require().True(foundIDs[file3.ID], "Should find file with 'testTAG'")
// Search for UPPERCASE - should also return all files
result, err = s.Store.SearchFileObjectByTags(s.ctx, []string{"TESTTAG"}, 1, 10)
s.Require().Nil(err)
s.Require().Equal(3, len(result.Results), "Should match all case variations")
// Search for MixedCase - should also return all files
result, err = s.Store.SearchFileObjectByTags(s.ctx, []string{"TeStTaG"}, 1, 10)
s.Require().Nil(err)
s.Require().Equal(3, len(result.Results), "Should match all case variations")
}
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsOrderByCreatedAt() {

View file

@ -484,7 +484,7 @@ func (FileObject) TableName() string {
type FileObjectTag struct {
ID uint `gorm:"primaryKey"`
FileObjectID uint `gorm:"index:idx_fileobject_tags_doc_id,priority:1;index:idx_fileobject_tags_tag,priority:1;not null"`
Tag string `gorm:"type:TEXT;index:idx_fileobject_tags_tag,priority:2;not null"`
Tag string `gorm:"type:TEXT COLLATE NOCASE;index:idx_fileobject_tags_tag,priority:2;not null"`
}
// TableName overrides the default table name