Skip to content

Commit eec45ea

Browse files
committed
Towards JSON.
1 parent f6d77f3 commit eec45ea

File tree

12 files changed

+272
-10
lines changed

12 files changed

+272
-10
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Performance is tested by running
8686
- [x] nested transactions
8787
- [x] incremental BLOB I/O
8888
- [x] online backup
89+
- [ ] JSON support
8990
- [ ] session extension
9091
- [ ] custom VFSes
9192
- [x] custom VFS API

context.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sqlite3
22

33
import (
4+
"encoding/json"
45
"errors"
56
"math"
67
"time"
@@ -138,6 +139,31 @@ func (c Context) resultRFC3339Nano(value time.Time) {
138139
uint64(c.api.destructor), _UTF8)
139140
}
140141

142+
// ResultJSON sets the result of the function to the JSON encoding of value.
143+
//
144+
// https://www.sqlite.org/c3ref/result_blob.html
145+
func (c Context) ResultJSON(value any) {
146+
data, err := json.Marshal(value)
147+
if err != nil {
148+
c.ResultError(err)
149+
}
150+
ptr := c.newBytes(data)
151+
c.call(c.api.resultText,
152+
uint64(c.handle), uint64(ptr), uint64(len(data)),
153+
uint64(c.api.destructor))
154+
}
155+
156+
// ResultValue sets the result of the function a copy of [Value].
157+
//
158+
// https://www.sqlite.org/c3ref/result_blob.html
159+
func (c Context) ResultValue(value Value) {
160+
if value.sqlite != c.sqlite {
161+
c.ResultError(MISUSE)
162+
}
163+
c.call(c.api.resultValue,
164+
uint64(c.handle), uint64(value.handle))
165+
}
166+
141167
// ResultError sets the result of the function an error.
142168
//
143169
// https://www.sqlite.org/c3ref/result_blob.html

embed/exports.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ sqlite3_result_double
6868
sqlite3_result_text64
6969
sqlite3_result_blob64
7070
sqlite3_result_zeroblob64
71+
sqlite3_result_value
7172
sqlite3_result_error
7273
sqlite3_result_error_code
7374
sqlite3_result_error_nomem

embed/sqlite3.wasm

24 Bytes
Binary file not shown.

gormlite/ddlmod.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ func (d *ddl) removeConstraint(name string) bool {
235235
return false
236236
}
237237

