Skip to content
Permalink
Browse files
feat(spanner): add support for JSON data type (#4104)
* feat(spanner): add json support
* Add NullJSON.
* Add array and custom types support.
* Add tests for json encoding and decoding for NullJSON.
* Add integration test.
* Only use NullJSON for encoding.
* Update integration test.
* Only decode Cloud Spanner JSON to NullJSON type.
* Support decoding ARRAY<JSON> to NullJSON.
  • Loading branch information
hengfengli committed Aug 24, 2021
1 parent bc966a3 commit ade8ab111315d84fa140ddde020387a78668dfa4
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 7 deletions.
@@ -18,6 +18,7 @@ package spanner

import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
@@ -113,6 +114,8 @@ var (
DateArray ARRAY<DATE>,
Timestamp TIMESTAMP,
TimestampArray ARRAY<TIMESTAMP>,
Numeric NUMERIC,
NumericArray ARRAY<NUMERIC>
) PRIMARY KEY (RowID)`,
}

@@ -169,6 +172,8 @@ var (
DateArray ARRAY<DATE>,
Timestamp TIMESTAMP,
TimestampArray ARRAY<TIMESTAMP>,
Numeric NUMERIC,
NumericArray ARRAY<NUMERIC>
) PRIMARY KEY (RowID)`,
}

@@ -1561,21 +1566,22 @@ func TestIntegration_BasicTypes(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
stmts := singerDBStatements
stmts = []string{
`CREATE TABLE Singers (
if !isEmulatorEnvSet() {
stmts = []string{
`CREATE TABLE Singers (
SingerId INT64 NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
SingerInfo BYTES(MAX)
) PRIMARY KEY (SingerId)`,
`CREATE INDEX SingerByName ON Singers(FirstName, LastName)`,
`CREATE TABLE Accounts (
`CREATE INDEX SingerByName ON Singers(FirstName, LastName)`,
`CREATE TABLE Accounts (
AccountId INT64 NOT NULL,
Nickname STRING(100),
Balance INT64 NOT NULL,
) PRIMARY KEY (AccountId)`,
`CREATE INDEX AccountByNickname ON Accounts(Nickname) STORING (Balance)`,
`CREATE TABLE Types (
`CREATE INDEX AccountByNickname ON Accounts(Nickname) STORING (Balance)`,
`CREATE TABLE Types (
RowID INT64 NOT NULL,
String STRING(MAX),
StringArray ARRAY<STRING(MAX)>,
@@ -1592,8 +1598,11 @@ func TestIntegration_BasicTypes(t *testing.T) {
Timestamp TIMESTAMP,
TimestampArray ARRAY<TIMESTAMP>,
Numeric NUMERIC,
NumericArray ARRAY<NUMERIC>
NumericArray ARRAY<NUMERIC>,
JSON JSON,
JSONArray ARRAY<JSON>
) PRIMARY KEY (RowID)`,
}
}
client, _, cleanup := prepareIntegrationTest(ctx, t, DefaultSessionPoolConfig, stmts)
defer cleanup()
@@ -1613,6 +1622,16 @@ func TestIntegration_BasicTypes(t *testing.T) {
n1 := *n1p
n2 := *n2p

type Message struct {
Name string
Body string
Time int64
}
msg := Message{"Alice", "Hello", 1294706395881547000}
jsonStr := `{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`
var unmarshalledJSONstruct interface{}
json.Unmarshal([]byte(jsonStr), &unmarshalledJSONstruct)

tests := []struct {
col string
val interface{}
@@ -1771,6 +1790,22 @@ func TestIntegration_BasicTypes(t *testing.T) {
}
}

if !isEmulatorEnvSet() {
tests = append(tests, []struct {
col string
val interface{}
want interface{}
}{
{col: "JSON", val: NullJSON{msg, true}, want: msg},
{col: "JSON", val: NullJSON{msg, true}, want: NullJSON{unmarshalledJSONstruct, true}},
{col: "JSON", val: NullJSON{msg, false}},
{col: "JSON", val: nil, want: NullJSON{}},
{col: "JSONArray", val: []NullJSON(nil)},
{col: "JSONArray", val: []NullJSON{}},
{col: "JSONArray", val: []NullJSON{{msg, true}, {msg, true}, {}}},
}...)
}

// Verify that we can insert the rows using mutations.
var muts []*Mutation
for i, test := range tests {
@@ -73,6 +73,10 @@ func numericType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_NUMERIC}
}

func jsonType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_JSON}
}

func bytesProto(b []byte) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString(b)}}
}

0 comments on commit ade8ab1

Please sign in to comment.