Skip to content

Commit 4e26107

Browse files
committed
Support for new, flexible wire protocol (V4):
The previous protocol is still supported for communication with servers that do not yet support V4. The version negotation is internal and automatic; however, use of V4 features will fail at runtime when attempted with an older server. Failure may be an empty or undefined result or an exception if the request cannot be serviced at all. The following new features or interfaces depend on the new protocol version: - added Durability to QueryRequest for queries that modify data - added pagination information to TableUsageResult and TableUsageRequest - added shard percent usage information to TableUsageResult - added IndexInfo.FieldTypes to return the type information on an index on a JSON field - added the ability to ask for and receive the schema of a query using * PrepareRequest.GetQuerySchema * PreparedStatement.GetQuerySchema - Cloud only: added use of ETags, DefinedTags and FreeFormTags in TableRequest and TableResult
1 parent e779c76 commit 4e26107

25 files changed

+4833
-1630
lines changed

CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,25 @@ 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
6+
## 1.4.0 - 2022-12-15
77

88
### Added
9+
Support for new, flexible wire protocol (V4):
10+
11+
The previous protocol is still supported for communication with servers that do not yet support V4. The
12+
version negotation is internal and automatic; however, use of V4 features will fail
13+
at runtime when attempted with an older server. Failure may be an empty or
14+
undefined result or an exception if the request cannot be serviced at all. The following
15+
new features or interfaces depend on the new protocol version:
16+
- added Durability to QueryRequest for queries that modify data
17+
- added pagination information to TableUsageResult and TableUsageRequest
18+
- added shard percent usage information to TableUsageResult
19+
- added IndexInfo.FieldTypes to return the type information on an index on a JSON field
20+
- added the ability to ask for and receive the schema of a query using
21+
* PrepareRequest.GetQuerySchema
22+
* PreparedStatement.GetQuerySchema
23+
- Cloud only: added use of ETags, DefinedTags and FreeFormTags in TableRequest and TableResult
24+
925
- Latest Oracle Cloud Infrastructure regions and region codes: SGU, IFP, GCN
1026

1127
## 1.3.2 - 2022-10-18

Makefile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,19 @@ testcases ?=
2626
options ?=
2727
examples := basic delete index
2828

29-
GOTEST := $(GOENV) $(GO) test -timeout 20m -count 1 -run "$(testcases)" -v $(options)
29+
# Enable to get code coverage from tests
30+
# afterwards, run go tool cover -html=nosqldb/cover.out
31+
#COVER := -coverprofile cover.out
32+
33+
GOTEST := $(GOENV) $(GO) test $(COVER) -timeout 20m -count 1 -run "$(testcases)" -v $(options)
3034

3135
.PHONY: all build test cloudsim-test onprem-test clean lint build-examples release $(examples) help
3236

3337
all: build
3438

3539
# compile all packages
3640
build:
37-
cd $(SRC) && $(GOENV) $(GO) build -v ./...
41+
cd $(SRC) && $(GOENV) $(GO) build -gcflags="-e" -v ./...
3842

3943
# run tests
4044
test:
@@ -54,7 +58,7 @@ clean:
5458

5559
# lint check
5660
lint:
57-
cd $(SRC) && golint -set_exit_status -min_confidence 0.3 ./...
61+
cd $(SRC) && $(GO) vet
5862

5963
# compile examples
6064
build-examples: $(examples)

internal/test/cloudsim_config.json

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{
2-
"version": "1.3.2",
3-
"tablePrefix": "Go",
4-
"reCreateTables": true,
5-
"dropTablesOnTearDown": true,
6-
"clientConfig": {
7-
"mode": "cloudsim",
8-
"endpoint": "http://localhost:8080"
9-
}
10-
}
2+
"version": "21.2.19",
3+
"tablePrefix": "Go",
4+
"reCreateTables": true,
5+
"verbose": true,
6+
"dropTablesOnTearDown": true,
7+
"clientConfig": {
8+
"mode": "cloudsim",
9+
"endpoint": "http://localhost:8080",
10+
"rateLimitingEnabled": true
11+
}
12+
}

internal/test/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ type Config struct {
5050

5151
// For extended testing
5252
RunExtended bool `json:"runExtended"`
53+
54+
// For testing
55+
SerialVersion int16 `json:"serialVersion"`
5356
}
5457

5558
// newConfig creates a test configuration object from the specified JSON file.
@@ -148,6 +151,14 @@ func createClient(cfg *Config) (*nosqldb.Client, error) {
148151
return nil, err
149152
}
150153

154+
// if specified, force a specific serial version
155+
if cfg.SerialVersion != 0 {
156+
if cfg.Verbose {
157+
fmt.Printf("Setting client serial version to %d\n", cfg.SerialVersion)
158+
}
159+
client.SetSerialVersion(cfg.SerialVersion)
160+
}
161+
151162
// this will set the protocol serial version according to the connected server.
152163
// ignore errors here, they may be expected.
153164
client.VerifyConnection()

