Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add typename in header and add DESCRIBE statement #191

Merged
merged 21 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ and `{}` for a mutually exclusive keyword.
| Show DML Execution Plan | `EXPLAIN {INSERT\|UPDATE\|DELETE} ...;` | |
| Show Query Execution Plan with Stats | `EXPLAIN ANALYZE SELECT ...;` | |
| Show DML Execution Plan with Stats | `EXPLAIN ANALYZE {INSERT\|UPDATE\|DELETE} ...;` | |
| Show Query Result Shape | `DESCRIBE SELECT ...;` | |
| Show DML Result Shape | `DESCRIBE {INSERT\|UPDATE\|DELETE} ... THEN RETURN ...;` | |
| Start a new query optimizer statistics package construction | `ANALYZE;` | |
| Start Read-Write Transaction | `BEGIN [RW] [PRIORITY {HIGH\|MEDIUM\|LOW}] [TAG <tag>];` | See [Request Priority](#request-priority) for details on the priority. The tag you set is used as both transaction tag and request tag. See also [Transaction Tags and Request Tags](#transaction-tags-and-request-tags).|
| Commit Read-Write Transaction | `COMMIT;` | |
Expand Down
18 changes: 16 additions & 2 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,25 @@ func printResult(out io.Writer, result *Result, mode DisplayMode, interactive, v
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false)

var forceTableRender bool
// This condition is true if statement is SelectStatement or DmlStatement
if verbose && len(result.ColumnTypes) > 0 {
forceTableRender = true
var headers []string
for _, field := range result.ColumnTypes {
typename := formatTypeSimple(field.GetType())
headers = append(headers, field.GetName()+"\n"+typename)
}
table.SetHeader(headers)
} else {
table.SetHeader(result.ColumnNames)
}

for _, row := range result.Rows {
table.Append(row.Columns)
}
table.SetHeader(result.ColumnNames)
if len(result.Rows) > 0 {

if forceTableRender || len(result.Rows) > 0 {
yfuruyama marked this conversation as resolved.
Show resolved Hide resolved
table.Render()
}
} else if mode == DisplayModeVertical {
Expand Down
38 changes: 38 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,41 @@ func nullJSONToString(v spanner.NullJSON) string {
return "NULL"
}
}

func formatTypeSimple(typ *sppb.Type) string {
switch code := typ.GetCode(); code {
case sppb.TypeCode_ARRAY:
return fmt.Sprintf("ARRAY<%v>", formatTypeSimple(typ.GetArrayElementType()))
default:
if name, ok := sppb.TypeCode_name[int32(code)]; ok {
return name
} else {
return "UNKNOWN"
}
}
}

func formatTypeVerbose(typ *sppb.Type) string {
switch code := typ.GetCode(); code {
case sppb.TypeCode_ARRAY:
return fmt.Sprintf("ARRAY<%v>", formatTypeVerbose(typ.GetArrayElementType()))
case sppb.TypeCode_ENUM, sppb.TypeCode_PROTO:
return typ.GetProtoTypeFqn()
case sppb.TypeCode_STRUCT:
var structTypeStrs []string
for _, v := range typ.GetStructType().GetFields() {
if v.GetName() != "" {
structTypeStrs = append(structTypeStrs, fmt.Sprintf("%v %v", v.GetName(), formatTypeVerbose(v.GetType())))
} else {
structTypeStrs = append(structTypeStrs, fmt.Sprintf("%v", formatTypeVerbose(v.GetType())))
}
}
return fmt.Sprintf("STRUCT<%v>", strings.Join(structTypeStrs, ", "))
default:
if name, ok := sppb.TypeCode_name[int32(code)]; ok {
return name
} else {
return "UNKNOWN"
}
}
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ require (
cloud.google.com/go/spanner v1.62.0
github.com/apstndb/gsqlsep v0.0.0-20230324124551-0e8335710080
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/davecgh/go-spew v1.1.1
github.com/google/go-cmp v0.6.0
github.com/jessevdk/go-flags v1.4.0
github.com/olekukonko/tablewriter v0.0.4
github.com/olekukonko/tablewriter v0.0.5
github.com/xlab/treeprint v1.0.1-0.20200715141336-10e0bc383e01
google.golang.org/api v0.180.0
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda
Expand Down Expand Up @@ -38,7 +39,7 @@ require (
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
Expand Down
9 changes: 4 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -849,14 +849,13 @@ github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
Expand Down
18 changes: 17 additions & 1 deletion integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"context"
"fmt"
"google.golang.org/protobuf/testing/protocmp"
"os"
"strings"
"sync/atomic"
Expand Down Expand Up @@ -142,11 +143,13 @@ func setup(t *testing.T, ctx context.Context, dmls []string) (*Session, string,
}

func compareResult(t *testing.T, got *Result, expected *Result) {
t.Helper()
opts := []cmp.Option{
cmpopts.IgnoreFields(Result{}, "Stats"),
cmpopts.IgnoreFields(Result{}, "Timestamp"),
// Commit Stats is only provided by real instances
cmpopts.IgnoreFields(Result{}, "CommitStats"),
protocmp.Transform(),
}
if !cmp.Equal(got, expected, opts...) {
t.Errorf("diff: %s", cmp.Diff(got, expected, opts...))
Expand Down Expand Up @@ -183,7 +186,11 @@ func TestSelect(t *testing.T) {
Row{[]string{"2", "false"}},
},
AffectedRows: 2,
IsMutation: false,
ColumnTypes: []*pb.StructType_Field{
{Name: "id", Type: &pb.Type{Code: pb.TypeCode_INT64}},
{Name: "active", Type: &pb.Type{Code: pb.TypeCode_BOOL}},
},
IsMutation: false,
})
}

Expand Down Expand Up @@ -481,6 +488,11 @@ func TestReadOnlyTransaction(t *testing.T) {
Row{[]string{"1", "true"}},
Row{[]string{"2", "false"}},
},

ColumnTypes: []*pb.StructType_Field{
{Name: "id", Type: &pb.Type{Code: pb.TypeCode_INT64}},
{Name: "active", Type: &pb.Type{Code: pb.TypeCode_BOOL}},
},
AffectedRows: 2,
IsMutation: false,
})
Expand Down Expand Up @@ -552,6 +564,10 @@ func TestReadOnlyTransaction(t *testing.T) {
Row{[]string{"1", "true"}},
Row{[]string{"2", "false"}},
},
ColumnTypes: []*pb.StructType_Field{
{Name: "id", Type: &pb.Type{Code: pb.TypeCode_INT64}},
{Name: "active", Type: &pb.Type{Code: pb.TypeCode_BOOL}},
},
AffectedRows: 2,
IsMutation: false,
})
Expand Down
18 changes: 9 additions & 9 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func (s *Session) RunQuery(ctx context.Context, stmt spanner.Statement) (*spanne
}

