all: remove 1.6, 1.7, 1.8 build flags and split files

Since we no longer support 1.8 and below, all of this is unnecessary.

Change-Id: Id47b97efcf6a5c0d40fb67c8fbd999a452de8f2f
Reviewed-on: https://code-review.googlesource.com/c/35411
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Eno Compton <enocom@google.com>
diff --git a/bigquery/oc_test.go b/bigquery/oc_test.go
index 07d1b71..de85173 100644
--- a/bigquery/oc_test.go
+++ b/bigquery/oc_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package bigquery
 
 import (
diff --git a/bigtable/not_go18.go b/bigtable/not_go18.go
deleted file mode 100644
index 17109ea..0000000
--- a/bigtable/not_go18.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2017 Google LLC
-//
-// 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.
-
-// +build !go1.8
-
-package bigtable
-
-import (
-	"context"
-
-	"google.golang.org/api/option"
-)
-
-// OpenCensus only supports go 1.8 and higher.
-
-func openCensusOptions() []option.ClientOption { return nil }
-
-func traceStartSpan(ctx context.Context, _ string) context.Context {
-	return ctx
-}
-
-func traceEndSpan(context.Context, error) {
-}
-
-func tracePrintf(context.Context, map[string]interface{}, string, ...interface{}) {
-}
diff --git a/bigtable/go18.go b/bigtable/trace.go
similarity index 84%
rename from bigtable/go18.go
rename to bigtable/trace.go
index 13f8f66..6c579b1 100644
--- a/bigtable/go18.go
+++ b/bigtable/trace.go
@@ -12,26 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package bigtable
 
 import (
 	"context"
 	"fmt"
 
-	"go.opencensus.io/plugin/ocgrpc"
 	"go.opencensus.io/trace"
-	"google.golang.org/api/option"
-	"google.golang.org/grpc"
 )
 
-func openCensusOptions() []option.ClientOption {
-	return []option.ClientOption{
-		option.WithGRPCDialOption(grpc.WithStatsHandler(&ocgrpc.ClientHandler{})),
-	}
-}
-
 func traceStartSpan(ctx context.Context, name string) context.Context {
 	ctx, _ = trace.StartSpan(ctx, name)
 	return ctx
diff --git a/datastore/oc_test.go b/datastore/oc_test.go
index 836c271..be1e5a4 100644
--- a/datastore/oc_test.go
+++ b/datastore/oc_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package datastore
 
 import (
diff --git a/httpreplay/cmd/httpr/httpr.go b/httpreplay/cmd/httpr/httpr.go
index eb5583c..cc734c4 100644
--- a/httpreplay/cmd/httpr/httpr.go
+++ b/httpreplay/cmd/httpr/httpr.go
@@ -18,8 +18,6 @@
 // To get the CA certificate of the proxy, issue a GET to http://localhost:CP/authority.cer, where
 // CP is the control port.
 
-// +build go1.8
-
 package main
 
 import (
diff --git a/httpreplay/cmd/httpr/integration_test.go b/httpreplay/cmd/httpr/integration_test.go
index 7d370b4..bce29e6 100644
--- a/httpreplay/cmd/httpr/integration_test.go
+++ b/httpreplay/cmd/httpr/integration_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package main_test
 
 import (
diff --git a/httpreplay/httpreplay.go b/httpreplay/httpreplay.go
index 86de2c0..7c4e9e3 100644
--- a/httpreplay/httpreplay.go
+++ b/httpreplay/httpreplay.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 // Package httpreplay provides an API for recording and replaying traffic
 // from HTTP-based Google API clients.
 //
diff --git a/httpreplay/httpreplay_not18.go b/httpreplay/httpreplay_not18.go
deleted file mode 100644
index 3fbbf65..0000000
--- a/httpreplay/httpreplay_not18.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-// +build !go1.8
-
-// httpreplay is available in go1.8 and forward. This file exists only for 1.6 and 1.7 to
-// compile, since every package must have some buildable file else go test ./... fails.
-package httpreplay
-
-import (
-	"context"
-	"net/http"
-
-	"google.golang.org/api/option"
-)
-
-// Supported reports whether httpreplay is supported in the current version of Go.
-// For Go 1.7 and below, the answer is false.
-func Supported() bool { return false }
-
-type (
-	Recorder struct{}
-
-	Replayer struct{}
-)
-
-func NewRecorder(string, []byte) (*Recorder, error) { return nil, nil }
-
-func (*Recorder) Client(context.Context, ...option.ClientOption) (*http.Client, error) {
-	return nil, nil
-}
-func (*Recorder) Close() error { return nil }
-
-func NewReplayer(string) (*Replayer, error) { return nil, nil }
-
-func (*Replayer) Initial() []byte                              { return nil }
-func (*Replayer) IgnoreHeader(string)                          {}
-func (*Replayer) Client(context.Context) (*http.Client, error) { return nil, nil }
-func (*Replayer) Close() error                                 { return nil }
-
-func DebugHeaders() {}
diff --git a/httpreplay/httpreplay_test.go b/httpreplay/httpreplay_test.go
index 45f7fa5..1fa0f24 100644
--- a/httpreplay/httpreplay_test.go
+++ b/httpreplay/httpreplay_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package httpreplay_test
 
 import (
diff --git a/httpreplay/internal/proxy/debug.go b/httpreplay/internal/proxy/debug.go
index 20acbe9..caf1997 100644
--- a/httpreplay/internal/proxy/debug.go
+++ b/httpreplay/internal/proxy/debug.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package proxy
 
 import (
diff --git a/httpreplay/internal/proxy/log.go b/httpreplay/internal/proxy/log.go
index 3eec478..d1759ce 100644
--- a/httpreplay/internal/proxy/log.go
+++ b/httpreplay/internal/proxy/log.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package proxy
 
 import (
diff --git a/httpreplay/internal/proxy/log_test.go b/httpreplay/internal/proxy/log_test.go
index f1de685..327227c 100644
--- a/httpreplay/internal/proxy/log_test.go
+++ b/httpreplay/internal/proxy/log_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package proxy
 
 import (
diff --git a/httpreplay/internal/proxy/record.go b/httpreplay/internal/proxy/record.go
index b3b76c0..57eacaf 100644
--- a/httpreplay/internal/proxy/record.go
+++ b/httpreplay/internal/proxy/record.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 // Package proxy provides a record/replay HTTP proxy. It is designed to support
 // both an in-memory API (cloud.google.com/go/httpreplay) and a standalone server
 // (cloud.google.com/go/httpreplay/cmd/httpr).
diff --git a/httpreplay/internal/proxy/replay.go b/httpreplay/internal/proxy/replay.go
index b802057..e785c9a 100644
--- a/httpreplay/internal/proxy/replay.go
+++ b/httpreplay/internal/proxy/replay.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package proxy
 
 import (
diff --git a/httpreplay/internal/proxy/replay_test.go b/httpreplay/internal/proxy/replay_test.go
index 0d0cb6e..72ce582 100644
--- a/httpreplay/internal/proxy/replay_test.go
+++ b/httpreplay/internal/proxy/replay_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package proxy
 
 import (
diff --git a/internal/btree/benchmarks_test.go b/internal/btree/benchmarks_test.go
index cc3da57..4910cab 100644
--- a/internal/btree/benchmarks_test.go
+++ b/internal/btree/benchmarks_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.7
-
 package btree
 
 import (
diff --git a/internal/testutil/go18.go b/internal/testutil/trace.go
similarity index 98%
rename from internal/testutil/go18.go
rename to internal/testutil/trace.go
index 5d7e027..b0a7b77 100644
--- a/internal/testutil/go18.go
+++ b/internal/testutil/trace.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package testutil
 
 import (
diff --git a/internal/trace/not_go18.go b/internal/trace/not_go18.go
deleted file mode 100644
index 7e681b6..0000000
--- a/internal/trace/not_go18.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-// +build !go1.8
-
-package trace
-
-import (
-	"context"
-)
-
-// OpenCensus only supports go 1.8 and higher.
-
-func StartSpan(ctx context.Context, _ string) context.Context {
-	return ctx
-}
-
-func EndSpan(context.Context, error) {
-}
diff --git a/internal/trace/go18.go b/internal/trace/trace.go
similarity index 99%
rename from internal/trace/go18.go
rename to internal/trace/trace.go
index 3a100a3..95c7821 100644
--- a/internal/trace/go18.go
+++ b/internal/trace/trace.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package trace
 
 import (
diff --git a/internal/trace/go18_test.go b/internal/trace/trace_test.go
similarity index 98%
rename from internal/trace/go18_test.go
rename to internal/trace/trace_test.go
index f907270..2af9fb8 100644
--- a/internal/trace/go18_test.go
+++ b/internal/trace/trace_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package trace
 
 import (
diff --git a/logging/examples_go18_test.go b/logging/examples_go18_test.go
deleted file mode 100644
index 778649c..0000000
--- a/logging/examples_go18_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2016 Google LLC
-//
-// 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.
-
-// +build go1.8
-
-package logging_test
-
-import (
-	"context"
-
-	"cloud.google.com/go/logging"
-	"go.opencensus.io/trace"
-)
-
-// This example shows how to create a Logger that disables OpenCensus tracing of the
-// WriteLogEntries RPC.
-func ExampleContextFunc() {
-	ctx := context.Background()
-	client, err := logging.NewClient(ctx, "my-project")
-	if err != nil {
-		// TODO: Handle error.
-	}
-	lg := client.Logger("logID", logging.ContextFunc(func() (context.Context, func()) {
-		ctx, span := trace.StartSpan(context.Background(), "this span will not be exported",
-			trace.WithSampler(trace.NeverSample()))
-		return ctx, span.End
-	}))
-	_ = lg // TODO: Use lg
-}
diff --git a/logging/examples_test.go b/logging/examples_test.go
index 44a8c82..b9623ee 100644
--- a/logging/examples_test.go
+++ b/logging/examples_test.go
@@ -21,6 +21,7 @@
 	"os"
 
 	"cloud.google.com/go/logging"
+	"go.opencensus.io/trace"
 )
 
 func ExampleNewClient() {
@@ -164,3 +165,19 @@
 	fmt.Println(sev)
 	// Output: Alert
 }
+
+// This example shows how to create a Logger that disables OpenCensus tracing of the
+// WriteLogEntries RPC.
+func ExampleContextFunc() {
+	ctx := context.Background()
+	client, err := logging.NewClient(ctx, "my-project")
+	if err != nil {
+		// TODO: Handle error.
+	}
+	lg := client.Logger("logID", logging.ContextFunc(func() (context.Context, func()) {
+		ctx, span := trace.StartSpan(context.Background(), "this span will not be exported",
+			trace.WithSampler(trace.NeverSample()))
+		return ctx, span.End
+	}))
+	_ = lg // TODO: Use lg
+}
diff --git a/profiler/mutex.go b/profiler/mutex.go
index 93512fa..c432adb 100644
--- a/profiler/mutex.go
+++ b/profiler/mutex.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package profiler
 
 import "runtime"
diff --git a/profiler/mutex_go17.go b/profiler/mutex_go17.go
deleted file mode 100644
index 5b9ae9c..0000000
--- a/profiler/mutex_go17.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2017 Google LLC
-//
-// 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.
-
-// +build !go1.8
-
-package profiler
-
-func enableMutexProfiling() bool {
-	return false
-}
diff --git a/profiler/proftest/proftest.go b/profiler/proftest/proftest.go
index 561414c..b103dd5 100644
--- a/profiler/proftest/proftest.go
+++ b/profiler/proftest/proftest.go
@@ -15,11 +15,6 @@
 // Package proftest contains test helpers for profiler agent integration tests.
 // This package is experimental.
 
-// golang.org/x/build/kubernetes/dialer.go imports "context" package (rather
-// than "context") and that does not exist in Go 1.6 or
-// earlier.
-// +build go1.7
-
 package proftest
 
 import (
diff --git a/pubsub/not_go18.go b/pubsub/not_go18.go
deleted file mode 100644
index dd90331..0000000
--- a/pubsub/not_go18.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-// +build !go1.8
-
-package pubsub
-
-import (
-	"context"
-
-	"google.golang.org/api/option"
-)
-
-// OpenCensus only supports go 1.8 and higher.
-
-func openCensusOptions() []option.ClientOption { return nil }
-
-func withSubscriptionKey(ctx context.Context, _ string) context.Context {
-	return ctx
-}
-
-type dummy struct{}
-
-var (
-	// Not supported below Go 1.8.
-	PullCount dummy
-	// Not supported below Go 1.8.
-	AckCount dummy
-	// Not supported below Go 1.8.
-	NackCount dummy
-	// Not supported below Go 1.8.
-	ModAckCount dummy
-	// Not supported below Go 1.8.
-	ModAckTimeoutCount dummy
-	// Not supported below Go 1.8.
-	StreamOpenCount dummy
-	// Not supported below Go 1.8.
-	StreamRetryCount dummy
-	// Not supported below Go 1.8.
-	StreamRequestCount dummy
-	// Not supported below Go 1.8.
-	StreamResponseCount dummy
-)
-
-func recordStat(context.Context, dummy, int64) {
-}
diff --git a/pubsub/go18.go b/pubsub/trace.go
similarity index 99%
rename from pubsub/go18.go
rename to pubsub/trace.go
index 9396cf1..06965c2 100644
--- a/pubsub/go18.go
+++ b/pubsub/trace.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package pubsub
 
 import (
diff --git a/spanner/go17.go b/spanner/go17.go
deleted file mode 100644
index f42419f..0000000
--- a/spanner/go17.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-// +build go1.7
-
-package spanner
-
-import "reflect"
-
-func structTagLookup(tag reflect.StructTag, key string) (string, bool) {
-	return tag.Lookup(key)
-}
diff --git a/spanner/go17_test.go b/spanner/go17_test.go
deleted file mode 100644
index 325349a..0000000
--- a/spanner/go17_test.go
+++ /dev/null
@@ -1,466 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-// +build go1.7
-
-package spanner
-
-import (
-	"context"
-	"reflect"
-	"testing"
-	"time"
-
-	"cloud.google.com/go/civil"
-	proto "github.com/golang/protobuf/proto"
-	proto3 "github.com/golang/protobuf/ptypes/struct"
-	sppb "google.golang.org/genproto/googleapis/spanner/v1"
-)
-
-func TestEncodeStructValueDynamicStructs(t *testing.T) {
-	dynStructType := reflect.StructOf([]reflect.StructField{
-		{Name: "A", Type: reflect.TypeOf(0), Tag: `spanner:"a"`},
-		{Name: "B", Type: reflect.TypeOf(""), Tag: `spanner:"b"`},
-	})
-	dynNullableStructType := reflect.PtrTo(dynStructType)
-	dynStructArrType := reflect.SliceOf(dynStructType)
-	dynNullableStructArrType := reflect.SliceOf(dynNullableStructType)
-
-	dynStructValue := reflect.New(dynStructType)
-	dynStructValue.Elem().Field(0).SetInt(10)
-	dynStructValue.Elem().Field(1).SetString("abc")
-
-	dynStructArrValue := reflect.MakeSlice(dynNullableStructArrType, 2, 2)
-	dynStructArrValue.Index(0).Set(reflect.Zero(dynNullableStructType))
-	dynStructArrValue.Index(1).Set(dynStructValue)
-
-	structProtoType := structType(
-		mkField("a", intType()),
-		mkField("b", stringType()))
-
-	arrProtoType := listType(structProtoType)
-
-	for _, test := range []encodeTest{
-		{
-			"Dynanic non-NULL struct value.",
-			dynStructValue.Elem().Interface(),
-			listProto(intProto(10), stringProto("abc")),
-			structProtoType,
-		},
-		{
-			"Dynanic NULL struct value.",
-			reflect.Zero(dynNullableStructType).Interface(),
-			nullProto(),
-			structProtoType,
-		},
-		{
-			"Empty array of dynamic structs.",
-			reflect.MakeSlice(dynStructArrType, 0, 0).Interface(),
-			listProto([]*proto3.Value{}...),
-			arrProtoType,
-		},
-		{
-			"NULL array of non-NULL-able dynamic structs.",
-			reflect.Zero(dynStructArrType).Interface(),
-			nullProto(),
-			arrProtoType,
-		},
-		{
-			"NULL array of NULL-able(nil) dynamic structs.",
-			reflect.Zero(dynNullableStructArrType).Interface(),
-			nullProto(),
-			arrProtoType,
-		},
-		{
-			"Array containing NULL(nil) dynamic-typed struct elements.",
-			dynStructArrValue.Interface(),
-			listProto(
-				nullProto(),
-				listProto(intProto(10), stringProto("abc"))),
-			arrProtoType,
-		},
-	} {
-		encodeStructValue(test, t)
-	}
-}
-
-func TestEncodeStructValueEmptyStruct(t *testing.T) {
-	emptyListValue := listProto([]*proto3.Value{}...)
-	emptyStructType := structType([]*sppb.StructType_Field{}...)
-	emptyStruct := struct{}{}
-	nullEmptyStruct := (*struct{})(nil)
-
-	dynamicEmptyStructType := reflect.StructOf(make([]reflect.StructField, 0, 0))
-	dynamicStructArrType := reflect.SliceOf(reflect.PtrTo((dynamicEmptyStructType)))
-
-	dynamicEmptyStruct := reflect.New(dynamicEmptyStructType)
-	dynamicNullEmptyStruct := reflect.Zero(reflect.PtrTo(dynamicEmptyStructType))
-
-	dynamicStructArrValue := reflect.MakeSlice(dynamicStructArrType, 2, 2)
-	dynamicStructArrValue.Index(0).Set(dynamicNullEmptyStruct)
-	dynamicStructArrValue.Index(1).Set(dynamicEmptyStruct)
-
-	for _, test := range []encodeTest{
-		{
-			"Go empty struct.",
-			emptyStruct,
-			emptyListValue,
-			emptyStructType,
-		},
-		{
-			"Dynamic empty struct.",
-			dynamicEmptyStruct.Interface(),
-			emptyListValue,
-			emptyStructType,
-		},
-		{
-			"Go NULL empty struct.",
-			nullEmptyStruct,
-			nullProto(),
-			emptyStructType,
-		},
-		{
-			"Dynamic NULL empty struct.",
-			dynamicNullEmptyStruct.Interface(),
-			nullProto(),
-			emptyStructType,
-		},
-		{
-			"Non-empty array of dynamic NULL and non-NULL empty structs.",
-			dynamicStructArrValue.Interface(),
-			listProto(nullProto(), emptyListValue),
-			listType(emptyStructType),
-		},
-		{
-			"Non-empty array of nullable empty structs.",
-			[]*struct{}{nullEmptyStruct, &emptyStruct},
-			listProto(nullProto(), emptyListValue),
-			listType(emptyStructType),
-		},
-		{
-			"Empty array of empty struct.",
-			[]struct{}{},
-			emptyListValue,
-			listType(emptyStructType),
-		},
-		{
-			"Null array of empty structs.",
-			[]struct{}(nil),
-			nullProto(),
-			listType(emptyStructType),
-		},
-	} {
-		encodeStructValue(test, t)
-	}
-}
-
-func TestEncodeStructValueMixedStructTypes(t *testing.T) {
-	type staticStruct struct {
-		F int `spanner:"fStatic"`
-	}
-	s1 := staticStruct{10}
-	s2 := (*staticStruct)(nil)
-
-	var f float64
-	dynStructType := reflect.StructOf([]reflect.StructField{
-		{Name: "A", Type: reflect.TypeOf(f), Tag: `spanner:"fDynamic"`},
-	})
-	s3 := reflect.New(dynStructType)
-	s3.Elem().Field(0).SetFloat(3.14)
-
-	for _, test := range []encodeTest{
-		{
-			"'struct' with static and dynamic *struct, []*struct, []struct fields",
-			struct {
-				A []staticStruct
-				B []*staticStruct
-				C interface{}
-			}{
-				[]staticStruct{s1, s1},
-				[]*staticStruct{&s1, s2},
-				s3.Interface(),
-			},
-			listProto(
-				listProto(listProto(intProto(10)), listProto(intProto(10))),
-				listProto(listProto(intProto(10)), nullProto()),
-				listProto(floatProto(3.14))),
-			structType(
-				mkField("A", listType(structType(mkField("fStatic", intType())))),
-				mkField("B", listType(structType(mkField("fStatic", intType())))),
-				mkField("C", structType(mkField("fDynamic", floatType())))),
-		},
-	} {
-		encodeStructValue(test, t)
-	}
-}
-
-func TestBindParamsDynamic(t *testing.T) {
-	// Verify Statement.bindParams generates correct values and types.
-	st := Statement{
-		SQL:    "SELECT id from t_foo WHERE col = @var",
-		Params: map[string]interface{}{"var": nil},
-	}
-	want := &sppb.ExecuteSqlRequest{
-		Params: &proto3.Struct{
-			Fields: map[string]*proto3.Value{"var": nil},
-		},
-		ParamTypes: map[string]*sppb.Type{"var": nil},
-	}
-	var (
-		t1, _ = time.Parse(time.RFC3339Nano, "2016-11-15T15:04:05.999999999Z")
-		// Boundaries
-		t2, _ = time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00.000000000Z")
-	)
-	dynamicStructType := reflect.StructOf([]reflect.StructField{
-		{Name: "A", Type: reflect.TypeOf(t1), Tag: `spanner:"field"`},
-		{Name: "B", Type: reflect.TypeOf(3.14), Tag: `spanner:""`},
-	})
-	dynamicStructArrType := reflect.SliceOf(reflect.PtrTo(dynamicStructType))
-	dynamicEmptyStructType := reflect.StructOf(make([]reflect.StructField, 0, 0))
-
-	dynamicStructTypeProto := structType(
-		mkField("field", timeType()),
-		mkField("", floatType()))
-
-	s3 := reflect.New(dynamicStructType)
-	s3.Elem().Field(0).Set(reflect.ValueOf(t1))
-	s3.Elem().Field(1).SetFloat(1.4)
-
-	s4 := reflect.New(dynamicStructType)
-	s4.Elem().Field(0).Set(reflect.ValueOf(t2))
-	s4.Elem().Field(1).SetFloat(-13.3)
-
-	dynamicStructArrayVal := reflect.MakeSlice(dynamicStructArrType, 2, 2)
-	dynamicStructArrayVal.Index(0).Set(s3)
-	dynamicStructArrayVal.Index(1).Set(s4)
-
-	for i, test := range []struct {
-		val       interface{}
-		wantField *proto3.Value
-		wantType  *sppb.Type
-	}{
-		{
-			s3.Interface(),
-			listProto(timeProto(t1), floatProto(1.4)),
-			structType(
-				mkField("field", timeType()),
-				mkField("", floatType())),
-		},
-		{
-			reflect.Zero(reflect.PtrTo(dynamicEmptyStructType)).Interface(),
-			nullProto(),
-			structType([]*sppb.StructType_Field{}...),
-		},
-		{
-			dynamicStructArrayVal.Interface(),
-			listProto(
-				listProto(timeProto(t1), floatProto(1.4)),
-				listProto(timeProto(t2), floatProto(-13.3))),
-			listType(dynamicStructTypeProto),
-		},
-		{
-			[]*struct {
-				F1 time.Time `spanner:"field"`
-				F2 float64   `spanner:""`
-			}{
-				nil,
-				{t1, 1.4},
-			},
-			listProto(
-				nullProto(),
-				listProto(timeProto(t1), floatProto(1.4))),
-			listType(dynamicStructTypeProto),
-		},
-	} {
-		st.Params["var"] = test.val
-		want.Params.Fields["var"] = test.wantField
-		want.ParamTypes["var"] = test.wantType
-		got := &sppb.ExecuteSqlRequest{}
-		if err := st.bindParams(got); err != nil || !proto.Equal(got, want) {
-			// handle NaN
-			if test.wantType.Code == floatType().Code && proto.MarshalTextString(got) == proto.MarshalTextString(want) {
-				continue
-			}
-			t.Errorf("#%d: bind result: \n(%v, %v)\nwant\n(%v, %v)\n", i, got, err, want, nil)
-		}
-	}
-}
-
-func TestStructParametersBind(t *testing.T) {
-	t.Parallel()
-	ctx := context.Background()
-	client, _, cleanup := prepare(ctx, t, nil)
-	defer cleanup()
-
-	type tRow []interface{}
-	type tRows []struct{ trow tRow }
-
-	type allFields struct {
-		Stringf string
-		Intf    int
-		Boolf   bool
-		Floatf  float64
-		Bytef   []byte
-		Timef   time.Time
-		Datef   civil.Date
-	}
-	allColumns := []string{
-		"Stringf",
-		"Intf",
-		"Boolf",
-		"Floatf",
-		"Bytef",
-		"Timef",
-		"Datef",
-	}
-	s1 := allFields{"abc", 300, false, 3.45, []byte("foo"), t1, d1}
-	s2 := allFields{"def", -300, false, -3.45, []byte("bar"), t2, d2}
-
-	dynamicStructType := reflect.StructOf([]reflect.StructField{
-		{Name: "A", Type: reflect.TypeOf(t1), Tag: `spanner:"ff1"`},
-	})
-	s3 := reflect.New(dynamicStructType)
-	s3.Elem().Field(0).Set(reflect.ValueOf(t1))
-
-	for i, test := range []struct {
-		param interface{}
-		sql   string
-		cols  []string
-		trows tRows
-	}{
-		// Struct value.
-		{
-			s1,
-			"SELECT" +
-				" @p.Stringf," +
-				" @p.Intf," +
-				" @p.Boolf," +
-				" @p.Floatf," +
-				" @p.Bytef," +
-				" @p.Timef," +
-				" @p.Datef",
-			allColumns,
-			tRows{
-				{tRow{"abc", 300, false, 3.45, []byte("foo"), t1, d1}},
-			},
-		},
-		// Array of struct value.
-		{
-			[]allFields{s1, s2},
-			"SELECT * FROM UNNEST(@p)",
-			allColumns,
-			tRows{
-				{tRow{"abc", 300, false, 3.45, []byte("foo"), t1, d1}},
-				{tRow{"def", -300, false, -3.45, []byte("bar"), t2, d2}},
-			},
-		},
-		// Null struct.
-		{
-			(*allFields)(nil),
-			"SELECT @p IS NULL",
-			[]string{""},
-			tRows{
-				{tRow{true}},
-			},
-		},
-		// Null Array of struct.
-		{
-			[]allFields(nil),
-			"SELECT @p IS NULL",
-			[]string{""},
-			tRows{
-				{tRow{true}},
-			},
-		},
-		// Empty struct.
-		{
-			struct{}{},
-			"SELECT @p IS NULL ",
-			[]string{""},
-			tRows{
-				{tRow{false}},
-			},
-		},
-		// Empty array of struct.
-		{
-			[]allFields{},
-			"SELECT * FROM UNNEST(@p) ",
-			allColumns,
-			tRows{},
-		},
-		// Struct with duplicate fields.
-		{
-			struct {
-				A int `spanner:"field"`
-				B int `spanner:"field"`
-			}{10, 20},
-			"SELECT * FROM UNNEST([@p]) ",
-			[]string{"field", "field"},
-			tRows{
-				{tRow{10, 20}},
-			},
-		},
-		// Struct with unnamed fields.
-		{
-			struct {
-				A string `spanner:""`
-			}{"hello"},
-			"SELECT * FROM UNNEST([@p]) ",
-			[]string{""},
-			tRows{
-				{tRow{"hello"}},
-			},
-		},
-		// Mixed struct.
-		{
-			struct {
-				DynamicStructField interface{}  `spanner:"f1"`
-				ArrayStructField   []*allFields `spanner:"f2"`
-			}{
-				DynamicStructField: s3.Interface(),
-				ArrayStructField:   []*allFields{nil},
-			},
-			"SELECT @p.f1.ff1, ARRAY_LENGTH(@p.f2), @p.f2[OFFSET(0)] IS NULL ",
-			[]string{"ff1", "", ""},
-			tRows{
-				{tRow{t1, 1, true}},
-			},
-		},
-	} {
-		iter := client.Single().Query(ctx, Statement{
-			SQL:    test.sql,
-			Params: map[string]interface{}{"p": test.param},
-		})
-		var gotRows []*Row
-		err := iter.Do(func(r *Row) error {
-			gotRows = append(gotRows, r)
-			return nil
-		})
-		if err != nil {
-			t.Errorf("Failed to execute test case %d, error: %v", i, err)
-		}
-
-		var wantRows []*Row
-		for j, row := range test.trows {
-			r, err := NewRow(test.cols, row.trow)
-			if err != nil {
-				t.Errorf("Invalid row %d in test case %d", j, i)
-			}
-			wantRows = append(wantRows, r)
-		}
-		if !testEqual(gotRows, wantRows) {
-			t.Errorf("%d: Want result %v, got result %v", i, wantRows, gotRows)
-		}
-	}
-}
diff --git a/spanner/not_go17.go b/spanner/not_go17.go
deleted file mode 100644
index 19b2c4a..0000000
--- a/spanner/not_go17.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-// +build !go1.7
-
-package spanner
-
-import (
-	"reflect"
-	"strconv"
-)
-
-func structTagLookup(tag reflect.StructTag, key string) (string, bool) {
-	// from go1.10.2 implementation of StructTag.Lookup.
-	for tag != "" {
-		// Skip leading space.
-		i := 0
-		for i < len(tag) && tag[i] == ' ' {
-			i++
-		}
-		tag = tag[i:]
-		if tag == "" {
-			break
-		}
-
-		// Scan to colon. A space, a quote or a control character is a syntax error.
-		// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
-		// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
-		// as it is simpler to inspect the tag's bytes than the tag's runes.
-		i = 0
-		for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
-			i++
-		}
-		if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
-			break
-		}
-		name := string(tag[:i])
-		tag = tag[i+1:]
-
-		// Scan quoted string to find value.
-		i = 1
-		for i < len(tag) && tag[i] != '"' {
-			if tag[i] == '\\' {
-				i++
-			}
-			i++
-		}
-		if i >= len(tag) {
-			break
-		}
-		qvalue := string(tag[:i+1])
-		tag = tag[i+1:]
-
-		if key == name {
-			value, err := strconv.Unquote(qvalue)
-			if err != nil {
-				break
-			}
-			return value, true
-		}
-	}
-	return "", false
-}
diff --git a/spanner/not_go18.go b/spanner/not_go18.go
deleted file mode 100644
index 41c3481..0000000
--- a/spanner/not_go18.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 Google LLC
-//
-// 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.
-
-// +build !go1.8
-
-package spanner
-
-import "context"
-
-// OpenCensus only supports go 1.8 and higher.
-
-func traceStartSpan(ctx context.Context, _ string) context.Context {
-	return ctx
-}
-
-func traceEndSpan(context.Context, error) {
-}
-
-func tracePrintf(context.Context, map[string]interface{}, string, ...interface{}) {
-}
-
-type dummy struct{}
-
-// Not supported below Go 1.8.
-var OpenSessionCount dummy
-
-func recordStat(context.Context, dummy, int64) {
-}
diff --git a/spanner/oc_test.go b/spanner/oc_test.go
index dbe93e7..7e95713 100644
--- a/spanner/oc_test.go
+++ b/spanner/oc_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package spanner
 
 import (
diff --git a/spanner/go18.go b/spanner/trace.go
similarity index 98%
rename from spanner/go18.go
rename to spanner/trace.go
index 9f09bf3..6fdbae4 100644
--- a/spanner/go18.go
+++ b/spanner/trace.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package spanner
 
 import (
diff --git a/spanner/value.go b/spanner/value.go
index 1e32a87..8e049d3 100644
--- a/spanner/value.go
+++ b/spanner/value.go
@@ -1487,7 +1487,7 @@
 			continue
 		}
 
-		fname, ok := structTagLookup(sf.Tag, "spanner")
+		fname, ok := sf.Tag.Lookup("spanner")
 		if !ok {
 			fname = sf.Name
 		}
diff --git a/spanner/value_benchmarks_test.go b/spanner/value_benchmarks_test.go
index fe09f00..32ea2d7 100644
--- a/spanner/value_benchmarks_test.go
+++ b/spanner/value_benchmarks_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.7
-
 package spanner
 
 import (
diff --git a/spanner/value_test.go b/spanner/value_test.go
index 5be8df3..e9ca587 100644
--- a/spanner/value_test.go
+++ b/spanner/value_test.go
@@ -17,12 +17,14 @@
 package spanner
 
 import (
+	"context"
 	"math"
 	"reflect"
 	"testing"
 	"time"
 
 	"cloud.google.com/go/civil"
+	proto "github.com/golang/protobuf/proto"
 	proto3 "github.com/golang/protobuf/ptypes/struct"
 	sppb "google.golang.org/genproto/googleapis/spanner/v1"
 )
@@ -1114,3 +1116,440 @@
 		}
 	}
 }
+
+func TestEncodeStructValueDynamicStructs(t *testing.T) {
+	dynStructType := reflect.StructOf([]reflect.StructField{
+		{Name: "A", Type: reflect.TypeOf(0), Tag: `spanner:"a"`},
+		{Name: "B", Type: reflect.TypeOf(""), Tag: `spanner:"b"`},
+	})
+	dynNullableStructType := reflect.PtrTo(dynStructType)
+	dynStructArrType := reflect.SliceOf(dynStructType)
+	dynNullableStructArrType := reflect.SliceOf(dynNullableStructType)
+
+	dynStructValue := reflect.New(dynStructType)
+	dynStructValue.Elem().Field(0).SetInt(10)
+	dynStructValue.Elem().Field(1).SetString("abc")
+
+	dynStructArrValue := reflect.MakeSlice(dynNullableStructArrType, 2, 2)
+	dynStructArrValue.Index(0).Set(reflect.Zero(dynNullableStructType))
+	dynStructArrValue.Index(1).Set(dynStructValue)
+
+	structProtoType := structType(
+		mkField("a", intType()),
+		mkField("b", stringType()))
+
+	arrProtoType := listType(structProtoType)
+
+	for _, test := range []encodeTest{
+		{
+			"Dynanic non-NULL struct value.",
+			dynStructValue.Elem().Interface(),
+			listProto(intProto(10), stringProto("abc")),
+			structProtoType,
+		},
+		{
+			"Dynanic NULL struct value.",
+			reflect.Zero(dynNullableStructType).Interface(),
+			nullProto(),
+			structProtoType,
+		},
+		{
+			"Empty array of dynamic structs.",
+			reflect.MakeSlice(dynStructArrType, 0, 0).Interface(),
+			listProto([]*proto3.Value{}...),
+			arrProtoType,
+		},
+		{
+			"NULL array of non-NULL-able dynamic structs.",
+			reflect.Zero(dynStructArrType).Interface(),
+			nullProto(),
+			arrProtoType,
+		},
+		{
+			"NULL array of NULL-able(nil) dynamic structs.",
+			reflect.Zero(dynNullableStructArrType).Interface(),
+			nullProto(),
+			arrProtoType,
+		},
+		{
+			"Array containing NULL(nil) dynamic-typed struct elements.",
+			dynStructArrValue.Interface(),
+			listProto(
+				nullProto(),
+				listProto(intProto(10), stringProto("abc"))),
+			arrProtoType,
+		},
+	} {
+		encodeStructValue(test, t)
+	}
+}
+
+func TestEncodeStructValueEmptyStruct(t *testing.T) {
+	emptyListValue := listProto([]*proto3.Value{}...)
+	emptyStructType := structType([]*sppb.StructType_Field{}...)
+	emptyStruct := struct{}{}
+	nullEmptyStruct := (*struct{})(nil)
+
+	dynamicEmptyStructType := reflect.StructOf(make([]reflect.StructField, 0, 0))
+	dynamicStructArrType := reflect.SliceOf(reflect.PtrTo((dynamicEmptyStructType)))
+
+	dynamicEmptyStruct := reflect.New(dynamicEmptyStructType)
+	dynamicNullEmptyStruct := reflect.Zero(reflect.PtrTo(dynamicEmptyStructType))
+
+	dynamicStructArrValue := reflect.MakeSlice(dynamicStructArrType, 2, 2)
+	dynamicStructArrValue.Index(0).Set(dynamicNullEmptyStruct)
+	dynamicStructArrValue.Index(1).Set(dynamicEmptyStruct)
+
+	for _, test := range []encodeTest{
+		{
+			"Go empty struct.",
+			emptyStruct,
+			emptyListValue,
+			emptyStructType,
+		},
+		{
+			"Dynamic empty struct.",
+			dynamicEmptyStruct.Interface(),
+			emptyListValue,
+			emptyStructType,
+		},
+		{
+			"Go NULL empty struct.",
+			nullEmptyStruct,
+			nullProto(),
+			emptyStructType,
+		},
+		{
+			"Dynamic NULL empty struct.",
+			dynamicNullEmptyStruct.Interface(),
+			nullProto(),
+			emptyStructType,
+		},
+		{
+			"Non-empty array of dynamic NULL and non-NULL empty structs.",
+			dynamicStructArrValue.Interface(),
+			listProto(nullProto(), emptyListValue),
+			listType(emptyStructType),
+		},
+		{
+			"Non-empty array of nullable empty structs.",
+			[]*struct{}{nullEmptyStruct, &emptyStruct},
+			listProto(nullProto(), emptyListValue),
+			listType(emptyStructType),
+		},
+		{
+			"Empty array of empty struct.",
+			[]struct{}{},
+			emptyListValue,
+			listType(emptyStructType),
+		},
+		{
+			"Null array of empty structs.",
+			[]struct{}(nil),
+			nullProto(),
+			listType(emptyStructType),
+		},
+	} {
+		encodeStructValue(test, t)
+	}
+}
+
+func TestEncodeStructValueMixedStructTypes(t *testing.T) {
+	type staticStruct struct {
+		F int `spanner:"fStatic"`
+	}
+	s1 := staticStruct{10}
+	s2 := (*staticStruct)(nil)
+
+	var f float64
+	dynStructType := reflect.StructOf([]reflect.StructField{
+		{Name: "A", Type: reflect.TypeOf(f), Tag: `spanner:"fDynamic"`},
+	})
+	s3 := reflect.New(dynStructType)
+	s3.Elem().Field(0).SetFloat(3.14)
+
+	for _, test := range []encodeTest{
+		{
+			"'struct' with static and dynamic *struct, []*struct, []struct fields",
+			struct {
+				A []staticStruct
+				B []*staticStruct
+				C interface{}
+			}{
+				[]staticStruct{s1, s1},
+				[]*staticStruct{&s1, s2},
+				s3.Interface(),
+			},
+			listProto(
+				listProto(listProto(intProto(10)), listProto(intProto(10))),
+				listProto(listProto(intProto(10)), nullProto()),
+				listProto(floatProto(3.14))),
+			structType(
+				mkField("A", listType(structType(mkField("fStatic", intType())))),
+				mkField("B", listType(structType(mkField("fStatic", intType())))),
+				mkField("C", structType(mkField("fDynamic", floatType())))),
+		},
+	} {
+		encodeStructValue(test, t)
+	}
+}
+
+func TestBindParamsDynamic(t *testing.T) {
+	// Verify Statement.bindParams generates correct values and types.
+	st := Statement{
+		SQL:    "SELECT id from t_foo WHERE col = @var",
+		Params: map[string]interface{}{"var": nil},
+	}
+	want := &sppb.ExecuteSqlRequest{
+		Params: &proto3.Struct{
+			Fields: map[string]*proto3.Value{"var": nil},
+		},
+		ParamTypes: map[string]*sppb.Type{"var": nil},
+	}
+	var (
+		t1, _ = time.Parse(time.RFC3339Nano, "2016-11-15T15:04:05.999999999Z")
+		// Boundaries
+		t2, _ = time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00.000000000Z")
+	)
+	dynamicStructType := reflect.StructOf([]reflect.StructField{
+		{Name: "A", Type: reflect.TypeOf(t1), Tag: `spanner:"field"`},
+		{Name: "B", Type: reflect.TypeOf(3.14), Tag: `spanner:""`},
+	})
+	dynamicStructArrType := reflect.SliceOf(reflect.PtrTo(dynamicStructType))
+	dynamicEmptyStructType := reflect.StructOf(make([]reflect.StructField, 0, 0))
+
+	dynamicStructTypeProto := structType(
+		mkField("field", timeType()),
+		mkField("", floatType()))
+
+	s3 := reflect.New(dynamicStructType)
+	s3.Elem().Field(0).Set(reflect.ValueOf(t1))
+	s3.Elem().Field(1).SetFloat(1.4)
+
+	s4 := reflect.New(dynamicStructType)
+	s4.Elem().Field(0).Set(reflect.ValueOf(t2))
+	s4.Elem().Field(1).SetFloat(-13.3)
+
+	dynamicStructArrayVal := reflect.MakeSlice(dynamicStructArrType, 2, 2)
+	dynamicStructArrayVal.Index(0).Set(s3)
+	dynamicStructArrayVal.Index(1).Set(s4)
+
+	for i, test := range []struct {
+		val       interface{}
+		wantField *proto3.Value
+		wantType  *sppb.Type
+	}{
+		{
+			s3.Interface(),
+			listProto(timeProto(t1), floatProto(1.4)),
+			structType(
+				mkField("field", timeType()),
+				mkField("", floatType())),
+		},
+		{
+			reflect.Zero(reflect.PtrTo(dynamicEmptyStructType)).Interface(),
+			nullProto(),
+			structType([]*sppb.StructType_Field{}...),
+		},
+		{
+			dynamicStructArrayVal.Interface(),
+			listProto(
+				listProto(timeProto(t1), floatProto(1.4)),
+				listProto(timeProto(t2), floatProto(-13.3))),
+			listType(dynamicStructTypeProto),
+		},
+		{
+			[]*struct {
+				F1 time.Time `spanner:"field"`
+				F2 float64   `spanner:""`
+			}{
+				nil,
+				{t1, 1.4},
+			},
+			listProto(
+				nullProto(),
+				listProto(timeProto(t1), floatProto(1.4))),
+			listType(dynamicStructTypeProto),
+		},
+	} {
+		st.Params["var"] = test.val
+		want.Params.Fields["var"] = test.wantField
+		want.ParamTypes["var"] = test.wantType
+		got := &sppb.ExecuteSqlRequest{}
+		if err := st.bindParams(got); err != nil || !proto.Equal(got, want) {
+			// handle NaN
+			if test.wantType.Code == floatType().Code && proto.MarshalTextString(got) == proto.MarshalTextString(want) {
+				continue
+			}
+			t.Errorf("#%d: bind result: \n(%v, %v)\nwant\n(%v, %v)\n", i, got, err, want, nil)
+		}
+	}
+}
+
+func TestStructParametersBind(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+	client, _, cleanup := prepare(ctx, t, nil)
+	defer cleanup()
+
+	type tRow []interface{}
+	type tRows []struct{ trow tRow }
+
+	type allFields struct {
+		Stringf string
+		Intf    int
+		Boolf   bool
+		Floatf  float64
+		Bytef   []byte
+		Timef   time.Time
+		Datef   civil.Date
+	}
+	allColumns := []string{
+		"Stringf",
+		"Intf",
+		"Boolf",
+		"Floatf",
+		"Bytef",
+		"Timef",
+		"Datef",
+	}
+	s1 := allFields{"abc", 300, false, 3.45, []byte("foo"), t1, d1}
+	s2 := allFields{"def", -300, false, -3.45, []byte("bar"), t2, d2}
+
+	dynamicStructType := reflect.StructOf([]reflect.StructField{
+		{Name: "A", Type: reflect.TypeOf(t1), Tag: `spanner:"ff1"`},
+	})
+	s3 := reflect.New(dynamicStructType)
+	s3.Elem().Field(0).Set(reflect.ValueOf(t1))
+
+	for i, test := range []struct {
+		param interface{}
+		sql   string
+		cols  []string
+		trows tRows
+	}{
+		// Struct value.
+		{
+			s1,
+			"SELECT" +
+				" @p.Stringf," +
+				" @p.Intf," +
+				" @p.Boolf," +
+				" @p.Floatf," +
+				" @p.Bytef," +
+				" @p.Timef," +
+				" @p.Datef",
+			allColumns,
+			tRows{
+				{tRow{"abc", 300, false, 3.45, []byte("foo"), t1, d1}},
+			},
+		},
+		// Array of struct value.
+		{
+			[]allFields{s1, s2},
+			"SELECT * FROM UNNEST(@p)",
+			allColumns,
+			tRows{
+				{tRow{"abc", 300, false, 3.45, []byte("foo"), t1, d1}},
+				{tRow{"def", -300, false, -3.45, []byte("bar"), t2, d2}},
+			},
+		},
+		// Null struct.
+		{
+			(*allFields)(nil),
+			"SELECT @p IS NULL",
+			[]string{""},
+			tRows{
+				{tRow{true}},
+			},
+		},
+		// Null Array of struct.
+		{
+			[]allFields(nil),
+			"SELECT @p IS NULL",
+			[]string{""},
+			tRows{
+				{tRow{true}},
+			},
+		},
+		// Empty struct.
+		{
+			struct{}{},
+			"SELECT @p IS NULL ",
+			[]string{""},
+			tRows{
+				{tRow{false}},
+			},
+		},
+		// Empty array of struct.
+		{
+			[]allFields{},
+			"SELECT * FROM UNNEST(@p) ",
+			allColumns,
+			tRows{},
+		},
+		// Struct with duplicate fields.
+		{
+			struct {
+				A int `spanner:"field"`
+				B int `spanner:"field"`
+			}{10, 20},
+			"SELECT * FROM UNNEST([@p]) ",
+			[]string{"field", "field"},
+			tRows{
+				{tRow{10, 20}},
+			},
+		},
+		// Struct with unnamed fields.
+		{
+			struct {
+				A string `spanner:""`
+			}{"hello"},
+			"SELECT * FROM UNNEST([@p]) ",
+			[]string{""},
+			tRows{
+				{tRow{"hello"}},
+			},
+		},
+		// Mixed struct.
+		{
+			struct {
+				DynamicStructField interface{}  `spanner:"f1"`
+				ArrayStructField   []*allFields `spanner:"f2"`
+			}{
+				DynamicStructField: s3.Interface(),
+				ArrayStructField:   []*allFields{nil},
+			},
+			"SELECT @p.f1.ff1, ARRAY_LENGTH(@p.f2), @p.f2[OFFSET(0)] IS NULL ",
+			[]string{"ff1", "", ""},
+			tRows{
+				{tRow{t1, 1, true}},
+			},
+		},
+	} {
+		iter := client.Single().Query(ctx, Statement{
+			SQL:    test.sql,
+			Params: map[string]interface{}{"p": test.param},
+		})
+		var gotRows []*Row
+		err := iter.Do(func(r *Row) error {
+			gotRows = append(gotRows, r)
+			return nil
+		})
+		if err != nil {
+			t.Errorf("Failed to execute test case %d, error: %v", i, err)
+		}
+
+		var wantRows []*Row
+		for j, row := range test.trows {
+			r, err := NewRow(test.cols, row.trow)
+			if err != nil {
+				t.Errorf("Invalid row %d in test case %d", j, i)
+			}
+			wantRows = append(wantRows, r)
+		}
+		if !testEqual(gotRows, wantRows) {
+			t.Errorf("%d: Want result %v, got result %v", i, wantRows, gotRows)
+		}
+	}
+}
diff --git a/storage/go17.go b/storage/go17.go
deleted file mode 100644
index 11b5982..0000000
--- a/storage/go17.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2017 Google LLC
-//
-// 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.
-
-// +build go1.7
-
-package storage
-
-import (
-	"context"
-	"net/http"
-)
-
-func withContext(ctx context.Context, r *http.Request) *http.Request {
-	return r.WithContext(ctx)
-}
-
-func goHTTPUncompressed(res *http.Response) bool {
-	return res.Uncompressed
-}
diff --git a/storage/not_go17.go b/storage/not_go17.go
deleted file mode 100644
index 744985f..0000000
--- a/storage/not_go17.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 Google LLC
-//
-// 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.
-
-// +build !go1.7
-
-package storage
-
-import (
-	"net/http"
-)
-
-func withContext(_ interface{}, r *http.Request) *http.Request {
-	// In Go 1.6 and below, ignore the context.
-	return r
-}
-
-// Go 1.6 doesn't have http.Response.Uncompressed, so we can't know whether the Go
-// HTTP stack uncompressed a gzip file. As a good approximation, assume that
-// the lack of a Content-Length header means that it did uncompress.
-func goHTTPUncompressed(res *http.Response) bool {
-	return res.Header.Get("Content-Length") == ""
-}
diff --git a/storage/oc_test.go b/storage/oc_test.go
index 7bb9d12..2143396 100644
--- a/storage/oc_test.go
+++ b/storage/oc_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.8
-
 package storage
 
 import (
diff --git a/storage/reader.go b/storage/reader.go
index 33c5d87..50f381f 100644
--- a/storage/reader.go
+++ b/storage/reader.go
@@ -107,7 +107,7 @@
 	if err != nil {
 		return nil, err
 	}
-	req = withContext(ctx, req)
+	req = req.WithContext(ctx)
 	if o.userProject != "" {
 		req.Header.Set("X-Goog-User-Project", o.userProject)
 	}
@@ -202,7 +202,7 @@
 		// The problem with the last two cases is that the CRC will not match -- GCS
 		// computes it on the compressed contents, but we compute it on the
 		// uncompressed contents.
-		if length != 0 && !goHTTPUncompressed(res) && !uncompressedByServer(res) {
+		if length != 0 && !res.Uncompressed && !uncompressedByServer(res) {
 			crc, checkCRC = parseCRC32c(res)
 		}
 	}
diff --git a/trace/http.go b/trace/http.go
index 7eae322..6a64465 100644
--- a/trace/http.go
+++ b/trace/http.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.7
-
 package trace
 
 import (
diff --git a/trace/http_test.go b/trace/http_test.go
index bf8ebd3..93cc1b0 100644
--- a/trace/http_test.go
+++ b/trace/http_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.7
-
 package trace
 
 import (
diff --git a/trace/httpexample_test.go b/trace/httpexample_test.go
index 4eaee09..2af4ead 100644
--- a/trace/httpexample_test.go
+++ b/trace/httpexample_test.go
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build go1.7
-
 package trace_test
 
 import (