Skip to content

Commit

Permalink
docs: add sample for all data types (#51)
Browse files Browse the repository at this point in the history
* docs: add sample for all data types

* deps: update Spanner client to latest

* docs: update comments and fix formatting

* fix: use sql.NullTime + released Spanner version
  • Loading branch information
olavloite committed Oct 25, 2021
1 parent 802e7be commit 5a0129b
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 42 deletions.
8 changes: 4 additions & 4 deletions driver_with_mockserver_test.go
Expand Up @@ -506,8 +506,8 @@ func TestQueryWithAllTypes(t *testing.T) {
var bt []byte
var i int64
var f float64
var r spanner.NullNumeric
var d spanner.NullDate
var r big.Rat
var d civil.Date
var ts time.Time
var j spanner.NullJSON
var bArray []spanner.NullBool
Expand Down Expand Up @@ -538,10 +538,10 @@ func TestQueryWithAllTypes(t *testing.T) {
if g, w := f, 3.14; g != w {
t.Errorf("row value mismatch for float64\nGot: %v\nWant: %v", g, w)
}
if g, w := r, numeric("6.626"); g.Numeric.Cmp(&w) != 0 {
if g, w := r, numeric("6.626"); g.Cmp(&w) != 0 {
t.Errorf("row value mismatch for numeric\nGot: %v\nWant: %v", g, w)
}
if g, w := d, nullDate(true, "2021-07-21"); !cmp.Equal(g, w) {
if g, w := d, date("2021-07-21"); !cmp.Equal(g, w) {
t.Errorf("row value mismatch for date\nGot: %v\nWant: %v", g, w)
}
if g, w := ts, time.Date(2021, 7, 21, 21, 7, 59, 339911800, time.UTC); g != w {
Expand Down
217 changes: 217 additions & 0 deletions examples/data-types/main.go
@@ -0,0 +1,217 @@
// Copyright 2021 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"database/sql"
"fmt"
"math/big"
"os"
"time"

"cloud.google.com/go/civil"
"cloud.google.com/go/spanner"
_ "github.com/cloudspannerecosystem/go-sql-spanner"
"github.com/cloudspannerecosystem/go-sql-spanner/examples"
)

var createTableStatement = `CREATE TABLE AllTypes (
key INT64,
bool BOOL,
string STRING(MAX),
bytes BYTES(MAX),
int64 INT64,
float64 FLOAT64,
numeric NUMERIC,
date DATE,
timestamp TIMESTAMP,
boolArray ARRAY<BOOL>,
stringArray ARRAY<STRING(MAX)>,
bytesArray ARRAY<BYTES(MAX)>,
int64Array ARRAY<INT64>,
float64Array ARRAY<FLOAT64>,
numericArray ARRAY<NUMERIC>,
dateArray ARRAY<DATE>,
timestampArray ARRAY<TIMESTAMP>,
) PRIMARY KEY (key)`

// Sample showing how to work with the different data types that are supported by Cloud Spanner:
// 1. How to get data from columns of each type.
// 2. How to set data of each type as a statement parameter.
//
// Execute the sample with the command `go run main.go` from this directory.
func dataTypes(projectId, instanceId, databaseId string) error {
ctx := context.Background()
db, err := sql.Open("spanner", fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId))
if err != nil {
return fmt.Errorf("failed to open database connection: %v\n", err)
}
defer db.Close()

// Statement parameters can be given as either native types (string, bool, int64, ...) or as nullable struct types
// defined in the Spanner client library (spanner.NullString, spanner.NullBool, spanner.NullInt64, ...).

// Insert a test row with all non-null values using DML and native types.
if _, err := db.ExecContext(ctx, `INSERT INTO AllTypes (
key, bool, string, bytes, int64, float64, numeric, date, timestamp,
boolArray, stringArray, bytesArray, int64Array, float64Array, numericArray, dateArray, timestampArray)
VALUES (@key, @bool, @string, @bytes, @int64, @float64, @numeric, @date, @timestamp,
@boolArray, @stringArray, @bytesArray, @int64Array, @float64Array, @numericArray, @dateArray, @timestampArray)`,
1, true, "string", []byte("bytes"), 100, 3.14, *big.NewRat(1, 1), civil.DateOf(time.Now()), time.Now(),
[]bool{true, false}, []string{"s1", "s2"}, [][]byte{[]byte("b1"), []byte("b2")}, []int64{1, 2},
[]float64{1.1, 2.2}, []big.Rat{*big.NewRat(1, 2), *big.NewRat(1, 3)},
[]civil.Date{{2021, 10, 12}, {2021, 10, 13}},
[]time.Time{time.Now(), time.Now().Add(24 * time.Hour)}); err != nil {
return fmt.Errorf("failed to insert a record with all non-null values using DML: %v", err)
}
fmt.Print("Inserted a test record with all non-null values\n")

// Insert a test row with all null values using DML and Spanner Null* types.
if _, err := db.ExecContext(ctx, `INSERT INTO AllTypes (
key, bool, string, bytes, int64, float64, numeric, date, timestamp,
boolArray, stringArray, bytesArray, int64Array, float64Array, numericArray, dateArray, timestampArray)
VALUES (@key, @bool, @string, @bytes, @int64, @float64, @numeric, @date, @timestamp,
@boolArray, @stringArray, @bytesArray, @int64Array, @float64Array, @numericArray, @dateArray, @timestampArray)`,
2, spanner.NullBool{}, spanner.NullString{}, []byte(nil), // There is no NullBytes type
spanner.NullInt64{}, spanner.NullFloat64{}, spanner.NullNumeric{}, spanner.NullDate{}, spanner.NullTime{},
// These array values all contain two NULL values in the (non-null) array.
[]spanner.NullBool{{}, {}}, []spanner.NullString{{}, {}}, [][]byte{[]byte(nil), []byte(nil)},
[]spanner.NullInt64{{}, {}}, []spanner.NullFloat64{{}, {}}, []spanner.NullNumeric{{}, {}},
[]spanner.NullDate{{}, {}}, []spanner.NullTime{{}, {}}); err != nil {
return fmt.Errorf("failed to insert a record with all null values using DML: %v", err)
}
fmt.Print("Inserted a test record with all typed null values\n")

// The Go sql driver supports inserting untyped nil values for NULL values. Cloud Spanner also supports untyped NULL
// values. The Spanner emulator however does not (yet) support this, which is why this part of the code sample is
// currently disabled on the emulator. Running it against a real Cloud Spanner database works.
if os.Getenv("SPANNER_EMULATOR_HOST") == "" {
if _, err := db.ExecContext(ctx, `INSERT INTO AllTypes (
key, bool, string, bytes, int64, float64, numeric, date, timestamp,
boolArray, stringArray, bytesArray, int64Array, float64Array, numericArray, dateArray, timestampArray)
VALUES (@key, @bool, @string, @bytes, @int64, @float64, @numeric, @date, @timestamp,
@boolArray, @stringArray, @bytesArray, @int64Array, @float64Array, @numericArray, @dateArray, @timestampArray)`,
3, nil, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil); err != nil {
return fmt.Errorf("failed to insert a record with all untyped null values using DML: %v", err)
}
fmt.Print("Inserted a test record with all untyped null values\n")
}

// You can use the same types for getting data from a column as for setting the data in a statement parameter,
// except for ARRAY columns. Arrays must be stored in a []spanner.Null* variable, as all arrays may always
// contain NULL elements in the array.
var r1 nativeTypes
if err := db.QueryRowContext(ctx, "SELECT * FROM AllTypes WHERE key=@key", 1).Scan(
&r1.key, &r1.bool, &r1.string, &r1.bytes, &r1.int64, &r1.float64, &r1.numeric, &r1.date, &r1.timestamp,
&r1.boolArray, &r1.stringArray, &r1.bytesArray, &r1.int64Array, &r1.float64Array, &r1.numericArray, &r1.dateArray, &r1.timestampArray,
); err != nil {
return fmt.Errorf("failed to get row with non-null values: %v", err)
}
fmt.Print("Queried a test record with all non-null values\n")

// You can also use the spanner.Null* types to get data. These types can store both non-null and null values.
var r2 nullTypes
if err := db.QueryRowContext(ctx, "SELECT * FROM AllTypes WHERE key=@key", 1).Scan(
&r2.key, &r2.bool, &r2.string, &r2.bytes, &r2.int64, &r2.float64, &r2.numeric, &r2.date, &r2.timestamp,
&r2.boolArray, &r2.stringArray, &r2.bytesArray, &r2.int64Array, &r2.float64Array, &r2.numericArray, &r2.dateArray, &r2.timestampArray,
); err != nil {
return fmt.Errorf("failed to get row with null values: %v", err)
}
fmt.Print("Queried a test record with all null values and stored these in spanner.Null* variables\n")

// You can also use the sql.Null* types where these are available. Note that the Go sql package
// does not contain any types for nullable numeric and date values, so for these you must always
// use spanner.NullNumeric and spanner.NullDate.
var r3 sqlNullTypes
if err := db.QueryRowContext(ctx, "SELECT * FROM AllTypes WHERE key=@key", 1).Scan(
&r3.key, &r3.bool, &r3.string, &r3.bytes, &r3.int64, &r3.float64, &r3.numeric, &r3.date, &r3.timestamp,
&r3.boolArray, &r3.stringArray, &r3.bytesArray, &r3.int64Array, &r3.float64Array, &r3.numericArray, &r3.dateArray, &r3.timestampArray,
); err != nil {
return fmt.Errorf("failed to get row with null values using Go sql null types: %v", err)
}
fmt.Print("Queried a test record with all null values and stored these in sql.Null* variables\n")

return nil
}

type nativeTypes struct {
key int64
bool bool
string string
bytes []byte
int64 int64
float64 float64
numeric big.Rat
date civil.Date
timestamp time.Time
// Array types must always use the Null* types, because an array may always
// contain NULL elements in the array itself (even if the ARRAY column is
// defined as NOT NULL).
boolArray []spanner.NullBool
stringArray []spanner.NullString
bytesArray [][]byte
int64Array []spanner.NullInt64
float64Array []spanner.NullFloat64
numericArray []spanner.NullNumeric
dateArray []spanner.NullDate
timestampArray []spanner.NullTime
}

type nullTypes struct {
key int64
bool spanner.NullBool
string spanner.NullString
bytes []byte // There is no spanner.NullBytes type
int64 spanner.NullInt64
float64 spanner.NullFloat64
numeric spanner.NullNumeric
date spanner.NullDate
timestamp spanner.NullTime
boolArray []spanner.NullBool
stringArray []spanner.NullString
bytesArray [][]byte
int64Array []spanner.NullInt64
float64Array []spanner.NullFloat64
numericArray []spanner.NullNumeric
dateArray []spanner.NullDate
timestampArray []spanner.NullTime
}

type sqlNullTypes struct {
key int64
bool sql.NullBool
string sql.NullString
bytes []byte // There is no sql.NullBytes type
int64 sql.NullInt64
float64 sql.NullFloat64
numeric spanner.NullNumeric // There is no sql.NullNumeric type
date spanner.NullDate // There is no sql.NullDate type
timestamp sql.NullTime
// Array types must always use the spanner.Null* structs.
boolArray []spanner.NullBool
stringArray []spanner.NullString
bytesArray [][]byte
int64Array []spanner.NullInt64
float64Array []spanner.NullFloat64
numericArray []spanner.NullNumeric
dateArray []spanner.NullDate
timestampArray []spanner.NullTime
}

func main() {
examples.RunSampleOnEmulator(dataTypes, createTableStatement)
}
5 changes: 3 additions & 2 deletions examples/go.mod
Expand Up @@ -5,13 +5,14 @@ go 1.14
replace github.com/cloudspannerecosystem/go-sql-spanner => ../

require (
cloud.google.com/go/spanner v1.25.0
cloud.google.com/go v0.97.0
cloud.google.com/go/spanner v1.27.0
github.com/cloudspannerecosystem/go-sql-spanner v0.0.0-00010101000000-000000000000
github.com/containerd/containerd v1.5.5 // indirect
github.com/docker/docker v20.10.8+incompatible
github.com/docker/go-connections v0.4.0
github.com/gorilla/mux v1.8.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8
google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404
)

0 comments on commit 5a0129b

Please sign in to comment.