func parseQueryResult(iter *spanner.RowIterator) ([]Row, []string, error) {
	var rows []Row
	var columnNames []string
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, nil, err
		}
		if len(columnNames) == 0 {
			columnNames = row.ColumnNames()
		}
		columns, err := DecodeRow(row)
		if err != nil {
			return nil, nil, err
		}
		rows = append(rows, Row{
			Columns: columns,
		})
	}
	return rows, columnNames, nil
}
parseQueryResult parses rows and columnNames from spanner.RowIterator.
A caller is responsible for calling iterator.Stop().
package main
import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"
	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
	pb "google.golang.org/genproto/googleapis/spanner/v1"
	"google.golang.org/grpc/codes"
)
const pdmlTimeout = time.Hour * 24
type Statement interface {
	Execute(session *Session) (*Result, error)
}
type rowCountType int
const (
	rowCountTypeExact rowCountType = iota
	rowCountTypeLowerBound
)
type Result struct {
	ColumnNames      []string
	Rows             []Row
	Predicates       []string
	AffectedRows     int
	AffectedRowsType rowCountType
	Stats            QueryStats
	IsMutation       bool
	Timestamp        time.Time
	ForceVerbose     bool
	CommitStats      *pb.CommitResponse_CommitStats
}
type Row struct {
	Columns []string
}
type QueryStats struct {
	ElapsedTime                string
	CPUTime                    string
	RowsReturned               string
	RowsScanned                string
	DeletedRowsScanned         string
	OptimizerVersion           string
	OptimizerStatisticsPackage string
}
var (
	selectRe = regexp.MustCompile(`(?is)^(?:WITH|@{.+|SELECT)\s.+$`)
	createDatabaseRe      = regexp.MustCompile(`(?is)^CREATE\s+DATABASE\s.+$`)
	dropDatabaseRe        = regexp.MustCompile(`(?is)^DROP\s+DATABASE\s+(.+)$`)
	alterDatabaseRe       = regexp.MustCompile(`(?is)^ALTER\s+DATABASE\s.+$`)
	createTableRe         = regexp.MustCompile(`(?is)^CREATE\s+TABLE\s.+$`)
	alterTableRe          = regexp.MustCompile(`(?is)^ALTER\s+TABLE\s.+$`)
	dropTableRe           = regexp.MustCompile(`(?is)^DROP\s+TABLE\s.+$`)
	createIndexRe         = regexp.MustCompile(`(?is)^CREATE\s+(UNIQUE\s+)?(NULL_FILTERED\s+)?INDEX\s.+$`)
	dropIndexRe           = regexp.MustCompile(`(?is)^DROP\s+INDEX\s.+$`)
	truncateTableRe       = regexp.MustCompile(`(?is)^TRUNCATE\s+TABLE\s+(.+)$`)
	createViewRe          = regexp.MustCompile(`(?is)^CREATE\s+VIEW\s.+$`)
	createOrReplaceViewRe = regexp.MustCompile(`(?is)^CREATE\s+OR\s+REPLACE\s+VIEW\s.+$`)
	dropViewRe            = regexp.MustCompile(`(?is)^DROP\s+VIEW\s.+$`)
	alterStatisticsRe     = regexp.MustCompile(`(?is)^ALTER\s+STATISTICS\s.+$`)
	dmlRe = regexp.MustCompile(`(?is)^(INSERT|UPDATE|DELETE)\s+.+$`)
	pdmlRe = regexp.MustCompile(`(?is)^PARTITIONED\s+((?:INSERT|UPDATE|DELETE)\s+.+$)`)
	beginRwRe  = regexp.MustCompile(`(?is)^BEGIN(?:\s+RW)?(?:\s+PRIORITY\s+(HIGH|MEDIUM|LOW))?$`)
	beginRoRe  = regexp.MustCompile(`(?is)^BEGIN\s+RO(?:\s+([^\s]+))?(?:\s+PRIORITY\s+(HIGH|MEDIUM|LOW))?$`)
	commitRe   = regexp.MustCompile(`(?is)^COMMIT$`)
	rollbackRe = regexp.MustCompile(`(?is)^ROLLBACK$`)
	closeRe    = regexp.MustCompile(`(?is)^CLOSE$`)
	exitRe            = regexp.MustCompile(`(?is)^EXIT$`)
	useRe             = regexp.MustCompile(`(?is)^USE\s+(.+)$`)
	showDatabasesRe   = regexp.MustCompile(`(?is)^SHOW\s+DATABASES$`)
	showCreateTableRe = regexp.MustCompile(`(?is)^SHOW\s+CREATE\s+TABLE\s+(.+)$`)
	showTablesRe      = regexp.MustCompile(`(?is)^SHOW\s+TABLES$`)
	showColumnsRe     = regexp.MustCompile(`(?is)^(?:SHOW\s+COLUMNS\s+FROM)\s+(.+)$`)
	showIndexRe       = regexp.MustCompile(`(?is)^SHOW\s+(?:INDEX|INDEXES|KEYS)\s+FROM\s+(.+)$`)
	explainRe         = regexp.MustCompile(`(?is)^(?:EXPLAIN|DESC(?:RIBE)?)\s+(ANALYZE\s+)?(.+)$`)
)
var (
	explainColumnNames        = []string{"ID", "Query_Execution_Plan (EXPERIMENTAL)"}
	explainAnalyzeColumnNames = []string{"ID", "Query_Execution_Plan", "Rows_Returned", "Executions", "Total_Latency"}
)
func BuildStatement(input string) (Statement, error) {
	switch {
	case exitRe.MatchString(input):
		return &ExitStatement{}, nil
	case useRe.MatchString(input):
		matched := useRe.FindStringSubmatch(input)
		return &UseStatement{Database: unquoteIdentifier(matched[1])}, nil
	case selectRe.MatchString(input):
		return &SelectStatement{Query: input}, nil
	case createDatabaseRe.MatchString(input):
		return &CreateDatabaseStatement{CreateStatement: input}, nil
	case dropDatabaseRe.MatchString(input):
		matched := dropDatabaseRe.FindStringSubmatch(input)
		return &DropDatabaseStatement{DatabaseId: unquoteIdentifier(matched[1])}, nil
	case alterDatabaseRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case createTableRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case alterTableRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case dropTableRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case createIndexRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case dropIndexRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case truncateTableRe.MatchString(input):
		matched := truncateTableRe.FindStringSubmatch(input)
		return &TruncateTableStatement{Table: unquoteIdentifier(matched[1])}, nil
	case createViewRe.MatchString(input), createOrReplaceViewRe.MatchString(input), dropViewRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case alterStatisticsRe.MatchString(input):
		return &DdlStatement{Ddl: input}, nil
	case showDatabasesRe.MatchString(input):
		return &ShowDatabasesStatement{}, nil
	case showCreateTableRe.MatchString(input):
		matched := showCreateTableRe.FindStringSubmatch(input)
		return &ShowCreateTableStatement{Table: unquoteIdentifier(matched[1])}, nil
	case showTablesRe.MatchString(input):
		return &ShowTablesStatement{}, nil
	case explainRe.MatchString(input):
		matched := explainRe.FindStringSubmatch(input)
		isAnalyze := matched[1] != ""
		isDML := dmlRe.MatchString(matched[2])
		switch {
		case isAnalyze && isDML:
			return &ExplainAnalyzeDmlStatement{Dml: matched[2]}, nil
		case isAnalyze:
			return &ExplainAnalyzeStatement{Query: matched[2]}, nil
		case isDML:
			return &ExplainDmlStatement{Dml: matched[2]}, nil
		default:
			return &ExplainStatement{Explain: matched[2]}, nil
		}
	case showColumnsRe.MatchString(input):
		matched := showColumnsRe.FindStringSubmatch(input)
		return &ShowColumnsStatement{Table: unquoteIdentifier(matched[1])}, nil
	case showIndexRe.MatchString(input):
		matched := showIndexRe.FindStringSubmatch(input)
		return &ShowIndexStatement{Table: unquoteIdentifier(matched[1])}, nil
	case dmlRe.MatchString(input):
		return &DmlStatement{Dml: input}, nil
	case pdmlRe.MatchString(input):
		matched := pdmlRe.FindStringSubmatch(input)
		return &PartitionedDmlStatement{Dml: matched[1]}, nil
	case beginRwRe.MatchString(input):
		return newBeginRwStatement(input)
	case beginRoRe.MatchString(input):
		return newBeginRoStatement(input)
	case commitRe.MatchString(input):
		return &CommitStatement{}, nil
	case rollbackRe.MatchString(input):
		return &RollbackStatement{}, nil
	case closeRe.MatchString(input):
		return &CloseStatement{}, nil
	}
	return nil, errors.New("invalid statement")
}
func unquoteIdentifier(input string) string {
	return strings.Trim(input, "`")
}
type SelectStatement struct {
	Query string
}
func (s *SelectStatement) Execute(session *Session) (*Result, error) {
	stmt := spanner.NewStatement(s.Query)
	iter, roTxn := session.RunQueryWithStats(stmt)
	defer iter.Stop()
	rows, columnNames, err := parseQueryResult(iter)
	if err != nil {
		if session.InReadWriteTransaction() && spanner.ErrCode(err) == codes.Aborted {
			rollback := &RollbackStatement{}
			rollback.Execute(session)
		}
		return nil, err
	}
	result := &Result{
		ColumnNames: columnNames,
		Rows:        rows,
	}
	queryStats := parseQueryStats(iter.QueryStats)
	rowsReturned, err := strconv.Atoi(queryStats.RowsReturned)
	if err != nil {
		return nil, fmt.Errorf("rowsReturned is invalid: %v", err)
	}
	result.AffectedRows = rowsReturned
	result.Stats = queryStats
	if roTxn != nil {
		result.Timestamp, _ = roTxn.Timestamp()
	}
	return result, nil
}