diff --git a/ext/fileio/fileio.go b/ext/fileio/fileio.go index 90c97272..dcb375ad 100644 --- a/ext/fileio/fileio.go +++ b/ext/fileio/fileio.go @@ -22,7 +22,7 @@ func Register(db *sqlite3.Conn) { // and the table-valued function fsdir; // fsys will be used to read files and list directories. func RegisterFS(db *sqlite3.Conn, fsys fs.FS) { - db.CreateFunction("lsmode", 1, 0, lsmode) + db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode) db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)) if fsys == nil { db.CreateFunction("writefile", -1, sqlite3.DIRECTONLY, writefile) diff --git a/ext/zorder/zorder.go b/ext/zorder/zorder.go new file mode 100644 index 00000000..8ab93724 --- /dev/null +++ b/ext/zorder/zorder.go @@ -0,0 +1,58 @@ +// Package zorder provides functions for z-order transformations. +// +// https://sqlite.org/src/doc/tip/ext/misc/zorder.c +package zorder + +import ( + "github.com/ncruces/go-sqlite3" + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Register registers the zorder and unzorder SQL functions. +func Register(db *sqlite3.Conn) { + flags := sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS + db.CreateFunction("zorder", -1, flags, zorder) + db.CreateFunction("unzorder", 3, flags, unzorder) +} + +func zorder(ctx sqlite3.Context, arg ...sqlite3.Value) { + var x [63]int64 + for i := range arg { + x[i] = arg[i].Int64() + } + if len(arg) > len(x) { + ctx.ResultError(util.ErrorString("zorder: too many parameters")) + return + } + + var z int64 + if len(arg) > 0 { + for i := 0; i < 63; i++ { + j := i % len(arg) + z |= (x[j] & 1) << i + x[j] >>= 1 + } + } + + for i := range arg { + if x[i] != 0 { + ctx.ResultError(util.ErrorString("zorder: parameter too large")) + return + } + } + ctx.ResultInt64(z) +} + +func unzorder(ctx sqlite3.Context, arg ...sqlite3.Value) { + z := arg[0].Int64() + n := arg[1].Int64() + i := arg[2].Int64() + + var k int + var x int64 + for j := i; j < 63; j += n { + x |= ((z >> j) & 1) << k + k++ + } + ctx.ResultInt64(x) +} diff --git a/ext/zorder/zorder_test.go b/ext/zorder/zorder_test.go new file mode 100644 index 00000000..a2699ce8 --- /dev/null +++ b/ext/zorder/zorder_test.go @@ -0,0 +1,106 @@ +package zorder_test + +import ( + "testing" + + "github.com/ncruces/go-sqlite3" + "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" + "github.com/ncruces/go-sqlite3/ext/zorder" +) + +func TestRegister_zorder(t *testing.T) { + t.Parallel() + + db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error { + zorder.Register(c) + return nil + }) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + var got int64 + err = db.QueryRow(`SELECT zorder(2, 3)`).Scan(&got) + if err != nil { + t.Fatal(err) + } + if got != 14 { + t.Errorf("got %d, want 14", got) + } + + err = db.QueryRow(`SELECT zorder(4, 5)`).Scan(&got) + if err != nil { + t.Fatal(err) + } + if got != 50 { + t.Errorf("got %d, want 14", got) + } + + var check bool + err = db.QueryRow(`SELECT zorder(3, 4) BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check) + if err != nil { + t.Fatal(err) + } + if !check { + t.Error("want true") + } + + err = db.QueryRow(`SELECT zorder(2, 2) NOT BETWEEN zorder(2, 3) AND zorder(4, 5)`).Scan(&check) + if err != nil { + t.Fatal(err) + } + if !check { + t.Error("want true") + } +} + +func TestRegister_unzorder(t *testing.T) { + t.Parallel() + + db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error { + zorder.Register(c) + return nil + }) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + var got int64 + err = db.QueryRow(`SELECT unzorder(zorder(3, 4), 2, 0)`).Scan(&got) + if err != nil { + t.Fatal(err) + } + if got != 3 { + t.Errorf("got %d, want 3", got) + } + + err = db.QueryRow(`SELECT unzorder(zorder(3, 4), 2, 1)`).Scan(&got) + if err != nil { + t.Fatal(err) + } + if got != 4 { + t.Errorf("got %d, want 4", got) + } +} + +func TestRegister_error(t *testing.T) { + t.Parallel() + + db, err := driver.Open(":memory:", func(c *sqlite3.Conn) error { + zorder.Register(c) + return nil + }) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + var got int64 + err = db.QueryRow(`SELECT zorder(1, 2, 3, 100000)`).Scan(&got) + if err == nil { + t.Error("want error") + } +}