238+
//lint:ignore U1000 ignore unused code.
238239
func (d *ddl) hasConstraint(name string) bool {
239240
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
240241

internal/util/mem.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ func View(mod api.Module, ptr uint32, size uint64) []byte {
1414
if size > math.MaxUint32 {
1515
panic(RangeErr)
1616
}
17+
if size == 0 {
18+
return nil
19+
}
1720
buf, ok := mod.Memory().Read(ptr, uint32(size))
1821
if !ok {
1922
panic(RangeErr)

sqlite.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func instantiateSQLite() (sqlt *sqlite, err error) {
170170
resultText: getFun("sqlite3_result_text64"),
171171
resultBlob: getFun("sqlite3_result_blob64"),
172172
resultZeroBlob: getFun("sqlite3_result_zeroblob64"),
173+
resultValue: getFun("sqlite3_result_value"),
173174
resultError: getFun("sqlite3_result_error"),
174175
resultErrorCode: getFun("sqlite3_result_error_code"),
175176
resultErrorMem: getFun("sqlite3_result_error_nomem"),
@@ -200,13 +201,15 @@ func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
200201
err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_STRING)
201202
}
202203

203-
if r := sqlt.call(sqlt.api.errmsg, uint64(handle)); r != 0 {
204-
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_STRING)
205-
}
204+
if handle != 0 {
205+
if r := sqlt.call(sqlt.api.errmsg, uint64(handle)); r != 0 {
206+
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_STRING)
207+
}
206208

207-
if sql != nil {
208-
if r := sqlt.call(sqlt.api.erroff, uint64(handle)); r != math.MaxUint32 {
209-
err.sql = sql[0][r:]
209+
if sql != nil {
210+
if r := sqlt.call(sqlt.api.erroff, uint64(handle)); r != math.MaxUint32 {
211+
err.sql = sql[0][r:]
212+
}
210213
}
211214
}
212215

@@ -245,7 +248,7 @@ func (sqlt *sqlite) new(size uint64) uint32 {
245248
}
246249

247250
func (sqlt *sqlite) newBytes(b []byte) uint32 {
248-
if b == nil {
251+
if (*[0]byte)(b) == nil {
249252
return 0
250253
}
251254
ptr := sqlt.new(uint64(len(b)))
@@ -386,6 +389,7 @@ type sqliteAPI struct {
386389
resultText api.Function
387390
resultBlob api.Function
388391
resultZeroBlob api.Function
392+
resultValue api.Function
389393
resultError api.Function
390394
resultErrorCode api.Function
391395
resultErrorMem api.Function

sqlite_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ func Test_sqlite_newBytes(t *testing.T) {
132132
if got := util.View(sqlite.mod, ptr, uint64(len(want))); !bytes.Equal(got, want) {
133133
t.Errorf("got %q, want %q", got, want)
134134
}
135+
136+
ptr = sqlite.newBytes(buf[:0])
137+
if ptr == 0 {
138+
t.Fatal("got nullptr, want a pointer")
139+
}
140+
141+
if got := util.View(sqlite.mod, ptr, 0); got != nil {
142+
t.Errorf("got %q, want nil", got)
143+
}
135144
}
136145

137146
func Test_sqlite_newString(t *testing.T) {

stmt.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package sqlite3
22

33
import (
4+
"encoding/json"
45
"math"
6+
"strconv"
57
"time"
68

79
"github.com/ncruces/go-sqlite3/internal/util"
@@ -248,6 +250,23 @@ func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error {
248250
return s.c.error(r)
249251
}
250252

253+
// BindJSON binds the JSON encoding of value to the prepared statement.
254+
// The leftmost SQL parameter has an index of 1.
255+
//
256+
// https://www.sqlite.org/c3ref/bind_blob.html
257+
func (s *Stmt) BindJSON(param int, value any) error {
258+
data, err := json.Marshal(value)
259+
if err != nil {
260+
return err
261+
}
262+
ptr := s.c.newBytes(data)
263+
r := s.c.call(s.c.api.bindText,
264+
uint64(s.handle), uint64(param),
265+
uint64(ptr), uint64(len(data)),
266+
uint64(s.c.api.destructor))
267+
return s.c.error(r)
268+
}
269+
251270
// ColumnCount returns the number of columns in a result set.
252271
//
253272
// https://www.sqlite.org/c3ref/column_count.html
@@ -402,6 +421,28 @@ func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte {
402421
return util.View(s.c.mod, ptr, r)
403422
}
404423

424+
// ColumnJSON parses the JSON-encoded value of the result column
425+
// and stores it in the value pointed to by ptr.
426+
// The leftmost column of the result set has the index 0.
427+
//
428+
// https://www.sqlite.org/c3ref/column_blob.html
429+
func (s *Stmt) ColumnJSON(col int, ptr any) error {
430+
var data []byte
431+
switch s.ColumnType(col) {
432+
case NULL:
433+
data = []byte("null")
434+
case TEXT, BLOB:
435+
data = s.ColumnRawBlob(col)
436+
case INTEGER:
437+
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
438+
case FLOAT:
439+
data = strconv.AppendFloat(nil, s.ColumnFloat(col), 'g', -1, 64)
440+
default:
441+
panic(util.AssertErr())
442+
}
443+
return json.Unmarshal(data, ptr)
444+
}
445+
405446
// Return true if stmt is an empty SQL statement.
406447
// This is used as an optimization.
407448
// It's OK to always return false here.

tests/func_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,25 @@ func TestCreateFunction(t *testing.T) {
3636
case 7:
3737
ctx.ResultTime(arg.Time(sqlite3.TimeFormatUnix), sqlite3.TimeFormatDefault)
3838
case 8:
39-
ctx.ResultNull()
39+
var v any
40+
if err := arg.JSON(&v); err != nil {
41+
ctx.ResultError(err)
42+
} else {
43+
ctx.ResultJSON(v)
44+
}
4045
case 9:
46+
ctx.ResultValue(arg)
47+
case 10:
48+
ctx.ResultNull()
49+
case 11:
4150
ctx.ResultError(sqlite3.FULL)
4251
}
4352
})
4453
if err != nil {
4554
t.Fatal(err)
4655
}
4756

48-
stmt, _, err := db.Prepare(`SELECT test(value) FROM generate_series(0, 9)`)
57+
stmt, _, err := db.Prepare(`SELECT test(value) FROM generate_series(0)`)
4958
if err != nil {
5059
t.Error(err)
5160
}
@@ -123,6 +132,27 @@ func TestCreateFunction(t *testing.T) {
123132
}
124133
}
125134

135+
if stmt.Step() {
136+
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
137+
t.Errorf("got %v, want TEXT", got)
138+
}
139+
var got int
140+
if err := stmt.ColumnJSON(0, &got); err != nil {
141+
t.Error(err)
142+
} else if got != 8 {
143+
t.Errorf("got %v, want 8", got)
144+
}
145+
}
146+
147+
if stmt.Step() {
148+
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
149+
t.Errorf("got %v, want INTEGER", got)
150+
}
151+
if got := stmt.ColumnInt64(0); got != 9 {
152+
t.Errorf("got %v, want 9", got)
153+
}
154+
}
155+
126156
if stmt.Step() {
127157
if got := stmt.ColumnType(0); got != sqlite3.NULL {
128158
t.Errorf("got %v, want NULL", got)

0 commit comments

Comments
 (0)