func (s *Session) RunUpdate(stmt spanner.Statement) (int64, error) {
	if !s.InReadWriteTransaction() {
		return 0, errors.New("read-write transaction is not running")
	}
	opts := spanner.QueryOptions{
		Priority: s.currentPriority(),
	}
	rowCount, err := s.tc.rwTxn.UpdateWithOptions(s.ctx, stmt, opts)
	s.tc.sendHeartbeat = true
	return rowCount, err
}
RunUpdate executes a DML statement on the running read-write transaction.
It returns error if there is no running read-write transaction.
package main
import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
	"cloud.google.com/go/spanner"
	"google.golang.org/api/option"
	"google.golang.org/grpc/codes"
	adminapi "cloud.google.com/go/spanner/admin/database/apiv1"
	pb "google.golang.org/genproto/googleapis/spanner/v1"
)
var clientConfig = spanner.ClientConfig{
	SessionPoolConfig: spanner.SessionPoolConfig{
		MinOpened: 1,
		MaxOpened: 10,  
	},
}
var defaultClientOpts = []option.ClientOption{
	option.WithGRPCConnectionPool(1),
}
const defaultPriority = pb.RequestOptions_PRIORITY_MEDIUM
type Session struct {
	ctx             context.Context
	projectId       string
	instanceId      string
	databaseId      string
	client          *spanner.Client
	adminClient     *adminapi.DatabaseAdminClient
	clientOpts      []option.ClientOption
	defaultPriority pb.RequestOptions_Priority
	tc              *transactionContext
	tcMutex         sync.Mutex  
}
type transactionContext struct {
	priority      pb.RequestOptions_Priority
	sendHeartbeat bool  
	rwTxn         *spanner.ReadWriteStmtBasedTransaction
	roTxn         *spanner.ReadOnlyTransaction
}
func NewSession(ctx context.Context, projectId string, instanceId string, databaseId string, priority pb.RequestOptions_Priority, opts ...option.ClientOption) (*Session, error) {
	dbPath := fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId)
	opts = append(opts, defaultClientOpts...)
	client, err := spanner.NewClientWithConfig(ctx, dbPath, clientConfig, opts...)
	if err != nil {
		return nil, err
	}
	adminClient, err := adminapi.NewDatabaseAdminClient(ctx, opts...)
	if err != nil {
		return nil, err
	}
	if priority == pb.RequestOptions_PRIORITY_UNSPECIFIED {
		priority = defaultPriority
	}
	session := &Session{
		ctx:             ctx,
		projectId:       projectId,
		instanceId:      instanceId,
		databaseId:      databaseId,
		client:          client,
		clientOpts:      opts,
		adminClient:     adminClient,
		defaultPriority: priority,
	}
	go session.startHeartbeat()
	return session, nil
}
func (s *Session) InReadWriteTransaction() bool {
	return s.tc != nil && s.tc.rwTxn != nil
}
func (s *Session) InReadOnlyTransaction() bool {
	return s.tc != nil && s.tc.roTxn != nil
}
func (s *Session) BeginReadWriteTransaction(priority pb.RequestOptions_Priority) error {
	if s.InReadWriteTransaction() {
		return errors.New("read-write transaction is already running")
	}
	if priority == pb.RequestOptions_PRIORITY_UNSPECIFIED {
		priority = s.defaultPriority
	}
	opts := spanner.TransactionOptions{
		CommitOptions:  spanner.CommitOptions{ReturnCommitStats: true},
		CommitPriority: priority,
	}
	txn, err := spanner.NewReadWriteStmtBasedTransactionWithOptions(s.ctx, s.client, opts)
	if err != nil {
		return err
	}
	s.tc = &transactionContext{
		priority: priority,
		rwTxn:    txn,
	}
	return nil
}
func (s *Session) CommitReadWriteTransaction() (spanner.CommitResponse, error) {
	if !s.InReadWriteTransaction() {
		return spanner.CommitResponse{}, errors.New("read-write transaction is not running")
	}
	s.tcMutex.Lock()
	defer s.tcMutex.Unlock()
	resp, err := s.tc.rwTxn.CommitWithReturnResp(s.ctx)
	s.tc = nil
	return resp, err
}
func (s *Session) RollbackReadWriteTransaction() error {
	if !s.InReadWriteTransaction() {
		return errors.New("read-write transaction is not running")
	}
	s.tcMutex.Lock()
	defer s.tcMutex.Unlock()
	s.tc.rwTxn.Rollback(s.ctx)
	s.tc = nil
	return nil
}
func (s *Session) BeginReadOnlyTransaction(typ timestampBoundType, staleness time.Duration, timestamp time.Time, priority pb.RequestOptions_Priority) (time.Time, error) {
	if s.InReadOnlyTransaction() {
		return time.Time{}, errors.New("read-only transaction is already running")
	}
	txn := s.client.ReadOnlyTransaction()
	switch typ {
	case strong:
		txn = txn.WithTimestampBound(spanner.StrongRead())
	case exactStaleness:
		txn = txn.WithTimestampBound(spanner.ExactStaleness(staleness))
	case readTimestamp:
		txn = txn.WithTimestampBound(spanner.ReadTimestamp(timestamp))
	}
	if priority == pb.RequestOptions_PRIORITY_UNSPECIFIED {
		priority = s.defaultPriority
	}
	opts := spanner.QueryOptions{Priority: priority}
	if err := txn.QueryWithOptions(s.ctx, spanner.NewStatement("SELECT 1"), opts).Do(func(r *spanner.Row) error {
		return nil
	}); err != nil {
		return time.Time{}, err
	}
	s.tc = &transactionContext{
		priority: priority,
		roTxn:    txn,
	}
	return txn.Timestamp()
}
func (s *Session) CloseReadOnlyTransaction() error {
	if !s.InReadOnlyTransaction() {
		return errors.New("read-only transaction is not running")
	}
	s.tc.roTxn.Close()
	s.tc = nil
	return nil
}
func (s *Session) RunQueryWithStats(stmt spanner.Statement) (*spanner.RowIterator, *spanner.ReadOnlyTransaction) {
	mode := pb.ExecuteSqlRequest_PROFILE
	opts := spanner.QueryOptions{
		Mode:     &mode,
		Priority: s.currentPriority(),
	}
	return s.runQueryWithOptions(stmt, opts)
}
func (s *Session) RunQuery(stmt spanner.Statement) (*spanner.RowIterator, *spanner.ReadOnlyTransaction) {
	opts := spanner.QueryOptions{
		Priority: s.currentPriority(),
	}
	return s.runQueryWithOptions(stmt, opts)
}
func (s *Session) RunAnalyzeQuery(stmt spanner.Statement) (*pb.QueryPlan, error) {
	mode := pb.ExecuteSqlRequest_PLAN
	opts := spanner.QueryOptions{
		Mode:     &mode,
		Priority: s.currentPriority(),
	}
	iter, _ := s.runQueryWithOptions(stmt, opts)
	iter.Do(func(r *spanner.Row) error {
		return nil
	})
	if iter.QueryPlan == nil {
		return nil, errors.New("query plan unavailable")
	}
	return iter.QueryPlan, nil
}
func (s *Session) runQueryWithOptions(stmt spanner.Statement, opts spanner.QueryOptions) (*spanner.RowIterator, *spanner.ReadOnlyTransaction) {
	if s.InReadWriteTransaction() {
		iter := s.tc.rwTxn.QueryWithOptions(s.ctx, stmt, opts)
		s.tc.sendHeartbeat = true
		return iter, nil
	}
	if s.InReadOnlyTransaction() {
		return s.tc.roTxn.QueryWithOptions(s.ctx, stmt, opts), s.tc.roTxn
	}
	txn := s.client.Single()
	return txn.QueryWithOptions(s.ctx, stmt, opts), txn
}