Files

202 lines
4.6 KiB
Go
Raw Permalink Normal View History

2024-07-04 15:28:49 +01:00
// Package uuid provides functions to generate RFC 4122 UUIDs.
//
// https://sqlite.org/src/file/ext/misc/uuid.c
package uuid
import (
"bytes"
2024-07-08 12:06:57 +01:00
"errors"
2024-07-04 15:28:49 +01:00
"fmt"
2025-01-28 11:51:14 +00:00
"time"
2024-07-04 15:28:49 +01:00
"github.com/google/uuid"
2024-10-18 12:20:32 +01:00
2024-07-04 15:28:49 +01:00
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Register registers the SQL functions:
//
2025-03-14 11:37:48 +00:00
// - uuid([ version [, domain/namespace, [ id/data ]]]):
2025-03-24 22:38:22 +00:00
// to generate a UUID as a string
2025-03-14 11:37:48 +00:00
// - uuid_str(u):
2025-03-24 22:38:22 +00:00
// to convert a UUID into a well-formed UUID string
2025-03-14 11:37:48 +00:00
// - uuid_blob(u):
2025-03-24 22:38:22 +00:00
// to convert a UUID into a 16-byte blob
2025-03-14 11:37:48 +00:00
// - uuid_extract_version(u):
2025-03-24 22:38:22 +00:00
// to extract the version of a RFC 4122 UUID
2025-03-14 11:37:48 +00:00
// - uuid_extract_timestamp(u):
2025-03-24 22:38:22 +00:00
// to extract the timestamp of a version 1/2/6/7 UUID
2025-03-14 11:37:48 +00:00
// - gen_random_uuid(u):
2025-03-24 22:38:22 +00:00
// to generate a version 4 (random) UUID
2024-07-08 12:06:57 +01:00
func Register(db *sqlite3.Conn) error {
2024-09-30 13:30:50 +01:00
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
2024-07-08 12:06:57 +01:00
return errors.Join(
db.CreateFunction("uuid", 0, sqlite3.INNOCUOUS, generate),
db.CreateFunction("uuid", 1, sqlite3.INNOCUOUS, generate),
db.CreateFunction("uuid", 2, sqlite3.INNOCUOUS, generate),
db.CreateFunction("uuid", 3, sqlite3.INNOCUOUS, generate),
db.CreateFunction("uuid_str", 1, flags, toString),
2025-01-28 11:51:14 +00:00
db.CreateFunction("uuid_blob", 1, flags, toBlob),
db.CreateFunction("uuid_extract_version", 1, flags, version),
2025-03-14 11:37:48 +00:00
db.CreateFunction("uuid_extract_timestamp", 1, flags, timestamp),
db.CreateFunction("gen_random_uuid", 0, sqlite3.INNOCUOUS, generate))
2024-07-04 15:28:49 +01:00
}
func generate(ctx sqlite3.Context, arg ...sqlite3.Value) {
var (
ver int
err error
u uuid.UUID
)
if len(arg) > 0 {
ver = arg[0].Int()
} else {
ver = 4
}
switch ver {
case 1:
u, err = uuid.NewUUID()
case 4:
u, err = uuid.NewRandom()
case 6:
u, err = uuid.NewV6()
case 7:
u, err = uuid.NewV7()
case 2:
var domain uuid.Domain
if len(arg) > 1 {
domain = uuid.Domain(arg[1].Int64())
if domain == 0 {
if txt := arg[1].RawText(); len(txt) > 0 {
2024-08-26 19:47:46 +01:00
switch txt[0] | 0x20 { // to lower
2024-07-04 15:28:49 +01:00
case 'g': // group
2024-08-26 19:47:46 +01:00
domain = uuid.Group
2024-07-04 15:28:49 +01:00
case 'o': // org
2024-08-26 19:47:46 +01:00
domain = uuid.Org
2024-07-04 15:28:49 +01:00
}
}
}
}
2024-08-26 19:47:46 +01:00
switch {
case len(arg) > 2:
u, err = uuid.NewDCESecurity(domain, uint32(arg[2].Int64()))
case domain == uuid.Person:
2024-07-04 15:28:49 +01:00
u, err = uuid.NewDCEPerson()
2024-08-26 19:47:46 +01:00
case domain == uuid.Group:
2024-07-04 15:28:49 +01:00
u, err = uuid.NewDCEGroup()
2024-08-26 19:47:46 +01:00
default:
2024-07-04 15:28:49 +01:00
err = util.ErrorString("missing id")
}
case 3, 5:
if len(arg) < 2 {
err = util.ErrorString("missing data")
break
}
ns, err := fromValue(arg[1])
if err != nil {
space := arg[1].RawText()
switch {
case bytes.EqualFold(space, []byte("url")):
ns = uuid.NameSpaceURL
case bytes.EqualFold(space, []byte("oid")):
ns = uuid.NameSpaceOID
case bytes.EqualFold(space, []byte("dns")):
ns = uuid.NameSpaceDNS
case bytes.EqualFold(space, []byte("fqdn")):
ns = uuid.NameSpaceDNS
case bytes.EqualFold(space, []byte("x500")):
ns = uuid.NameSpaceX500
default:
ctx.ResultError(err)
2024-07-26 13:29:24 +01:00
return // notest
2024-07-04 15:28:49 +01:00
}
}
if ver == 3 {
u = uuid.NewMD5(ns, arg[2].RawBlob())
} else {
u = uuid.NewSHA1(ns, arg[2].RawBlob())
}
default:
err = fmt.Errorf("invalid version: %d", ver)
}
if err != nil {
2024-07-26 13:29:24 +01:00
ctx.ResultError(fmt.Errorf("uuid: %w", err)) // notest
2024-07-04 15:28:49 +01:00
} else {
ctx.ResultText(u.String())
}
}
func fromValue(arg sqlite3.Value) (u uuid.UUID, err error) {
switch t := arg.Type(); t {
case sqlite3.TEXT:
u, err = uuid.ParseBytes(arg.RawText())
if err != nil {
err = fmt.Errorf("uuid: %w", err)
}
case sqlite3.BLOB:
blob := arg.RawBlob()
if len := len(blob); len != 16 {
err = fmt.Errorf("uuid: invalid BLOB length: %d", len)
} else {
copy(u[:], blob)
}
default:
err = fmt.Errorf("uuid: invalid type: %v", t)
}
return u, err
}
2024-07-08 12:06:57 +01:00
func toBlob(ctx sqlite3.Context, arg ...sqlite3.Value) {
2024-07-04 15:28:49 +01:00
u, err := fromValue(arg[0])
if err != nil {
2024-07-26 13:29:24 +01:00
ctx.ResultError(err) // notest
2024-07-04 15:28:49 +01:00
} else {
ctx.ResultBlob(u[:])
}
}
func toString(ctx sqlite3.Context, arg ...sqlite3.Value) {
u, err := fromValue(arg[0])
if err != nil {
2024-07-26 13:29:24 +01:00
ctx.ResultError(err) // notest
2024-07-04 15:28:49 +01:00
} else {
ctx.ResultText(u.String())
}
}
2025-01-28 11:51:14 +00:00
func version(ctx sqlite3.Context, arg ...sqlite3.Value) {
u, err := fromValue(arg[0])
if err != nil {
ctx.ResultError(err)
return // notest
}
if u.Variant() == uuid.RFC4122 {
ctx.ResultInt64(int64(u.Version()))
}
}
func timestamp(ctx sqlite3.Context, arg ...sqlite3.Value) {
u, err := fromValue(arg[0])
if err != nil {
ctx.ResultError(err)
return // notest
}
if u.Variant() == uuid.RFC4122 {
switch u.Version() {
case 1, 2, 6, 7:
ctx.ResultTime(
2025-09-19 11:13:23 +01:00
time.Unix(u.Time().UnixTime()).UTC(),
2025-01-28 11:51:14 +00:00
sqlite3.TimeFormatDefault)
}
}
}