Skip to content

Commit fb25e4a

Browse files
authored
Native Structs
Native Structs: Added capability to use native structs in get/put/query.
1 parent 0b23eec commit fb25e4a

File tree

19 files changed

+2356
-86
lines changed

19 files changed

+2356
-86
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file.
33

44
The format is based on [Keep a Changelog](http://keepachangelog.com/).
55

6+
## Unreleased
7+
8+
### Added
9+
- Native Structs: Added capability to use native structs in get/put/query. See [Native Structs](https://github.com/oracle/nosql-go-sdk/blob/main/native_structs.md) for details.
10+
11+
### Changed
12+
- Now requires Go 1.18 or higher
13+
14+
615
## 1.4.3 - 2024-07-01
716

817
### Added

go.mod

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
module github.com/oracle/nosql-go-sdk
22

3-
go 1.12
3+
go 1.18
4+
5+
require github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467
46

57
require (
6-
github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467
8+
github.com/davecgh/go-spew v1.1.0 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
github.com/stretchr/objx v0.1.0 // indirect
711
gopkg.in/yaml.v2 v2.4.0 // indirect
812
)

go.sum

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 h1:/pva5wyh0PKq
88
github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
99
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1010
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11-
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
1211
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1312
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
1413
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

internal/test/cloudsim_config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "24.1.11-SNAPSHOT",
2+
"version": "",
33
"tablePrefix": "Go",
44
"reCreateTables": true,
55
"verbose": true,

native_structs.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Oracle NoSQL Database Go SDK: using native structs in put/get/query operations
2+
3+
The Oracle NoSQL go SDK supports directly writing and reading data to/from the NoSQL database using native Go structs. This bypasses having to convert data from structs into NoSQL MapValues and vice versa.
4+
5+
## Directly writing structs using PutRequest
6+
7+
`PutRequest` has an optional field called `StructValue`, which can be used instead of the normal `Value` field which requires a `types.MapValue`. To use this, set the `StructValue` field to a pointer to your native struct:
8+
```
9+
sval := &MyStruct{...}
10+
putReq := &nosqldb.PutRequest{
11+
TableName: tableName,
12+
StructValue: sval,
13+
}
14+
```
15+
The fields in the struct will be mapped to NoSQL row columns based on their field names, and optional annotations given in the struct fields. The annotation `nosql:"nnnn"` can be used to specify a database field name to map the struct field to, similar to the go `encoding/json` methods as documented for the [json Marshal function](https://pkg.go.dev/encoding/json#Marshal). If the struct already uses JSON annotations, those will be used if there are no additional `nosql` annotations:
16+
```
17+
type MyStruct struct {
18+
// Will be written as column "Id"
19+
Id int
20+
// Will be written as column "phone_number"
21+
Phone string `nosql:"phone_number"`
22+
// Json annotations can be used as well: will be written as column "userAge"
23+
Age int `json:"userAge"`
24+
// Timestamp values are supported: this will be written as column "StartDate"
25+
StartDate time.Time
26+
}
27+
```
28+
Complex fields (maps, arrays) are supported, provided that their corresponding NoSQL columns are of the same type.
29+
30+
Note: only exported fields will be written. Any private fields (fields starting with a lowercase letter) will be ignored.
31+
32+
Native structs written to the NoSQL database in this way will be serialized directly to the internal NoSQL byte output stream, bypassing all `types.MapValue` processing. As such, using this method is more efficient, using less memory and CPU cycles.
33+
34+
35+
## Directly reading structs using GetRequest
36+
37+
There are two methods for retrieving data from NoSQL into native structs:
38+
1. Supply an allocated struct with primary key fields populated: on successful return, remaining fields in the struct will be filled in with row data
39+
2. Specify the desired type of struct returned: the SDK will allocate a new struct of this type and populate its fields with row data
40+
41+
### Supplying an existing struct
42+
```
43+
nval := &MyStruct{Id: 10}
44+
getReq := &nosqldb.GetRequest{
45+
TableName: tableName,
46+
StructValue: nval,
47+
}
48+
getRes, err := client.Get(getReq)
49+
if err != nil {
50+
// nval now has all row data filled in
51+
// nval is also available via getRes.StructValue
52+
}
53+
```
54+
55+
### Specifying desired type of allocated struct
56+
```
57+
import "reflect"
58+
59+
60+
nval := &MyStruct{Id: 10}
61+
getReq := &nosqldb.GetRequest{
62+
TableName: tableName,
63+
StructValue: nval,
64+
StructType: reflect.TypeOf((*MyStruct)(nil)).Elem(),
65+
}
66+
getRes, err := client.Get(getReq)
67+
if err != nil {
68+
// nval is left unchanged
69+
// getRes.StructValue contains a newly allocated populated struct
70+
}
71+
```
72+
It is not required to use a native struct for the primary key in the `GetRequest`. A MapValue can be used instead:
73+
```
74+
import "reflect"
75+
76+
key := &types.MapValue{}
77+
key.Put("id", 10)
78+
getReq := &nosqldb.GetRequest{
79+
TableName: tableName,
80+
Key: key,
81+
StructType: reflect.TypeOf((*MyStruct)(nil)).Elem(),
82+
}
83+
getRes, err := client.Get(getReq)
84+
if err != nil {
85+
// getRes.StructValue contains a newly allocated populated struct
86+
}
87+
```
88+
Native structs read from the NoSQL database in this way will be deserialized directly from the internal NoSQL byte input stream, bypassing all `types.MapValue` processing. As such, using this method is more efficient, using less memory and CPU cycles.
89+
90+
91+
## Reading native structs in queries
92+
93+
Query results can be internally converted to native structs by specifying `StructType` in `QueryRequest`, and using `GetStructResults`:
94+
95+
```
96+
import "reflect"
97+
98+
stmt = fmt.Sprintf("SELECT * FROM %s", tableName)
99+
queryReq := &nosqldb.QueryRequest{
100+
Statement: stmt,
101+
StructType: reflect.TypeOf((*MyStruct)(nil)).Elem(),
102+
}
103+
queryRes, err := client.Query(queryReq)
104+
if err != nil {
105+
// retrieve slice of direct structs using GetStructResults:
106+
res, err := queryRes.GetStructResults()
107+
if err != nil {
108+
// res is a slice of MyStruct
109+
}
110+
}
111+
```
112+
Unlike Put and Get, query results will be internally converted from `types.MapValue` to native structs after all query processing is complete.
113+

nosqldb/internal/proto/binary/reader.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,12 @@ func (r *Reader) ReadFieldValue() (types.FieldValue, error) {
342342
}
343343
}
344344

345+
// ReadStructValue deserializes data into a native struct.
346+
// The passed in value must be a pointer to a struct.
347+
func (r *Reader) ReadStructValue(v any) error {
348+
return UnmarshalFromReader(v, r)
349+
}
350+
345351
// ReadByteArray reads byte sequences and returns as a slice of byte or any error encountered.
346352
// The returned bytes could be nil.
347353
func (r *Reader) ReadByteArray() ([]byte, error) {

nosqldb/internal/proto/binary/rw_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"math/rand"
1616
"reflect"
1717
"testing"
18+
"time"
1819

1920
"github.com/oracle/nosql-go-sdk/nosqldb/types"
2021
"github.com/stretchr/testify/suite"
@@ -521,3 +522,67 @@ func genString(n int) string {
521522
}
522523
return string(b)
523524
}
525+
526+
func (suite *ReadWriteTestSuite) TestReadWriteStruct() {
527+
type s1 struct {
528+
A int32 `nosql:"columna"`
529+
B float64 `json:"columnb"`
530+
C string
531+
d bool
532+
E *int64
533+
F []byte
534+
G []int16
535+
}
536+
type s2 struct {
537+
s1
538+
S2A interface{}
539+
S2B time.Time
540+
S2C *float64
541+
S2D *string
542+
}
543+
w := NewWriter()
544+
var eval int64 = 123456789
545+
fval := [8]byte{1, 2, 3, 4, 5, 6, 7, 0}
546+
gval := [5]int16{0, 0, 0, 2345, -1234}
547+
var cfval float64 = 98765.12345
548+
tstr := "another string"
549+
550+
tests := []s1{
551+
{A: 25, B: 1234.56, C: "test string", d: false, E: &eval, F: fval[:], G: gval[:]},
552+
{A: 0, B: 34.56, C: "", d: false, E: nil, F: nil, G: nil},
553+
{A: 12345678, B: -123.45, C: "foobar", d: false},
554+
}
555+
s2tests := []s2{
556+
{tests[0], &eval, time.Now().UTC(), &cfval, nil},
557+
{tests[1], &cfval, time.Now().UTC(), &cfval, &tstr},
558+
{tests[2], &tstr, time.Now().UTC(), &cfval, &tstr},
559+
}
560+
for _, v := range tests {
561+
MarshalToWriter(v, w)
562+
}
563+
for _, v := range s2tests {
564+
MarshalToWriter(v, w)
565+
}
566+
567+
r := NewReader(bytes.NewBuffer(w.Bytes()))
568+
for _, in := range tests {
569+
out := &s1{}
570+
err := UnmarshalFromReader(out, r)
571+
if suite.NoErrorf(err, "UnmarshalFromReader() got error %v", err) {
572+
// TODO: deepEqual
573+
suite.Equalf(in, *out, "UnmarshalFromReader() got unexpected value")
574+
}
575+
}
576+
for _, in := range s2tests {
577+
out := &s2{}
578+
err := UnmarshalFromReader(out, r)
579+
if suite.NoErrorf(err, "UnmarshalFromReader() got error %v", err) {
580+
// TODO: deepEqual
581+
suite.Equalf(in, *out, "UnmarshalFromReader() got unexpected value")
582+
}
583+
}
584+
585+
_, err := r.ReadInt()
586+
suite.Equalf(io.EOF, err, "ReadInt() got unexpected error")
587+
588+
}

0 commit comments

Comments
 (0)