Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ func (ctx Context) ResultValue(value Value) {
//
// https://sqlite.org/c3ref/result_blob.html
func (ctx Context) ResultError(err error) {
if err == nil {
return
}
if errors.Is(err, NOMEM) {
ctx.c.wrp.Xsqlite3_result_error_nomem(int32(ctx.handle))
return
Expand Down
97 changes: 97 additions & 0 deletions ext/stats/bitwise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package stats

import (
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/util/sql3util"
)

func newBitAnd() sqlite3.AggregateConstructor {
return sql3util.WindowFromCombiner(1e6, func() *bitAnd {
return &bitAnd{val: -1}
})
}

type bitAnd struct {
val int64
ok bool
}

func (b *bitAnd) Value(ctx sqlite3.Context) {
if b.ok {
ctx.ResultInt64(b.val)
}
}

func (b *bitAnd) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
if arg[0].NumericType() <= sqlite3.FLOAT {
b.val &= arg[0].Int64()
b.ok = true
}
}

func (b *bitAnd) Combine(other *bitAnd) {
if other.ok {
b.val &= other.val
b.ok = true
}
}

func newBitOr() sqlite3.AggregateConstructor {
return sql3util.WindowFromCombiner(1e6, func() *bitOr {
return &bitOr{}
})
}

type bitOr struct {
val int64
ok bool
}

func (b *bitOr) Value(ctx sqlite3.Context) {
if b.ok {
ctx.ResultInt64(b.val)
}
}

func (b *bitOr) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
if arg[0].NumericType() <= sqlite3.FLOAT {
b.val |= arg[0].Int64()
b.ok = true
}
}

func (b *bitOr) Combine(other *bitOr) {
if other.ok {
b.val |= other.val
b.ok = true
}
}

func newBitXor() sqlite3.AggregateFunction {
return &bitXor{}
}

type bitXor struct {
val int64
count int
}

func (b *bitXor) Value(ctx sqlite3.Context) {
if b.count > 0 {
ctx.ResultInt64(b.val)
}
}

func (b *bitXor) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
if arg[0].NumericType() <= sqlite3.FLOAT {
b.val ^= arg[0].Int64()
b.count++
}
}

func (b *bitXor) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
if arg[0].NumericType() <= sqlite3.FLOAT {
b.val ^= arg[0].Int64()
b.count--
}
}
106 changes: 106 additions & 0 deletions ext/stats/bitwise_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package stats_test

