Skip to content

Commit

Permalink
UUID extension (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncruces committed Jul 4, 2024
1 parent 72f8ad0 commit da6e4d8
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 16 deletions.
Binary file modified embed/sqlite3.wasm
Binary file not shown.
14 changes: 7 additions & 7 deletions ext/bloom/bloom.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
package bloom

import (
"errors"
"fmt"
"io"
"math"
"strconv"

"github.com/dchest/siphash"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)

// Register registers the bloom_filter virtual table:
Expand Down Expand Up @@ -47,7 +47,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if nelem <= 0 {
return nil, errors.New("bloom: number of elements in filter must be positive")
return nil, util.ErrorString("bloom: number of elements in filter must be positive")
}
} else {
nelem = 100
Expand All @@ -59,7 +59,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if t.prob <= 0 || t.prob >= 1 {
return nil, errors.New("bloom: probability must be in the range (0,1)")
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
}
} else {
t.prob = 0.01
Expand All @@ -71,7 +71,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
return nil, err
}
if t.hashes <= 0 {
return nil, errors.New("bloom: number of hash functions must be positive")
return nil, util.ErrorString("bloom: number of hash functions must be positive")
}
} else {
t.hashes = max(1, numHashes(t.prob))
Expand Down Expand Up @@ -171,7 +171,7 @@ func (t *bloom) Integrity(schema, table string, flags int) error {
}
defer load.Close()

err = errors.New("bloom: invalid parameters")
err = util.ErrorString("bloom: invalid parameters")
if !load.Step() {
return err
}
Expand Down Expand Up @@ -213,9 +213,9 @@ func (b *bloom) BestIndex(idx *sqlite3.IndexInfo) error {
func (b *bloom) Update(arg ...sqlite3.Value) (rowid int64, err error) {
if arg[0].Type() != sqlite3.NULL {
if len(arg) == 1 {
return 0, errors.New("bloom: elements cannot be deleted")
return 0, util.ErrorString("bloom: elements cannot be deleted")
}
return 0, errors.New("bloom: elements cannot be updated")
return 0, util.ErrorString("bloom: elements cannot be updated")
}

blob := arg[2].RawBlob()
Expand Down
4 changes: 2 additions & 2 deletions ext/csv/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ package csv
import (
"bufio"
"encoding/csv"
"errors"
"fmt"
"io"
"io/fs"
"strconv"
"strings"

"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/util/osutil"
"github.com/ncruces/go-sqlite3/util/vtabutil"
)
Expand Down Expand Up @@ -73,7 +73,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
}

if (filename == "") == (data == "") {
return nil, errors.New(`csv: must specify either "filename" or "data" but not both`)
return nil, util.ErrorString(`csv: must specify either "filename" or "data" but not both`)
}

table := &table{
Expand Down
5 changes: 3 additions & 2 deletions ext/pivot/pivot.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)

// Register registers the pivot virtual table.
Expand Down Expand Up @@ -65,7 +66,7 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err
}

if stmt.ColumnCount() != 2 {
return nil, errors.New("pivot: column definition query expects 2 result columns")
return nil, util.ErrorString("pivot: column definition query expects 2 result columns")
}
for stmt.Step() {
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
Expand All @@ -83,7 +84,7 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err
}

if stmt.ColumnCount() != 1 {
return nil, errors.New("pivot: cell query expects 1 result columns")
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
}
if stmt.BindCount() != len(table.keys)+1 {
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(table.keys)+1)
Expand Down
4 changes: 2 additions & 2 deletions ext/statement/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ package statement

import (
"encoding/json"
"errors"
"strconv"
"strings"
"unsafe"

"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)

// Register registers the statement virtual table.
Expand All @@ -29,7 +29,7 @@ type table struct {

func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
if len(arg) != 1 {
return nil, errors.New("statement: wrong number of arguments")
return nil, util.ErrorString("statement: wrong number of arguments")
}

sql := "SELECT * FROM\n" + arg[0]
Expand Down
166 changes: 166 additions & 0 deletions ext/uuid/uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Package uuid provides functions to generate RFC 4122 UUIDs.
//
// https://sqlite.org/src/file/ext/misc/uuid.c
package uuid

import (
"bytes"
"fmt"

"github.com/google/uuid"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
)

// 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.
func Register(db *sqlite3.Conn) {
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
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)
db.CreateFunction("uuid_blob", 1, flags, toBLOB)
}

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 {
switch txt[0] | 0x20 {
case 'g': // group
domain = 1
case 'o': // org
domain = 2
}
}
}
}
if len(arg) > 2 {
id := uint32(arg[2].Int64())
u, err = uuid.NewDCESecurity(domain, id)
} else if domain == uuid.Person {
u, err = uuid.NewDCEPerson()
} else if domain == uuid.Group {
u, err = uuid.NewDCEGroup()
} else {
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)
return
}
}
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 {
ctx.ResultError(fmt.Errorf("uuid: %w", err))
} 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
}

func toBLOB(ctx sqlite3.Context, arg ...sqlite3.Value) {
u, err := fromValue(arg[0])
if err != nil {
ctx.ResultError(err)
} else {
ctx.ResultBlob(u[:])
}
}

func toString(ctx sqlite3.Context, arg ...sqlite3.Value) {
u, err := fromValue(arg[0])
if err != nil {
ctx.ResultError(err)
} else {
ctx.ResultText(u.String())
}
}
Loading

0 comments on commit da6e4d8

Please sign in to comment.