memos/store/db/sqlite/functions.go
Steven 8770b186e4 fix: add Unicode case-insensitive search for SQLite (#5559)
Add custom memos_unicode_lower() SQLite function to enable proper
case-insensitive text search for non-English languages (Cyrillic,
Greek, CJK, etc.).

Previously, SQLite's LOWER() only worked for ASCII characters due to
modernc.org/sqlite lacking ICU extension. This caused searches for
non-English text to be case-sensitive (e.g., searching 'блины' wouldn't
find 'Блины').

Changes:
- Add store/db/sqlite/functions.go with Unicode case folding function
- Register custom function using golang.org/x/text/cases.Fold()
- Update filter renderer to use custom function for SQLite dialect
- Add test for Unicode case-insensitive search
- Make golang.org/x/text a direct dependency

Fixes #5559
2026-02-02 21:10:07 +08:00

44 lines
1.4 KiB
Go

// Package sqlite provides SQLite driver implementation with custom functions.
// Custom functions are registered globally on first use to extend SQLite's
// limited ASCII-only text operations with proper Unicode support.
package sqlite
import (
"database/sql/driver"
"sync"
"golang.org/x/text/cases"
msqlite "modernc.org/sqlite"
)
var (
registerUnicodeLowerOnce sync.Once
registerUnicodeLowerErr error
// unicodeFold provides Unicode case folding for case-insensitive comparisons.
// It's safe to use concurrently and reused across all function calls.
unicodeFold = cases.Fold()
)
// ensureUnicodeLowerRegistered registers the memos_unicode_lower custom function
// with SQLite. This function provides proper Unicode case folding for case-insensitive
// text comparisons, overcoming modernc.org/sqlite's lack of ICU extension.
//
// The function is registered once globally and is safe to call multiple times.
func ensureUnicodeLowerRegistered() error {
registerUnicodeLowerOnce.Do(func() {
registerUnicodeLowerErr = msqlite.RegisterScalarFunction("memos_unicode_lower", 1, func(_ *msqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
if len(args) == 0 || args[0] == nil {
return nil, nil
}
switch v := args[0].(type) {
case string:
return unicodeFold.String(v), nil
case []byte:
return unicodeFold.String(string(v)), nil
default:
return v, nil
}
})
})
return registerUnicodeLowerErr
}