diff --git a/ext/unicode/unicode.go b/ext/unicode/unicode.go index 439f8dc..bf4b0ca 100644 --- a/ext/unicode/unicode.go +++ b/ext/unicode/unicode.go @@ -5,18 +5,19 @@ // - LIKE and REGEXP operators, // - collation sequences. // -// The implementation is not 100% compatible with the [ICU extension]: -// - upper() and lower() use [strings.ToUpper], [strings.ToLower] and [cases]; +// Like PostgreSQL, it also provides: +// - initcap(), +// - casefold(), +// - normalize(), +// - unaccent(). +// +// The implementations are not 100% compatible: +// - upper(), lower(), initcap() casefold() use [strings.ToUpper], [strings.ToLower], [strings.Title] and [cases]; +// - normalize(), unaccent() use [transform] and [unicode.Mn]; // - the LIKE operator follows [strings.EqualFold] rules; // - the REGEXP operator uses Go [regexp/syntax]; // - collation sequences use [collate]. // -// It also provides (approximately) from PostgreSQL: -// - casefold(), -// - initcap(), -// - normalize(), -// - unaccent(). -// // Expect subtle differences (e.g.) in the handling of Turkish case folding. // // [ICU extension]: https://sqlite.org/src/dir/ext/icu @@ -27,6 +28,7 @@ import ( "errors" "regexp" "strings" + "sync" "unicode" "unicode/utf8" @@ -159,8 +161,16 @@ func casefold(ctx sqlite3.Context, arg ...sqlite3.Value) { ctx.ResultRawText(cases.Fold().Bytes(arg[0].RawText())) } +var unaccentPool = sync.Pool{ + New: func() any { + return transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) + }, +} + func unaccent(ctx sqlite3.Context, arg ...sqlite3.Value) { - unaccent := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) + unaccent := unaccentPool.Get().(transform.Transformer) + defer unaccentPool.Put(unaccent) + res, _, err := transform.Bytes(unaccent, arg[0].RawText()) if err != nil { ctx.ResultError(err) // notest diff --git a/ext/uuid/uuid.go b/ext/uuid/uuid.go index 9db1304..a62a039 100644 --- a/ext/uuid/uuid.go +++ b/ext/uuid/uuid.go @@ -17,17 +17,18 @@ import ( // Register registers the SQL functions: // -// uuid([version], [domain/namespace], [id/data]) -// -// Generates a UUID as a string. -// -// uuid_str(u) -// -// Converts a UUID into a well-formed UUID string. -// -// uuid_blob(u) -// -// Converts a UUID into a 16-byte blob. +// - uuid([ version [, domain/namespace, [ id/data ]]]): +// to generate a UUID as a string, +// - uuid_str(u): +// to convert a UUID into a well-formed UUID string, +// - uuid_blob(u): +// to convert a UUID into a 16-byte blob, +// - uuid_extract_version(u): +// to extract the version of a RFC 4122 UUID, +// - uuid_extract_timestamp(u): +// to extract the timestamp of a version 1/2/6/7 UUID, +// - gen_random_uuid(u): +// to generate a version 4 (random) UUID. func Register(db *sqlite3.Conn) error { const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS return errors.Join( @@ -38,7 +39,8 @@ func Register(db *sqlite3.Conn) error { db.CreateFunction("uuid_str", 1, flags, toString), db.CreateFunction("uuid_blob", 1, flags, toBlob), db.CreateFunction("uuid_extract_version", 1, flags, version), - db.CreateFunction("uuid_extract_timestamp", 1, flags, timestamp)) + db.CreateFunction("uuid_extract_timestamp", 1, flags, timestamp), + db.CreateFunction("gen_random_uuid", 0, sqlite3.INNOCUOUS, generate)) } func generate(ctx sqlite3.Context, arg ...sqlite3.Value) {