diff --git a/database/sql/file_store.go b/database/sql/file_store.go index f93b6ff8..61a3e1a8 100644 --- a/database/sql/file_store.go +++ b/database/sql/file_store.go @@ -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 } diff --git a/database/sql/file_store_test.go b/database/sql/file_store_test.go index 364ccf80..77352241 100644 --- a/database/sql/file_store_test.go +++ b/database/sql/file_store_test.go @@ -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() { diff --git a/database/sql/models.go b/database/sql/models.go index 3b297c97..388375ed 100644 --- a/database/sql/models.go +++ b/database/sql/models.go @@ -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 diff --git a/util/util.go b/util/util.go index 30a3cfe7..6234aa84 100644 --- a/util/util.go +++ b/util/util.go @@ -22,11 +22,11 @@ import ( "net/http" "unicode/utf8" + "github.com/h2non/filetype" + "github.com/cloudbase/garm-provider-common/cloudconfig" runnerErrors "github.com/cloudbase/garm-provider-common/errors" commonParams "github.com/cloudbase/garm-provider-common/params" - "github.com/h2non/filetype" - "github.com/cloudbase/garm/internal/templates" "github.com/cloudbase/garm/runner/common" )