Skip to content

Commit da6e4d8

Browse files
authored
UUID extension (#113)
1 parent 72f8ad0 commit da6e4d8

File tree

11 files changed

+363
-16
lines changed

11 files changed

+363
-16
lines changed

embed/sqlite3.wasm

-1.11 KB
Binary file not shown.

ext/bloom/bloom.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
package bloom
88

99
import (
10-
"errors"
1110
"fmt"
1211
"io"
1312
"math"
1413
"strconv"
1514

1615
"github.com/dchest/siphash"
1716
"github.com/ncruces/go-sqlite3"
17+
"github.com/ncruces/go-sqlite3/internal/util"
1818
)
1919

2020
// Register registers the bloom_filter virtual table:
@@ -47,7 +47,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
4747
return nil, err
4848
}
4949
if nelem <= 0 {
50-
return nil, errors.New("bloom: number of elements in filter must be positive")
50+
return nil, util.ErrorString("bloom: number of elements in filter must be positive")
5151
}
5252
} else {
5353
nelem = 100
@@ -59,7 +59,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
5959
return nil, err
6060
}
6161
if t.prob <= 0 || t.prob >= 1 {
62-
return nil, errors.New("bloom: probability must be in the range (0,1)")
62+
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
6363
}
6464
} else {
6565
t.prob = 0.01
@@ -71,7 +71,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
7171
return nil, err
7272
}
7373
if t.hashes <= 0 {
74-
return nil, errors.New("bloom: number of hash functions must be positive")
74+
return nil, util.ErrorString("bloom: number of hash functions must be positive")
7575
}
7676
} else {
7777
t.hashes = max(1, numHashes(t.prob))
@@ -171,7 +171,7 @@ func (t *bloom) Integrity(schema, table string, flags int) error {
171171
}
172172
defer load.Close()
173173

174-
err = errors.New("bloom: invalid parameters")
174+
err = util.ErrorString("bloom: invalid parameters")
175175
if !load.Step() {
176176
return err
177177
}
@@ -213,9 +213,9 @@ func (b *bloom) BestIndex(idx *sqlite3.IndexInfo) error {
213213
func (b *bloom) Update(arg ...sqlite3.Value) (rowid int64, err error) {
214214
if arg[0].Type() != sqlite3.NULL {
215215
if len(arg) == 1 {
216-
return 0, errors.New("bloom: elements cannot be deleted")
216+
return 0, util.ErrorString("bloom: elements cannot be deleted")
217217
}
218-
return 0, errors.New("bloom: elements cannot be updated")
218+
return 0, util.ErrorString("bloom: elements cannot be updated")
219219
}
220220

221221
blob := arg[2].RawBlob()

ext/csv/csv.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ package csv
99
import (
1010
"bufio"
1111
"encoding/csv"
12-
"errors"
1312
"fmt"
1413
"io"
1514
"io/fs"
1615
"strconv"
1716
"strings"
1817

1918
"github.com/ncruces/go-sqlite3"
19+
"github.com/ncruces/go-sqlite3/internal/util"
2020
"github.com/ncruces/go-sqlite3/util/osutil"
2121
"github.com/ncruces/go-sqlite3/util/vtabutil"
2222
)
@@ -73,7 +73,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
7373
}
7474

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

7979
table := &table{

ext/pivot/pivot.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010

1111
"github.com/ncruces/go-sqlite3"
12+
"github.com/ncruces/go-sqlite3/internal/util"
1213
)
1314

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

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

8586
if stmt.ColumnCount() != 1 {
86-
return nil, errors.New("pivot: cell query expects 1 result columns")
87+
return nil, util.ErrorString("pivot: cell query expects 1 result columns")
8788
}
8889
if stmt.BindCount() != len(table.keys)+1 {
8990
return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(table.keys)+1)

ext/statement/stmt.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ package statement
88

99
import (
1010
"encoding/json"
11-
"errors"
1211
"strconv"
1312
"strings"
1413
"unsafe"
1514

1615
"github.com/ncruces/go-sqlite3"
16+
"github.com/ncruces/go-sqlite3/internal/util"
1717
)
1818

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

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

3535
sql := "SELECT * FROM\n" + arg[0]

ext/uuid/uuid.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Package uuid provides functions to generate RFC 4122 UUIDs.
2+
//
3+
// https://sqlite.org/src/file/ext/misc/uuid.c
4+
package uuid
5+
6+
import (
7+
"bytes"
8+
"fmt"
9+
10+
"github.com/google/uuid"
11+
"github.com/ncruces/go-sqlite3"
12+
"github.com/ncruces/go-sqlite3/internal/util"
13+
)
14+
15+
// Register registers the SQL functions:
16+
//
17+
// uuid([version], [domain/namespace], [id/data])
18+
//
19+
// Generates a UUID as a string.
20+
//
21+
// uuid_str(u)
22+
//
23+
// Converts a UUID into a well-formed UUID string.
24+
//
25+
// uuid_blob(u)
26+
//
27+
// Converts a UUID into a 16-byte blob.
28+
func Register(db *sqlite3.Conn) {
29+
flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
30+
db.CreateFunction("uuid", 0, sqlite3.INNOCUOUS, generate)
31+
db.CreateFunction("uuid", 1, sqlite3.INNOCUOUS, generate)
32+
db.CreateFunction("uuid", 2, sqlite3.INNOCUOUS, generate)
33+
db.CreateFunction("uuid", 3, sqlite3.INNOCUOUS, generate)
34+
db.CreateFunction("uuid_str", 1, flags, toString)
35+
db.CreateFunction("uuid_blob", 1, flags, toBLOB)
36+
}
37+
38+
func generate(ctx sqlite3.Context, arg ...sqlite3.Value) {
39+
var (
40+
ver int
41+
err error
42+
u uuid.UUID
43+
)
44+
45+
if len(arg) > 0 {
46+
ver = arg[0].Int()
47+
} else {
48+
ver = 4
49+
}
50+
51+
switch ver {
52+
case 1:
53+
u, err = uuid.NewUUID()
54+
case 4:
55+
u, err = uuid.NewRandom()
56+
case 6:
57+
u, err = uuid.NewV6()
58+
case 7:
59+
u, err = uuid.NewV7()
60+
61+
case 2:
62+
var domain uuid.Domain
63+
if len(arg) > 1 {
64+
domain = uuid.Domain(arg[1].Int64())
65+
if domain == 0 {
66+
if txt := arg[1].RawText(); len(txt) > 0 {
67+
switch txt[0] | 0x20 {
68+
case 'g': // group
69+
domain = 1
70+
case 'o': // org
71+
domain = 2
72+
}
73+
}
74+
}
75+
}
76+
if len(arg) > 2 {
77+
id := uint32(arg[2].Int64())
78+
u, err = uuid.NewDCESecurity(domain, id)
79+
} else if domain == uuid.Person {
80+
u, err = uuid.NewDCEPerson()
81+
} else if domain == uuid.Group {
82+
u, err = uuid.NewDCEGroup()
83+
} else {
84+
err = util.ErrorString("missing id")
85+
}
86+
87+
case 3, 5:
88+
if len(arg) < 2 {
89+
err = util.ErrorString("missing data")
90+
break
91+
}
92+
ns, err := fromValue(arg[1])
93+
if err != nil {
94+
space := arg[1].RawText()
95+
switch {
96+
case bytes.EqualFold(space, []byte("url")):
97+
ns = uuid.NameSpaceURL
98+
case bytes.EqualFold(space, []byte("oid")):
99+
ns = uuid.NameSpaceOID
100+
case bytes.EqualFold(space, []byte("dns")):
101+
ns = uuid.NameSpaceDNS
102+
case bytes.EqualFold(space, []byte("fqdn")):
103+
ns = uuid.NameSpaceDNS
104+
case bytes.EqualFold(space, []byte("x500")):
105+
ns = uuid.NameSpaceX500
106+
default:
107+
ctx.ResultError(err)
108+
return
109+
}
110+
}
111+
if ver == 3 {
112+
u = uuid.NewMD5(ns, arg[2].RawBlob())
113+
} else {
114+
u = uuid.NewSHA1(ns, arg[2].RawBlob())
115+
}
116+
117+
default:
118+
err = fmt.Errorf("invalid version: %d", ver)
119+
}
120+
121+
if err != nil {
122+
ctx.ResultError(fmt.Errorf("uuid: %w", err))
123+
} else {
124+
ctx.ResultText(u.String())
125+
}
126+
}
127+
128+
func fromValue(arg sqlite3.Value) (u uuid.UUID, err error) {
129+
switch t := arg.Type(); t {
130+
case sqlite3.TEXT:
131+
u, err = uuid.ParseBytes(arg.RawText())
132+
if err != nil {
133+
err = fmt.Errorf("uuid: %w", err)
134+
}
135+
136+
case sqlite3.BLOB:
137+
blob := arg.RawBlob()
138+
if len := len(blob); len != 16 {
139+
err = fmt.Errorf("uuid: invalid BLOB length: %d", len)
140+
} else {
141+
copy(u[:], blob)
142+
}
143+
144+
default:
145+
err = fmt.Errorf("uuid: invalid type: %v", t)
146+
}
147+
return u, err
148+
}
149+
150+
func toBLOB(ctx sqlite3.Context, arg ...sqlite3.Value) {
151+
u, err := fromValue(arg[0])
152+
if err != nil {
153+
ctx.ResultError(err)
154+
} else {
155+
ctx.ResultBlob(u[:])
156+
}
157+
}
158+
159+
func toString(ctx sqlite3.Context, arg ...sqlite3.Value) {
160+
u, err := fromValue(arg[0])
161+
if err != nil {
162+
ctx.ResultError(err)
163+
} else {
164+
ctx.ResultText(u.String())
165+
}
166+
}

0 commit comments

Comments
 (0)