Skip to content

Commit 05548ff

Browse files
authored
Merge pull request #423 from danderson/master
Add support for collation sequences implemented in Go.
2 parents b8d537f + 132eeed commit 05548ff

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

callback.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ func doneTrampoline(ctx *C.sqlite3_context) {
5353
ai.Done(ctx)
5454
}
5555

56+
//export compareTrampoline
57+
func compareTrampoline(handlePtr uintptr, la C.int, a *C.char, lb C.int, b *C.char) C.int {
58+
cmp := lookupHandle(handlePtr).(func(string, string) int)
59+
return C.int(cmp(C.GoStringN(a, la), C.GoStringN(b, lb)))
60+
}
61+
5662
//export commitHookTrampoline
5763
func commitHookTrampoline(handle uintptr) int {
5864
callback := lookupHandle(handle).(func() int)

sqlite3.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ int _sqlite3_create_function(
100100
}
101101
102102
void callbackTrampoline(sqlite3_context*, int, sqlite3_value**);
103+
104+
int compareTrampoline(void*, int, char*, int, char*);
103105
int commitHookTrampoline(void*);
104106
void rollbackHookTrampoline(void*);
105107
void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64);
@@ -326,6 +328,29 @@ func (tx *SQLiteTx) Rollback() error {
326328
return err
327329
}
328330

331+
// RegisterCollation makes a Go function available as a collation.
332+
//
333+
// cmp receives two UTF-8 strings, a and b. The result should be 0 if
334+
// a==b, -1 if a < b, and +1 if a > b.
335+
//
336+
// cmp must always return the same result given the same
337+
// inputs. Additionally, it must have the following properties for all
338+
// strings A, B and C: if A==B then B==A; if A==B and B==C then A==C;
339+
// if A<B then B>A; if A<B and B<C then A<C.
340+
//
341+
// If cmp does not obey these constraints, sqlite3's behavior is
342+
// undefined when the collation is used.
343+
func (c *SQLiteConn) RegisterCollation(name string, cmp func(string, string) int) error {
344+
handle := newHandle(c, cmp)
345+
cname := C.CString(name)
346+
defer C.free(unsafe.Pointer(cname))
347+
rv := C.sqlite3_create_collation(c.db, cname, C.SQLITE_UTF8, unsafe.Pointer(handle), (*[0]byte)(unsafe.Pointer(C.compareTrampoline)))
348+
if rv != C.SQLITE_OK {
349+
return c.lastError()
350+
}
351+
return nil
352+
}
353+
329354
// RegisterCommitHook sets the commit hook for a connection.
330355
//
331356
// If the callback returns non-zero the transaction will become a rollback.

sqlite3_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,127 @@ func TestFunctionRegistration(t *testing.T) {
12321232
}
12331233
}
12341234

1235+
func rot13(r rune) rune {
1236+
switch {
1237+
case r >= 'A' && r <= 'Z':
1238+
return 'A' + (r-'A'+13)%26
1239+
case r >= 'a' && r <= 'z':
1240+
return 'a' + (r-'a'+13)%26
1241+
}
1242+
return r
1243+
}
1244+
1245+
func TestCollationRegistration(t *testing.T) {
1246+
collateRot13 := func(a, b string) int {
1247+
ra, rb := strings.Map(rot13, a), strings.Map(rot13, b)
1248+
return strings.Compare(ra, rb)
1249+
}
1250+
collateRot13Reverse := func(a, b string) int {
1251+
return collateRot13(b, a)
1252+
}
1253+
1254+
sql.Register("sqlite3_CollationRegistration", &SQLiteDriver{
1255+
ConnectHook: func(conn *SQLiteConn) error {
1256+
if err := conn.RegisterCollation("rot13", collateRot13); err != nil {
1257+
return err
1258+
}
1259+
if err := conn.RegisterCollation("rot13reverse", collateRot13Reverse); err != nil {
1260+
return err
1261+
}
1262+
return nil
1263+
},
1264+
})
1265+
1266+
db, err := sql.Open("sqlite3_CollationRegistration", ":memory:")
1267+
if err != nil {
1268+
t.Fatal("Failed to open database:", err)
1269+
}
1270+
defer db.Close()
1271+
1272+
populate := []string{
1273+
`CREATE TABLE test (s TEXT)`,
1274+
`INSERT INTO test VALUES ("aaaa")`,
1275+
`INSERT INTO test VALUES ("ffff")`,
1276+
`INSERT INTO test VALUES ("qqqq")`,
1277+
`INSERT INTO test VALUES ("tttt")`,
1278+
`INSERT INTO test VALUES ("zzzz")`,
1279+
}
1280+
for _, stmt := range populate {
1281+
if _, err := db.Exec(stmt); err != nil {
1282+
t.Fatal("Failed to populate test DB:", err)
1283+
}
1284+
}
1285+
1286+
ops := []struct {
1287+
query string
1288+
want []string
1289+
}{
1290+
{
1291+
"SELECT * FROM test ORDER BY s COLLATE rot13 ASC",
1292+
[]string{
1293+
"qqqq",
1294+
"tttt",
1295+
"zzzz",
1296+
"aaaa",
1297+
"ffff",
1298+
},
1299+
},
1300+
{
1301+
"SELECT * FROM test ORDER BY s COLLATE rot13 DESC",
1302+
[]string{
1303+
"ffff",
1304+
"aaaa",
1305+
"zzzz",
1306+
"tttt",
1307+
"qqqq",
1308+
},
1309+
},
1310+
{
1311+
"SELECT * FROM test ORDER BY s COLLATE rot13reverse ASC",
1312+
[]string{
1313+
"ffff",
1314+
"aaaa",
1315+
"zzzz",
1316+
"tttt",
1317+
"qqqq",
1318+
},
1319+
},
1320+
{
1321+
"SELECT * FROM test ORDER BY s COLLATE rot13reverse DESC",
1322+
[]string{
1323+
"qqqq",
1324+
"tttt",
1325+
"zzzz",
1326+
"aaaa",
1327+
"ffff",
1328+
},
1329+
},
1330+
}
1331+
1332+
for _, op := range ops {
1333+
rows, err := db.Query(op.query)
1334+
if err != nil {
1335+
t.Fatalf("Query %q failed: %s", op.query, err)
1336+
}
1337+
got := []string{}
1338+
defer rows.Close()
1339+
for rows.Next() {
1340+
var s string
1341+
if err = rows.Scan(&s); err != nil {
1342+
t.Fatalf("Reading row for %q: %s", op.query, err)
1343+
}
1344+
got = append(got, s)
1345+
}
1346+
if err = rows.Err(); err != nil {
1347+
t.Fatalf("Reading rows for %q: %s", op.query, err)
1348+
}
1349+
1350+
if !reflect.DeepEqual(got, op.want) {
1351+
t.Fatalf("Unexpected output from %q\ngot:\n%s\n\nwant:\n%s", op.query, strings.Join(got, "\n"), strings.Join(op.want, "\n"))
1352+
}
1353+
}
1354+
}
1355+
12351356
func TestDeclTypes(t *testing.T) {
12361357

12371358
d := SQLiteDriver{}

0 commit comments

Comments
 (0)