chore: import upstream snapshot with attribution
Backend Tests / Tests (other) (push) Failing after 1s
Backend Tests / Static Checks (push) Failing after 2s
Backend Tests / Tests (internal) (push) Failing after 2s
Backend Tests / Tests (server) (push) Failing after 1s
Backend Tests / Tests (store) (push) Failing after 1s
Build Canary Image / build-frontend (push) Failing after 1s
Frontend Tests / Lint (push) Failing after 1s
Build Canary Image / build-push (linux/amd64) (push) Has been skipped
Build Canary Image / build-push (linux/arm64) (push) Has been skipped
Build Canary Image / merge (push) Has been skipped
Frontend Tests / Build (push) Failing after 1s
Release Please / release-please (push) Failing after 0s
Proto Linter / Lint Protos (push) Failing after 2s
Backend Tests / Tests (other) (push) Failing after 1s
Backend Tests / Static Checks (push) Failing after 2s
Backend Tests / Tests (internal) (push) Failing after 2s
Backend Tests / Tests (server) (push) Failing after 1s
Backend Tests / Tests (store) (push) Failing after 1s
Build Canary Image / build-frontend (push) Failing after 1s
Frontend Tests / Lint (push) Failing after 1s
Build Canary Image / build-push (linux/amd64) (push) Has been skipped
Build Canary Image / build-push (linux/arm64) (push) Has been skipped
Build Canary Image / merge (push) Has been skipped
Frontend Tests / Build (push) Failing after 1s
Release Please / release-please (push) Failing after 0s
Proto Linter / Lint Protos (push) Failing after 2s
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"regexp"
|
||||
"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
|
||||
}
|
||||
|
||||
var (
|
||||
registerRegexpOnce sync.Once
|
||||
registerRegexpErr error
|
||||
// regexpCache memoizes compiled patterns; keys are pattern strings.
|
||||
regexpCache sync.Map
|
||||
)
|
||||
|
||||
// ensureRegexpRegistered registers a Go-backed `regexp(pattern, value)` scalar
|
||||
// function so SQLite's `value REGEXP pattern` operator works (modernc.org/sqlite
|
||||
// has no built-in implementation). Patterns use Go's RE2 syntax. Registered once
|
||||
// globally; safe to call multiple times.
|
||||
func ensureRegexpRegistered() error {
|
||||
registerRegexpOnce.Do(func() {
|
||||
registerRegexpErr = msqlite.RegisterScalarFunction("regexp", 2, func(_ *msqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
||||
if len(args) != 2 || args[0] == nil || args[1] == nil {
|
||||
return int64(0), nil
|
||||
}
|
||||
pattern, ok := args[0].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("regexp pattern must be a string")
|
||||
}
|
||||
var value string
|
||||
switch v := args[1].(type) {
|
||||
case string:
|
||||
value = v
|
||||
case []byte:
|
||||
value = string(v)
|
||||
default:
|
||||
return int64(0), nil
|
||||
}
|
||||
re, err := compileRegexp(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if re.MatchString(value) {
|
||||
return int64(1), nil
|
||||
}
|
||||
return int64(0), nil
|
||||
})
|
||||
})
|
||||
return registerRegexpErr
|
||||
}
|
||||
|
||||
// compileRegexp compiles and caches a RE2 pattern.
|
||||
func compileRegexp(pattern string) (*regexp.Regexp, error) {
|
||||
if cached, ok := regexpCache.Load(pattern); ok {
|
||||
if re, ok := cached.(*regexp.Regexp); ok {
|
||||
return re, nil
|
||||
}
|
||||
}
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regexpCache.Store(pattern, re)
|
||||
return re, nil
|
||||
}
|
||||
Reference in New Issue
Block a user