Skip to content

Commit bb5f859

Browse files
author
Gusted
authored
Fix non-ASCII search on database (#18437)
Use `ToASCIIUpper` for SQLite database on issues search, this because `UPPER(x)` on SQLite only transforms ASCII letters. Resolves #18429
1 parent 7f2530e commit bb5f859

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

models/issue.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"code.gitea.io/gitea/modules/git"
2424
"code.gitea.io/gitea/modules/log"
2525
"code.gitea.io/gitea/modules/references"
26+
"code.gitea.io/gitea/modules/setting"
2627
api "code.gitea.io/gitea/modules/structs"
2728
"code.gitea.io/gitea/modules/timeutil"
2829
"code.gitea.io/gitea/modules/util"
@@ -1862,7 +1863,12 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen,
18621863
func SearchIssueIDsByKeyword(ctx context.Context, kw string, repoIDs []int64, limit, start int) (int64, []int64, error) {
18631864
repoCond := builder.In("repo_id", repoIDs)
18641865
subQuery := builder.Select("id").From("issue").Where(repoCond)
1865-
kw = strings.ToUpper(kw)
1866+
// SQLite's UPPER function only transforms ASCII letters.
1867+
if setting.Database.UseSQLite3 {
1868+
kw = util.ToUpperASCII(kw)
1869+
} else {
1870+
kw = strings.ToUpper(kw)
1871+
}
18661872
cond := builder.And(
18671873
repoCond,
18681874
builder.Or(

modules/util/util.go

+11
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,14 @@ func CryptoRandomBytes(length int64) ([]byte, error) {
170170
_, err := rand.Read(buf)
171171
return buf, err
172172
}
173+
174+
// ToUpperASCII returns s with all ASCII letters mapped to their upper case.
175+
func ToUpperASCII(s string) string {
176+
b := []byte(s)
177+
for i, c := range b {
178+
if 'a' <= c && c <= 'z' {
179+
b[i] -= 'a' - 'A'
180+
}
181+
}
182+
return string(b)
183+
}

modules/util/util_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,37 @@ func Test_OptionalBool(t *testing.T) {
186186
assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("t"))
187187
assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("True"))
188188
}
189+
190+
// Test case for any function which accepts and returns a single string.
191+
type StringTest struct {
192+
in, out string
193+
}
194+
195+
var upperTests = []StringTest{
196+
{"", ""},
197+
{"ONLYUPPER", "ONLYUPPER"},
198+
{"abc", "ABC"},
199+
{"AbC123", "ABC123"},
200+
{"azAZ09_", "AZAZ09_"},
201+
{"longStrinGwitHmixofsmaLLandcAps", "LONGSTRINGWITHMIXOFSMALLANDCAPS"},
202+
{"long\u0250string\u0250with\u0250nonascii\u2C6Fchars", "LONG\u0250STRING\u0250WITH\u0250NONASCII\u2C6FCHARS"},
203+
{"\u0250\u0250\u0250\u0250\u0250", "\u0250\u0250\u0250\u0250\u0250"},
204+
{"a\u0080\U0010FFFF", "A\u0080\U0010FFFF"},
205+
{"lél", "LéL"},
206+
}
207+
208+
func TestToUpperASCII(t *testing.T) {
209+
for _, tc := range upperTests {
210+
assert.Equal(t, ToUpperASCII(tc.in), tc.out)
211+
}
212+
}
213+
214+
func BenchmarkToUpper(b *testing.B) {
215+
for _, tc := range upperTests {
216+
b.Run(tc.in, func(b *testing.B) {
217+
for i := 0; i < b.N; i++ {
218+
ToUpperASCII(tc.in)
219+
}
220+
})
221+
}
222+
}

0 commit comments

Comments
 (0)