Skip to content

Commit

Permalink
clisqlshell: implement COPY ... FROM STDIN for CLI
Browse files Browse the repository at this point in the history
Steps:
* Add a lower level API to lib/pq for use.
* Add some abstraction boundary breakers in `clisqlclient` that allow a
  lower level handling of the COPY protocol.
* Altered the state machine in `clisqlshell` to account for copy.

Release note (cli change): COPY ... FROM STDIN now works from the
cockroach CLI. It is not supported inside transactions.
  • Loading branch information
otan committed Apr 8, 2022
1 parent 11d4026 commit af53d44
Show file tree
Hide file tree
Showing 15 changed files with 496 additions and 96 deletions.
6 changes: 3 additions & 3 deletions DEPS.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5059,10 +5059,10 @@ def go_deps():
name = "com_github_lib_pq",
build_file_proto_mode = "disable_global",
importpath = "github.com/lib/pq",
sha256 = "0f50cfc8d4ed4bbb39767aacc04d6b23e1105d2fa50dcb8e4ae204b2c90018f0",
strip_prefix = "github.com/lib/pq@v1.10.2",
sha256 = "a5e7f8973a5370999678a82a452ec35d4004ec19b3089dc1af387c0082550688",
strip_prefix = "github.com/cockroachdb/pq@v0.0.0-20220408053752-c3ffc8d4376f",
urls = [
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/lib/pq/com_github_lib_pq-v1.10.2.zip",
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/cockroachdb/pq/com_github_cockroachdb_pq-v0.0.0-20220408053752-c3ffc8d4376f.zip",
],
)
go_repository(
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,5 @@ replace go.etcd.io/etcd/pkg/v3 => go.etcd.io/etcd/pkg/v3 v3.0.0-20201109164711-0
replace github.com/docker/docker => github.com/moby/moby v20.10.6+incompatible

replace github.com/maruel/panicparse/v2 => github.com/cockroachdb/panicparse/v2 v2.0.0-20211103220158-604c82a44f1e

replace github.com/lib/pq => github.com/cockroachdb/pq v0.0.0-20220408053752-c3ffc8d4376f
11 changes: 2 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ github.com/cockroachdb/panicparse/v2 v2.0.0-20211103220158-604c82a44f1e h1:FrERd
github.com/cockroachdb/panicparse/v2 v2.0.0-20211103220158-604c82a44f1e/go.mod h1:pMxsKyCewnV3xPaFvvT9NfwvDTcIx2Xqg0qL5Gq0SjM=
github.com/cockroachdb/pebble v0.0.0-20220322040433-6164579cf2cb h1:1JgeoLiHDlpa+AV6MU2qvDctffM9zoHiPRXOCvgOX2k=
github.com/cockroachdb/pebble v0.0.0-20220322040433-6164579cf2cb/go.mod h1:buxOO9GBtOcq1DiXDpIPYrmxY020K2A8lOrwno5FetU=
github.com/cockroachdb/pq v0.0.0-20220408053752-c3ffc8d4376f h1:YtjvoSyDMMyGVau3mND/lDfQPP40Z4BFAuYNaHjRkFw=
github.com/cockroachdb/pq v0.0.0-20220408053752-c3ffc8d4376f/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
Expand Down Expand Up @@ -1475,15 +1477,6 @@ github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406 h1:+OUpk+IVvmKU
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq/auth/kerberos v0.0.0-20200720160335-984a6aa1ca46 h1:q7hY+WNJTcSqJNGwJzXZYL++nWBaoKlKdgZOyY6jxz4=
github.com/lib/pq/auth/kerberos v0.0.0-20200720160335-984a6aa1ca46/go.mod h1:jydegJvs5JvVcuFD/YAT8JRmRVeOoRhtnGEgRnAoPpE=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
Expand Down
1 change: 1 addition & 0 deletions pkg/cli/clisqlclient/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
"api.go",
"conn.go",
"context.go",
"copy.go",
"doc.go",
"init_conn_error.go",
"make_query.go",
Expand Down
116 changes: 116 additions & 0 deletions pkg/cli/clisqlclient/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package clisqlclient

import (
"context"
"database/sql/driver"
"io"
"reflect"
"strings"

"github.com/cockroachdb/errors"
)

type copyFromer interface {
CopyData(ctx context.Context, line string) (r driver.Result, err error)
Close() error
}

// CopyFromState represents an in progress COPY FROM.
type CopyFromState struct {
driver.Tx
copyFromer
}

// BeginCopyFrom starts a COPY FROM query.
func BeginCopyFrom(ctx context.Context, conn Conn, query string) (*CopyFromState, error) {
txn, err := conn.(*sqlConn).conn.(driver.ConnBeginTx).BeginTx(ctx, driver.TxOptions{})
if err != nil {
return nil, err
}
stmt, err := txn.(driver.Conn).Prepare(query)
if err != nil {
return nil, errors.CombineErrors(err, txn.Rollback())
}
return &CopyFromState{Tx: txn, copyFromer: stmt.(copyFromer)}, nil
}

// copyFromRows is a mock Rows interface for COPY results.
type copyFromRows struct {
r driver.Result
}

func (c copyFromRows) Close() error {
return nil
}

func (c copyFromRows) Columns() []string {
return nil
}

func (c copyFromRows) ColumnTypeScanType(index int) reflect.Type {
return nil
}

func (c copyFromRows) ColumnTypeDatabaseTypeName(index int) string {
return ""
}

func (c copyFromRows) ColumnTypeNames() []string {
return nil
}

func (c copyFromRows) Result() driver.Result {
return c.r
}

func (c copyFromRows) Tag() string {
return "COPY"
}

func (c copyFromRows) Next(values []driver.Value) error {
return io.EOF
}

func (c copyFromRows) NextResultSet() (bool, error) {
return false, nil
}

// Cancel cancels a COPY FROM query from completing.
func (c *CopyFromState) Cancel() error {
return errors.CombineErrors(c.copyFromer.Close(), c.Tx.Rollback())
}

// Commit completes a COPY FROM query by committing lines to the database.
func (c *CopyFromState) Commit(ctx context.Context, cleanupFunc func(), lines string) QueryFn {
return func(ctx context.Context, conn Conn) (Rows, bool, error) {
defer cleanupFunc()
rows, isMulti, err := func() (Rows, bool, error) {
for _, l := range strings.Split(lines, "\n") {
_, err := c.copyFromer.CopyData(ctx, l)
if err != nil {
return nil, false, err
}
}
// Empty line signifies completion.
r, err := c.copyFromer.CopyData(ctx, "")
if err != nil {
return nil, false, err
}
return copyFromRows{r: r}, false, c.Tx.Commit()
}()
if err != nil {
return rows, isMulti, errors.CombineErrors(err, errors.CombineErrors(c.copyFromer.Close(), c.Tx.Rollback()))
}
return rows, isMulti, err
}
}
22 changes: 0 additions & 22 deletions pkg/cli/clisqlclient/make_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ package clisqlclient
import (
"context"
"database/sql/driver"
"strings"

"github.com/cockroachdb/cockroach/pkg/sql/scanner"
"github.com/cockroachdb/errors"
)

// QueryFn is the type of functions produced by MakeQuery.
Expand All @@ -28,7 +26,6 @@ func MakeQuery(query string, parameters ...interface{}) QueryFn {
return func(ctx context.Context, conn Conn) (Rows, bool, error) {
isMultiStatementQuery, _ := scanner.HasMultipleStatements(query)
rows, err := conn.Query(ctx, query, parameters...)
err = handleCopyError(conn.(*sqlConn), err)
return rows, isMultiStatementQuery, err
}
}
Expand All @@ -51,22 +48,3 @@ func convertArgs(parameters []interface{}) ([]driver.NamedValue, error) {
}
return dVals, nil
}