import (
"testing"

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

func TestRegister_bitwise(t *testing.T) {
t.Parallel()

db, err := sqlite3.OpenContext(testcfg.Context(t), ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()

err = db.Exec(`CREATE TABLE data (x)`)
if err != nil {
t.Fatal(err)
}

stmt, _, err := db.Prepare(`SELECT bit_and(x), bit_or(x), bit_xor(x) FROM data`)
if err != nil {
t.Fatal(err)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
for i := range 3 {
if got := stmt.ColumnType(i); got != sqlite3.NULL {
t.Errorf("got %v, want NULL", got)
}
}
}
stmt.Close()

err = db.Exec(`INSERT INTO data (x) VALUES (3), (6.0), ('12'), (10), (NULL), ('abc')`)
if err != nil {
t.Fatal(err)
}

stmt, _, err = db.Prepare(`
SELECT
bit_and(x),
bit_and(x | 1),
bit_or(x),
bit_xor(x)
FROM data`)
if err != nil {
t.Fatal(err)
}
if !stmt.Step() {
t.Fatal(stmt.Err())
} else {
if got := stmt.ColumnInt64(0); got != 0 {
t.Errorf("got %v, want 0", got)
}
if got := stmt.ColumnInt64(1); got != 1 {
t.Errorf("got %v, want 1", got)
}
if got := stmt.ColumnInt64(2); got != 15 {
t.Errorf("got %v, want 15", got)
}
if got := stmt.ColumnInt64(3); got != 3 {
t.Errorf("got %v, want 3", got)
}
}
stmt.Close()

stmt, _, err = db.Prepare(`
SELECT
bit_and(x) OVER (ROWS 1 PRECEDING),
bit_or(x) OVER (ROWS 1 PRECEDING),
bit_xor(x) OVER (ROWS 1 PRECEDING)
FROM data`)
if err != nil {
t.Fatal(err)
}

wantAnd := [...]int64{3, 2, 4, 8, 10, 0}
wantOr := [...]int64{3, 7, 14, 14, 10, 0}
wantXor := [...]int64{3, 5, 10, 6, 10, 0}
for i := 0; stmt.Step(); i++ {
if got := stmt.ColumnInt64(0); got != wantAnd[i] {
t.Errorf("row %d: got %v, want %v", i, got, wantAnd[i])
}
if got := stmt.ColumnInt64(1); got != wantOr[i] {
t.Errorf("row %d: got %v, want %v", i, got, wantOr[i])
}
if got := stmt.ColumnInt64(2); got != wantXor[i] {
t.Errorf("row %d: got %v, want %v", i, got, wantXor[i])
}
wantType := sqlite3.INTEGER
if i == 5 {
wantType = sqlite3.NULL
}
for c := range 3 {
if got := stmt.ColumnType(c); got != wantType {
t.Errorf("row %d col %d: got %v, want %v", i, c, got, wantType)
}
}
}
stmt.Close()
}
10 changes: 4 additions & 6 deletions ext/stats/boolean.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,19 @@ func (b *boolean) Value(ctx sqlite3.Context) {
}

func (b *boolean) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
if a.Bool() {
if arg[0].Bool() {
b.count++
}
if a.Type() != sqlite3.NULL {
if arg[0].Type() != sqlite3.NULL {
b.total++
}
}

func (b *boolean) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
a := arg[0]
if a.Bool() {
if arg[0].Bool() {
b.count--
}
if a.Type() != sqlite3.NULL {
if arg[0].Type() != sqlite3.NULL {
b.total--
}
}
13 changes: 0 additions & 13 deletions ext/stats/stats

This file was deleted.

6 changes: 6 additions & 0 deletions ext/stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
// - mode: most frequent value
// - every: boolean and
// - some: boolean or
// - bit_and: bitwise and
// - bit_or: bitwise or
// - bit_xor: bitwise xor
//
// These join the [Built-in Aggregate Functions]:
// - count: count rows/values
Expand Down Expand Up @@ -91,6 +94,9 @@ func Register(db *sqlite3.Conn) error {
db.CreateWindowFunction("percentile_disc", 2, order, newPercentile(percentile_disc)),
db.CreateWindowFunction("every", 1, flags, newBoolean(every)),
db.CreateWindowFunction("some", 1, flags, newBoolean(some)),
db.CreateWindowFunction("bit_and", 1, flags, newBitAnd()),
db.CreateWindowFunction("bit_or", 1, flags, newBitOr()),
db.CreateWindowFunction("bit_xor", 1, flags, newBitXor),
db.CreateWindowFunction("mode", 1, order, newMode),
db.CreateFunction("cbrt", 1, flags, cbrt),
db.CreateFunction("cot", 1, flags, cot))
Expand Down
2 changes: 1 addition & 1 deletion ext/unicode/unicode.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func like(ctx sqlite3.Context, arg ...sqlite3.Value) {
b := arg[2].RawText()
escape, size = utf8.DecodeRune(b)
if size != len(b) {
ctx.ResultError(errutil.ErrorString("ESCAPE expression must be a single character"))
ctx.ResultError(errutil.ErrorString("unicode: ESCAPE expression must be a single character"))
return
}
}
Expand Down
2 changes: 1 addition & 1 deletion func.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func (e env) Xgo_inverse(pCtx, pAgg, nArg, pArg int32) {
fn, ok := db.wrp.GetHandle(ptr_t(pAgg)).(WindowFunction)
if !ok {
Context{db, ptr_t(pCtx)}.ResultError(
errutil.ErrorString("may not be used as a window function"))
errutil.ErrorString("sqlite3: may not be used as a window function"))
return // notest
}
fn.Inverse(Context{db, ptr_t(pCtx)}, *args...)
Expand Down
Loading
Loading