nosqldb/bad_protocol_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ func (suite *BadProtocolTestSuite) SetupSuite() {
5353
suite.bpTestClient, err = nosqldb.NewClient(suite.Client.Config)
5454
suite.Require().NoErrorf(err, "failed to create a client, got error %v", err)
5555

56+
// Currently this test requires V3 or lower
57+
suite.bpTestClient.SetSerialVersion(3)
58+
5659
// this will set the serial protocol version. Ignore errors from it.
5760
suite.bpTestClient.VerifyConnection()
5861

@@ -111,7 +114,7 @@ func (suite *BadProtocolTestSuite) createTableAndIndex() {
111114

112115
// processTestResponse is a custom handleResponse function for the Client.
113116
// It checks error code from the response, does not parse the response content.
114-
func processTestResponse(httpResp *http.Response, req nosqldb.Request) (nosqldb.Result, error) {
117+
func processTestResponse(httpResp *http.Response, req nosqldb.Request, serialVerUsed int16) (nosqldb.Result, error) {
115118
data, err := ioutil.ReadAll(httpResp.Body)
116119
httpResp.Body.Close()
117120
if err != nil {
@@ -765,7 +768,7 @@ func (suite *BadProtocolTestSuite) TestBadWriteMultipleRequest() {
765768
suite.wr.Reset()
766769
suite.wr.WriteOpCode(v)
767770
copy(data[off:], suite.wr.Bytes())
768-
suite.doBadProtoTest(req, data, desc, nosqlerr.BadProtocolMessage)
771+
suite.doBadProtoTest2(req, data, desc, nosqlerr.BadProtocolMessage, nosqlerr.IllegalArgument)
769772
}
770773

771774
}

nosqldb/client.go

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"io/ioutil"
1616
"net/http"
1717
"net/url"
18+
//"os"
1819
"reflect"
1920
"strconv"
2021
"strings"
@@ -68,7 +69,7 @@ type Client struct {
6869
// handleResponse specifies a function that is used to handle the response
6970
// returned from server.
7071
// This is used internally by tests for customizing response processing.
71-
handleResponse func(httpResp *http.Response, req Request) (Result, error)
72+
handleResponse func(httpResp *http.Response, req Request, serialVerUsed int16) (Result, error)
7273

7374
// isCloud represents whether the client connects to the cloud service or
7475
// cloud simulator.
@@ -1103,7 +1104,7 @@ func (c *Client) doExecute(ctx context.Context, req Request, data []byte, serial
11031104
continue
11041105
}
11051106

1106-
result, err = c.handleResponse(httpResp, req)
1107+
result, err = c.handleResponse(httpResp, req, serialVerUsed)
11071108
// Cancel request context after response body has been read.
11081109
reqCancel()
11091110
if err != nil {
@@ -1348,14 +1349,20 @@ func (c *Client) signHTTPRequest(httpReq *http.Request) error {
13481349
// will be sent to the server. The serial version is always written followed by
13491350
// the actual request payload.
13501351
func (c *Client) serializeRequest(req Request) (data []byte, serialVerUsed int16, err error) {
1351-
wr := binary.NewWriter()
13521352
serialVerUsed = c.serialVersion
1353+
wr := binary.NewWriter()
13531354
if _, err = wr.WriteSerialVersion(serialVerUsed); err != nil {
13541355
return nil, 0, err
13551356
}
13561357

1357-
if err = req.serialize(wr, serialVerUsed); err != nil {
1358-
return nil, 0, err
1358+
if serialVerUsed >= 4 {
1359+
if err = req.serialize(wr, serialVerUsed); err != nil {
1360+
return nil, 0, err
1361+
}
1362+
} else {
1363+
if err = req.serializeV3(wr, serialVerUsed); err != nil {
1364+
return nil, 0, err
1365+
}
13591366
}
13601367

13611368
return wr.Bytes(), serialVerUsed, nil
@@ -1366,7 +1373,7 @@ func (c *Client) serializeRequest(req Request) (data []byte, serialVerUsed int16
13661373
// If the http response status code is 200, this method reads in response
13671374
// content and parses them as an appropriate result suitable for the request.
13681375
// Otherwise, it returns the http error.
1369-
func (c *Client) processResponse(httpResp *http.Response, req Request) (Result, error) {
1376+
func (c *Client) processResponse(httpResp *http.Response, req Request, serialVerUsed int16) (Result, error) {
13701377
data, err := ioutil.ReadAll(httpResp.Body)
13711378
httpResp.Body.Close()
13721379
if err != nil {
@@ -1375,31 +1382,41 @@ func (c *Client) processResponse(httpResp *http.Response, req Request) (Result,
13751382

13761383
if httpResp.StatusCode == http.StatusOK {
13771384
c.setSessionCookie(httpResp.Header)
1378-
return c.processOKResponse(data, req)
1385+
return c.processOKResponse(data, req, serialVerUsed)
13791386
}
13801387

13811388
return nil, c.processNotOKResponse(data, httpResp.StatusCode)
13821389
}
13831390

1384-
func (c *Client) processOKResponse(data []byte, req Request) (Result, error) {
1391+
func (c *Client) processOKResponse(data []byte, req Request, serialVerUsed int16) (res Result, err error) {
13851392
buf := bytes.NewBuffer(data)
13861393
rd := binary.NewReader(buf)
1387-
code, err := rd.ReadByte()
1394+
1395+
var code int
1396+
if serialVerUsed >= 4 {
1397+
if res, code, err = req.deserialize(rd, serialVerUsed); err != nil {
1398+
return nil, wrapResponseErrors(int(code), err.Error())
1399+
}
1400+
if queryReq, ok := req.(*QueryRequest); ok && !queryReq.isSimpleQuery() {
1401+
queryReq.driver.client = c
1402+
}
1403+
return res, nil
1404+
}
1405+
1406+
// V3
1407+
bcode, err := rd.ReadByte()
13881408
if err != nil {
13891409
return nil, err
13901410
}
1391-
1411+
code = int(bcode)
13921412
// A zero byte represents the operation succeeded.
13931413
if code == 0 {
1394-
res, err := req.deserialize(rd, c.serialVersion)
1395-
if err != nil {
1414+
if res, err = req.deserializeV3(rd, serialVerUsed); err != nil {
13961415
return nil, err
13971416
}
1398-
13991417
if queryReq, ok := req.(*QueryRequest); ok && !queryReq.isSimpleQuery() {
14001418
queryReq.driver.client = c
14011419
}
1402-
14031420
return res, nil
14041421
}
14051422

@@ -1548,6 +1565,7 @@ func (c *Client) decrementSerialVersion(serialVerUsed int16) bool {
15481565
}
15491566
if c.serialVersion > 2 {
15501567
c.serialVersion--
1568+
c.logger.Fine("Decremented serial version to %d\n", c.serialVersion)
15511569
return true
15521570
}
15531571
return false
@@ -1558,6 +1576,11 @@ func (c *Client) GetSerialVersion() int16 {
15581576
return c.serialVersion
15591577
}
15601578

1579+
// SetSerialVersion is used for tests. Do not use this in regular client code.
1580+
func (c *Client) SetSerialVersion(sVer int16) {
1581+
c.serialVersion = sVer
1582+
}
1583+
15611584
func (c *Client) oneTimeMessage(msg string) {
15621585
if _, ok := c.oneTimeMessages[msg]; ok == false {
15631586
c.oneTimeMessages[msg] = struct{}{}

nosqldb/client_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import (
2626
func TestExecuteErrorHandling(t *testing.T) {
2727
client, err := newMockClient()
2828
require.NoErrorf(t, err, "failed to create client, got error %v.", err)
29+
30+
// This test is very specific to V2/3 protocol.
31+
// TODO: V4 protocol error tests
32+
client.SetSerialVersion(3)
33+
2934
// GetRequest is a retryable request.
3035
getReq := &GetRequest{
3136
TableName: "T1",

nosqldb/export_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
// This file exports functions/methods that are used in test codes.
1616

17-
type HandleResponse func(httpResp *http.Response, req Request) (Result, error)
17+
type HandleResponse func(httpResp *http.Response, req Request, serialVerUsed int16) (Result, error)
1818

1919
func (c *Client) SetResponseHandler(fn HandleResponse) {
2020
c.handleResponse = fn

nosqldb/internal/proto/binary/reader.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package binary
99

1010
import (
11+
"bytes"
1112
"encoding/binary"
1213
"errors"
1314
"fmt"
@@ -28,25 +29,25 @@ import (
2829
// Reader implements the io.Reader and io.ByteReader interfaces.
2930
type Reader struct {
3031
// The underlying io.Reader.
31-
rd io.Reader
32+
rd *bytes.Buffer
3233

3334
// A buffer that holds the bytes for decoding.
3435
buf []byte
3536
}
3637

3738
// NewReader creates a reader for the binary protocol.
38-
// If the provided io.Reader is already a binary protocol Reader, it returns
39-
// the provided one without creating a new Reader.
40-
func NewReader(r io.Reader) *Reader {
41-
if r, ok := r.(*Reader); ok {
42-
return r
43-
}
39+
func NewReader(r *bytes.Buffer) *Reader {
4440
return &Reader{
4541
rd: r,
4642
buf: make([]byte, 64, 256),
4743
}
4844
}
4945

46+
// GetBuffer returns the underlying bytes Buffer.
47+
func (r *Reader) GetBuffer() *bytes.Buffer {
48+
return r.rd
49+
}
50+
5051
// Read reads up to len(p) bytes into p.
5152
// It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.
5253
func (r *Reader) Read(p []byte) (n int, err error) {
@@ -181,6 +182,16 @@ func (r *Reader) ReadString() (*string, error) {
181182
return &s, nil
182183
}
183184

185+
// ReadNonNilString reads a string. If there is an error, it will return
186+
// an empty string and the error.
187+
func (r *Reader) ReadNonNilString() (string, error) {
188+
str, err := r.ReadString()
189+
if str == nil || err != nil {
190+
return "", err
191+
}
192+
return *str, nil
193+
}
194+
184195
// ReadVersion reads byte sequences and decodes as a types.Version.
185196
func (r *Reader) ReadVersion() (types.Version, error) {
186197
return r.ReadByteArray()

0 commit comments

Comments
 (0)