// handleCopyError ensures the user is properly informed when they issue
// a COPY statement somewhere in their input.
func handleCopyError(conn *sqlConn, err error) error {
if err == nil {
return nil
}

if !strings.HasPrefix(err.Error(), "pq: unknown response for simple query: 'G'") {
return err
}

// The COPY statement has hosed the connection by putting the
// protocol in a state that lib/pq cannot understand any more. Reset
// it.
_ = conn.Close()
conn.reconnecting = true
return errors.New("woops! COPY has confused this client! Suggestion: use 'psql' for COPY")
}
1 change: 1 addition & 0 deletions pkg/cli/clisqlexec/run_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ var tagsWithRowsAffected = map[string]struct{}{
"DELETE": {},
"MOVE": {},
"DROP USER": {},
"COPY": {},
// This one is used with e.g. CREATE TABLE AS (other SELECT
// statements have type Rows, not RowsAffected).
"SELECT": {},
Expand Down
1 change: 1 addition & 0 deletions pkg/cli/clisqlshell/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ go_library(
"//pkg/sql/scanner",
"//pkg/sql/sqlfsm",
"//pkg/util/envutil",
"//pkg/util/errorutil/unimplemented",
"//pkg/util/syncutil",
"@com_github_cockroachdb_errors//:errors",
"@com_github_knz_go_libedit//:go-libedit",
Expand Down
Loading

0 comments on commit af53d44

Please sign in to comment.