Repository navigation
Expand file tree
/
Copy pathfunction.go
More file actions
151 lines (137 loc) · 4.16 KB
/
Copy pathfunction.go
File metadata and controls
151 lines (137 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// SPDX-License-Identifier: Apache-2.0
package dbkit
import (
"database/sql/driver"
"fmt"
"slices"
"strings"
"unicode"
"unicode/utf8"
)
// Function is one Go function an engine registers for SQL to call.
type Function struct {
// Name is the name SQL calls the function by, its ASCII letters matched in any case and every other letter exactly.
Name string
// Args is the count of arguments the function takes, or -1 for any count.
Args int
// Deterministic reports that the same arguments always give the same result.
Deterministic bool
// Call computes the result from the arguments, which stay valid only until it returns.
Call func(args []driver.Value) (driver.Value, error)
}
// FunctionList is a checked list of functions with no name repeated, its ASCII letters in any case.
type FunctionList struct {
// fns holds the functions in the order given.
fns []Function
}
// NewFunctionList returns a list of fns, or an error naming the first function it refuses.
func NewFunctionList(fns ...Function) (*FunctionList, error) {
seen := make(map[string]string, len(fns))
for i, fn := range fns {
if err := checkFunction(i, fn); err != nil {
return nil, err
}
key := asciiLower(fn.Name)
if first, ok := seen[key]; ok {
return nil, fmt.Errorf("dbkit: function %q repeats the name of function %q", fn.Name, first)
}
seen[key] = fn.Name
}
return &FunctionList{fns: slices.Clone(fns)}, nil
}
// checkFunction returns the error for the function at index i, or nil when it is accepted.
func checkFunction(i int, fn Function) error {
switch {
case fn.Name == "":
return fmt.Errorf("dbkit: function %d has an empty name", i)
case fn.Args < -1:
return fmt.Errorf("dbkit: function %q takes Args %d, want -1 for any count or 0 and more", fn.Name, fn.Args)
case fn.Call == nil:
return fmt.Errorf("dbkit: function %q has a nil Call", fn.Name)
}
return nil
}
// asciiLower returns s with its ASCII upper case letters in lower case.
func asciiLower(s string) string {
return strings.Map(func(r rune) rune {
if 'A' <= r && r <= 'Z' {
return r + 'a' - 'A'
}
return r
}, s)
}
// All returns a copy of the functions in the list, or nil for a nil list.
func (l *FunctionList) All() []Function {
if l == nil {
return nil
}
return slices.Clone(l.fns)
}
// CaseFold returns casefold, a deterministic function of one argument that folds text by Unicode simple folding.
func CaseFold() Function {
return Function{Name: "casefold", Args: 1, Deterministic: true, Call: caseFold}
}
// caseFold folds a text or blob argument into a string and returns any other value unchanged.
func caseFold(args []driver.Value) (driver.Value, error) {
if len(args) != 1 {
return nil, fmt.Errorf("dbkit: casefold takes one argument, got %d", len(args))
}
switch v := args[0].(type) {
case string:
return foldString(v), nil
case []byte:
return foldString(string(v)), nil
default:
return v, nil
}
}
// foldString maps every rune of s through foldRune and keeps every byte of invalid UTF-8 as it is.
func foldString(s string) string {
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
if foldRune(r) != r {
return foldFrom(s, i)
}
i += size
}
return s
}
// foldFrom returns s with every rune from byte i on mapped through foldRune and every unchanged byte kept.
func foldFrom(s string, i int) string {
var b strings.Builder
b.Grow(len(s))
b.WriteString(s[:i])
for i < len(s) {
r, size := utf8.DecodeRuneInString(s[i:])
if folded := foldRune(r); folded != r {
b.WriteRune(folded)
} else {
b.WriteString(s[i : i+size])
}
i += size
}
return b.String()
}
// foldRune returns the smallest rune in the simple folding orbit of r, or its lower case when the orbit holds that.
func foldRune(r rune) rune {
smallest := r
for f := unicode.SimpleFold(r); f != r; f = unicode.SimpleFold(f) {
smallest = min(smallest, f)
}
if lower := unicode.ToLower(smallest); inOrbit(r, lower) {
return lower
}
return smallest
}
// inOrbit reports whether c is in the simple folding orbit of r.
func inOrbit(r, c rune) bool {
if c == r {
return true
}
for f := unicode.SimpleFold(r); f != r; f = unicode.SimpleFold(f) {
if f == c {
return true
}
}
return false
}