// RunAnalyzeQuery analyzes a statement either on the running transaction or on the temporal read-only transaction.
func (s *Session) RunAnalyzeQuery(ctx context.Context, stmt spanner.Statement) (*pb.QueryPlan, error) {
func (s *Session) RunAnalyzeQuery(ctx context.Context, stmt spanner.Statement) (*pb.QueryPlan, *pb.ResultSetMetadata, error) {
mode := pb.ExecuteSqlRequest_PLAN
opts := spanner.QueryOptions{
Mode: &mode,
Expand All @@ -251,13 +251,13 @@ func (s *Session) RunAnalyzeQuery(ctx context.Context, stmt spanner.Statement) (
iter, _ := s.runQueryWithOptions(ctx, stmt, opts)

// Need to read rows from iterator to get the query plan.
iter.Do(func(r *spanner.Row) error {
err := iter.Do(func(r *spanner.Row) error {
return nil
})
if iter.QueryPlan == nil {
return nil, errors.New("query plan unavailable")
if err != nil {
return nil, nil, err
}
return iter.QueryPlan, nil
return iter.QueryPlan, iter.Metadata, nil
}

func (s *Session) runQueryWithOptions(ctx context.Context, stmt spanner.Statement, opts spanner.QueryOptions) (*spanner.RowIterator, *spanner.ReadOnlyTransaction) {
Expand All @@ -282,9 +282,9 @@ func (s *Session) runQueryWithOptions(ctx context.Context, stmt spanner.Statemen
// RunUpdate executes a DML statement on the running read-write transaction.
// It returns error if there is no running read-write transaction.
// useUpdate flag enforce to use Update function internally and disable `THEN RETURN` result printing.
func (s *Session) RunUpdate(ctx context.Context, stmt spanner.Statement, useUpdate bool) ([]Row, []string, int64, error) {
func (s *Session) RunUpdate(ctx context.Context, stmt spanner.Statement, useUpdate bool) ([]Row, []string, int64, *pb.ResultSetMetadata, error) {
if !s.InReadWriteTransaction() {
return nil, nil, 0, errors.New("read-write transaction is not running")
return nil, nil, 0, nil, errors.New("read-write transaction is not running")
}

opts := spanner.QueryOptions{
Expand All @@ -298,14 +298,14 @@ func (s *Session) RunUpdate(ctx context.Context, stmt spanner.Statement, useUpda
if useUpdate {
rowCount, err := s.tc.rwTxn.UpdateWithOptions(ctx, stmt, opts)
s.tc.sendHeartbeat = true
return nil, nil, rowCount, err
return nil, nil, rowCount, nil, err
}

rowIter := s.tc.rwTxn.QueryWithOptions(ctx, stmt, opts)
defer rowIter.Stop()
result, columnNames, err := parseQueryResult(rowIter)
s.tc.sendHeartbeat = true
return result, columnNames, rowIter.RowCount, err
return result, columnNames, rowIter.RowCount, rowIter.Metadata, err
}

func (s *Session) Close() {
Expand Down
2 changes: 1 addition & 1 deletion session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestRequestPriority(t *testing.T) {
}); err != nil {
t.Fatalf("failed to run query: %v", err)
}
if _, _, _, err := session.RunUpdate(ctx, spanner.NewStatement("DELETE FROM t1 WHERE Id = 1"), true); err != nil {
if _, _, _, _, err := session.RunUpdate(ctx, spanner.NewStatement("DELETE FROM t1 WHERE Id = 1"), true); err != nil {
t.Fatalf("failed to run update: %v", err)
}
if _, err := session.CommitReadWriteTransaction(ctx); err != nil {
Expand Down
Loading
Loading