diff --git a/_example/main.go b/_example/main.go index a7a22580a..63cd882c1 100644 --- a/_example/main.go +++ b/_example/main.go @@ -7,7 +7,7 @@ import ( "gopkg.in/src-d/go-mysql-server.v0/mem" "gopkg.in/src-d/go-mysql-server.v0/server" "gopkg.in/src-d/go-mysql-server.v0/sql" - "gopkg.in/src-d/go-vitess.v0/mysql" + "gopkg.in/src-d/go-vitess.v1/mysql" ) // Example of how to implement a MySQL server based on a Engine: diff --git a/engine_test.go b/engine_test.go index 3f4f928e0..2d9ed6b6c 100644 --- a/engine_test.go +++ b/engine_test.go @@ -1095,6 +1095,28 @@ func TestSessionVariables(t *testing.T) { require.Equal([]sql.Row{{int64(1), ",STRICT_TRANS_TABLES"}}, rows) } +func TestSessionVariablesONOFF(t *testing.T) { + require := require.New(t) + + e := newEngine(t) + + session := sql.NewBaseSession() + ctx := sql.NewContext(context.Background(), sql.WithSession(session), sql.WithPid(1)) + + _, _, err := e.Query(ctx, `set autocommit=ON, sql_mode = OFF, autoformat="true"`) + require.NoError(err) + + ctx = sql.NewContext(context.Background(), sql.WithSession(session), sql.WithPid(2)) + + _, iter, err := e.Query(ctx, `SELECT @@autocommit, @@session.sql_mode, @@autoformat`) + require.NoError(err) + + rows, err := sql.RowIterToRows(iter) + require.NoError(err) + + require.Equal([]sql.Row{{int64(1), int64(0), true}}, rows) +} + func insertRows(t *testing.T, table sql.Inserter, rows ...sql.Row) { t.Helper() diff --git a/go.mod b/go.mod index e06936d2e..23e25b9bc 100644 --- a/go.mod +++ b/go.mod @@ -1,65 +1,19 @@ module gopkg.in/src-d/go-mysql-server.v0 require ( - github.com/BurntSushi/toml v0.3.0 // indirect - github.com/CAFxX/gcnotifier v0.0.0-20170518020117-39b0596a2da3 // indirect - github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 // indirect - github.com/OneOfOne/xxhash v1.2.2 // indirect - github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect - github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/boltdb/bolt v1.3.1 - github.com/cespare/xxhash v1.0.0 // indirect - github.com/circonus-labs/circonus-gometrics v2.2.1+incompatible // indirect - github.com/circonus-labs/circonusllhist v0.0.0-20180430145027-5eb751da55c6 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-ole/go-ole v1.2.1 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/gogo/protobuf v1.1.1 // indirect - github.com/google/go-cmp v0.2.0 // indirect - github.com/gorilla/context v1.1.1 // indirect - github.com/gorilla/handlers v1.4.0 // indirect - github.com/gorilla/mux v1.6.2 // indirect - github.com/hashicorp/consul v1.2.3 // indirect - github.com/hashicorp/go-cleanhttp v0.5.0 // indirect - github.com/hashicorp/go-immutable-radix v1.0.0 // indirect - github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c // indirect - github.com/hashicorp/go-multierror v1.0.0 // indirect - github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 // indirect - github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect - github.com/hashicorp/memberlist v0.1.0 // indirect - github.com/hashicorp/serf v0.8.1 // indirect - github.com/hashicorp/yamux v0.0.0-20180917205041-7221087c3d28 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/miekg/dns v1.0.9 // indirect - github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452 - github.com/onsi/gomega v1.4.2 // indirect + github.com/mitchellh/hashstructure v1.0.0 github.com/opentracing/opentracing-go v1.0.2 - github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect - github.com/pelletier/go-toml v1.2.0 // indirect github.com/pilosa/pilosa v1.1.0 github.com/pkg/errors v0.8.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v0.8.0 // indirect - github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect - github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect - github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 // indirect - github.com/satori/go.uuid v1.2.0 - github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - github.com/shirou/gopsutil v2.17.12+incompatible // indirect - github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect - github.com/sirupsen/logrus v1.0.6 - github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/sirupsen/logrus v1.1.0 github.com/spf13/cast v1.2.0 - github.com/stretchr/testify v1.2.2 - github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect - golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect - google.golang.org/appengine v1.2.0 // indirect - google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 // indirect - google.golang.org/grpc v1.14.0 // indirect - gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect - gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect + golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect + google.golang.org/grpc v1.15.0 // indirect gopkg.in/src-d/go-errors.v1 v1.0.0 - gopkg.in/src-d/go-vitess.v0 v0.0.0-20180222154500-2cb632cdef3c - gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect + gopkg.in/src-d/go-vitess.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.1 ) diff --git a/server/context.go b/server/context.go index 04794dc8c..d785ed397 100644 --- a/server/context.go +++ b/server/context.go @@ -6,7 +6,7 @@ import ( opentracing "github.com/opentracing/opentracing-go" "gopkg.in/src-d/go-mysql-server.v0/sql" - "gopkg.in/src-d/go-vitess.v0/mysql" + "gopkg.in/src-d/go-vitess.v1/mysql" ) // SessionBuilder creates sessions given a MySQL connection and a server address. diff --git a/server/handler.go b/server/handler.go index 16d299261..292fe3570 100644 --- a/server/handler.go +++ b/server/handler.go @@ -12,9 +12,9 @@ import ( "gopkg.in/src-d/go-mysql-server.v0/sql" "github.com/sirupsen/logrus" - "gopkg.in/src-d/go-vitess.v0/mysql" - "gopkg.in/src-d/go-vitess.v0/sqltypes" - "gopkg.in/src-d/go-vitess.v0/vt/proto/query" + "gopkg.in/src-d/go-vitess.v1/mysql" + "gopkg.in/src-d/go-vitess.v1/sqltypes" + "gopkg.in/src-d/go-vitess.v1/vt/proto/query" ) var regKillCmd = regexp.MustCompile(`^kill (?:(query|connection) )?(\d+)$`) diff --git a/server/handler_test.go b/server/handler_test.go index 34b0d6b98..23959be0d 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -9,8 +9,8 @@ import ( "gopkg.in/src-d/go-mysql-server.v0" "gopkg.in/src-d/go-mysql-server.v0/mem" "gopkg.in/src-d/go-mysql-server.v0/sql" - "gopkg.in/src-d/go-vitess.v0/mysql" - "gopkg.in/src-d/go-vitess.v0/sqltypes" + "gopkg.in/src-d/go-vitess.v1/mysql" + "gopkg.in/src-d/go-vitess.v1/sqltypes" opentracing "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/require" diff --git a/server/server.go b/server/server.go index f3904fbb8..532aaddba 100644 --- a/server/server.go +++ b/server/server.go @@ -1,10 +1,17 @@ package server // import "gopkg.in/src-d/go-mysql-server.v0/server" import ( + "time" + opentracing "github.com/opentracing/opentracing-go" "gopkg.in/src-d/go-mysql-server.v0" - "gopkg.in/src-d/go-vitess.v0/mysql" + "gopkg.in/src-d/go-vitess.v1/mysql" +) + +const ( + DefaultConnReadTimeout = 30 * time.Second + DefaultConnWriteTimeout = 60 * time.Second ) // Server is a MySQL server for SQLe engines. @@ -23,6 +30,9 @@ type Config struct { // Tracer to use in the server. By default, a noop tracer will be used if // no tracer is provided. Tracer opentracing.Tracer + + ConnReadTimeout time.Duration + ConnWriteTimeout time.Duration } // NewDefaultServer creates a Server with the default session builder. @@ -40,8 +50,16 @@ func NewServer(cfg Config, e *sqle.Engine, sb SessionBuilder) (*Server, error) { tracer = opentracing.NoopTracer{} } + if cfg.ConnReadTimeout == 0 { + cfg.ConnReadTimeout = DefaultConnReadTimeout + } + + if cfg.ConnWriteTimeout == 0 { + cfg.ConnWriteTimeout = DefaultConnWriteTimeout + } + handler := NewHandler(e, NewSessionManager(sb, tracer, cfg.Address)) - l, err := mysql.NewListener(cfg.Protocol, cfg.Address, cfg.Auth, handler) + l, err := mysql.NewListener(cfg.Protocol, cfg.Address, cfg.Auth, handler, cfg.ConnReadTimeout, cfg.ConnWriteTimeout) if err != nil { return nil, err } diff --git a/sql/analyzer/resolve_columns.go b/sql/analyzer/resolve_columns.go index 0295ea1ec..67d370203 100644 --- a/sql/analyzer/resolve_columns.go +++ b/sql/analyzer/resolve_columns.go @@ -9,6 +9,7 @@ import ( "gopkg.in/src-d/go-mysql-server.v0/sql" "gopkg.in/src-d/go-mysql-server.v0/sql/expression" "gopkg.in/src-d/go-mysql-server.v0/sql/plan" + "gopkg.in/src-d/go-vitess.v1/vt/sqlparser" ) // deferredColumn is a wrapper on UnresolvedColumn used only to defer the @@ -190,17 +191,25 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) return e, nil } + const ( + sessionTable = "@@" + sqlparser.SessionStr + sessionPrefix = sqlparser.SessionStr + "." + ) columns, ok := colMap[uc.Name()] if !ok { switch uc := uc.(type) { case *expression.UnresolvedColumn: if isGlobalOrSessionColumn(uc) { - if uc.Table() != "" && strings.ToLower(uc.Table()) != "@@session" { + if uc.Table() != "" && strings.ToLower(uc.Table()) != sessionTable { return nil, errGlobalVariablesNotSupported.New(uc) } - typ, value := ctx.Get(strings.TrimLeft(uc.Name(), "@")) - return expression.NewGetSessionField(uc.Name(), typ, value), nil + name := strings.TrimLeft(uc.Name(), "@") + if strings.HasPrefix(name, sessionPrefix) { + name = name[len(sessionPrefix):] + } + typ, value := ctx.Get(name) + return expression.NewGetSessionField(name, typ, value), nil } a.Log("evaluation of column %q was deferred", uc.Name()) diff --git a/sql/analyzer/resolve_columns_test.go b/sql/analyzer/resolve_columns_test.go index e1165be29..a6fe99646 100644 --- a/sql/analyzer/resolve_columns_test.go +++ b/sql/analyzer/resolve_columns_test.go @@ -45,22 +45,44 @@ func TestQualifyColumns(t *testing.T) { table := mem.NewTable("mytable", sql.Schema{{Name: "i", Type: sql.Int32}}) table2 := mem.NewTable("mytable2", sql.Schema{{Name: "i", Type: sql.Int32}}) + sessionTable := mem.NewTable("@@session", sql.Schema{{Name: "autocommit", Type: sql.Int64}}) node := plan.NewProject( + []sql.Expression{ + expression.NewUnresolvedColumn("@@autocommit"), + }, + plan.NewResolvedTable(sessionTable), + ) + col, ok := node.Projections[0].(*expression.UnresolvedColumn) + require.True(ok) + require.Truef(isGlobalOrSessionColumn(col), "@@autocommit is not session column") + + expected := plan.NewProject( + []sql.Expression{ + expression.NewUnresolvedQualifiedColumn("", "@@autocommit"), + }, + plan.NewResolvedTable(sessionTable), + ) + + result, err := f.Apply(sql.NewEmptyContext(), nil, node) + require.NoError(err) + require.Equal(expected, result) + + node = plan.NewProject( []sql.Expression{ expression.NewUnresolvedColumn("i"), }, plan.NewResolvedTable(table), ) - expected := plan.NewProject( + expected = plan.NewProject( []sql.Expression{ expression.NewUnresolvedQualifiedColumn("mytable", "i"), }, plan.NewResolvedTable(table), ) - result, err := f.Apply(sql.NewEmptyContext(), nil, node) + result, err = f.Apply(sql.NewEmptyContext(), nil, node) require.NoError(err) require.Equal(expected, result) @@ -204,11 +226,13 @@ func TestResolveColumnsSession(t *testing.T) { ctx := sql.NewContext(context.Background(), sql.WithSession(sql.NewBaseSession())) ctx.Set("foo_bar", sql.Int64, int64(42)) + ctx.Set("autocommit", sql.Boolean, true) node := plan.NewProject( []sql.Expression{ expression.NewUnresolvedColumn("@@foo_bar"), expression.NewUnresolvedColumn("@@bar_baz"), + expression.NewUnresolvedColumn("@@autocommit"), }, plan.NewResolvedTable(dualTable), ) @@ -218,8 +242,9 @@ func TestResolveColumnsSession(t *testing.T) { expected := plan.NewProject( []sql.Expression{ - expression.NewGetSessionField("@@foo_bar", sql.Int64, int64(42)), - expression.NewGetSessionField("@@bar_baz", sql.Null, nil), + expression.NewGetSessionField("foo_bar", sql.Int64, int64(42)), + expression.NewGetSessionField("bar_baz", sql.Null, nil), + expression.NewGetSessionField("autocommit", sql.Boolean, true), }, plan.NewResolvedTable(dualTable), ) diff --git a/sql/expression/arithmetic.go b/sql/expression/arithmetic.go index b4a058a65..21ef18009 100644 --- a/sql/expression/arithmetic.go +++ b/sql/expression/arithmetic.go @@ -4,7 +4,7 @@ import ( "fmt" errors "gopkg.in/src-d/go-errors.v1" - "gopkg.in/src-d/go-vitess.v0/vt/sqlparser" + "gopkg.in/src-d/go-vitess.v1/vt/sqlparser" "gopkg.in/src-d/go-mysql-server.v0/sql" ) diff --git a/sql/parse/indexes.go b/sql/parse/indexes.go index 7e85914b3..5e4e7b1b3 100644 --- a/sql/parse/indexes.go +++ b/sql/parse/indexes.go @@ -10,7 +10,7 @@ import ( "gopkg.in/src-d/go-mysql-server.v0/sql/plan" "gopkg.in/src-d/go-mysql-server.v0/sql" - "gopkg.in/src-d/go-vitess.v0/vt/sqlparser" + "gopkg.in/src-d/go-vitess.v1/vt/sqlparser" ) func parseShowIndex(s string) (sql.Node, error) { diff --git a/sql/parse/parse.go b/sql/parse/parse.go index 2f6de72e8..a9637be2c 100644 --- a/sql/parse/parse.go +++ b/sql/parse/parse.go @@ -6,12 +6,14 @@ import ( "strconv" "strings" + "gopkg.in/src-d/go-mysql-server.v0/sql/expression/function" + opentracing "github.com/opentracing/opentracing-go" "gopkg.in/src-d/go-errors.v1" "gopkg.in/src-d/go-mysql-server.v0/sql" "gopkg.in/src-d/go-mysql-server.v0/sql/expression" "gopkg.in/src-d/go-mysql-server.v0/sql/plan" - "gopkg.in/src-d/go-vitess.v0/vt/sqlparser" + "gopkg.in/src-d/go-vitess.v1/vt/sqlparser" ) var ( @@ -109,15 +111,40 @@ func convertSet(ctx *sql.Context, n *sqlparser.Set) (sql.Node, error) { return nil, err } - fmt.Println(strings.TrimSpace(strings.ToLower(e.Name.Qualifier.Name.String()))) - switch strings.TrimSpace(strings.ToLower(e.Name.Qualifier.Name.String())) { - case "@@session", "": // do nothing - default: - return nil, ErrUnsupportedFeature.New("qualifiers in set variable names other than @@session") + name := strings.TrimSpace(e.Name.Lowered()) + if expr, err = expr.TransformUp(func(e sql.Expression) (sql.Expression, error) { + if !e.Resolved() || e.Type() != sql.Text { + return e, nil + } + + txt, err := e.Eval(ctx, nil) + if err != nil { + return nil, err + } + + val, ok := txt.(string) + if !ok { + return nil, ErrUnsupportedFeature.New("invalid qualifiers in set variable names") + } + + switch strings.ToLower(val) { + case "on": + return expression.NewLiteral(int64(1), sql.Int64), nil + case "true": + return expression.NewLiteral(true, sql.Boolean), nil + case "off": + return expression.NewLiteral(int64(0), sql.Int64), nil + case "false": + return expression.NewLiteral(false, sql.Boolean), nil + } + + return e, nil + }); err != nil { + return nil, err } variables[i] = plan.SetVariable{ - Name: e.Name.Name.Lowered(), + Name: name, Value: expr, } } @@ -525,6 +552,20 @@ func exprToExpression(e sqlparser.Expr) (sql.Expression, error) { switch v := e.(type) { default: return nil, ErrUnsupportedSyntax.New(e) + case *sqlparser.SubstrExpr: + name, err := exprToExpression(v.Name) + if err != nil { + return nil, err + } + from, err := exprToExpression(v.From) + if err != nil { + return nil, err + } + to, err := exprToExpression(v.To) + if err != nil { + return nil, err + } + return function.NewSubstring(name, from, to) case *sqlparser.ComparisonExpr: return comparisonExprToExpression(v) case *sqlparser.IsExpr: diff --git a/sql/parse/parse_test.go b/sql/parse/parse_test.go index 9ddd46e9c..4ba2e2d94 100644 --- a/sql/parse/parse_test.go +++ b/sql/parse/parse_test.go @@ -654,7 +654,7 @@ var fixtures = map[string]sql.Node{ ), `SET @@session.autocommit=1, foo="bar"`: plan.NewSet( plan.SetVariable{ - Name: "autocommit", + Name: "@@session.autocommit", Value: expression.NewLiteral(int64(1), sql.Int64), }, plan.SetVariable{ @@ -662,6 +662,62 @@ var fixtures = map[string]sql.Node{ Value: expression.NewLiteral("bar", sql.Text), }, ), + `SET autocommit=ON, on="1"`: plan.NewSet( + plan.SetVariable{ + Name: "autocommit", + Value: expression.NewLiteral(int64(1), sql.Int64), + }, + plan.SetVariable{ + Name: "on", + Value: expression.NewLiteral("1", sql.Text), + }, + ), + `SET @@session.autocommit=OFF, off="0"`: plan.NewSet( + plan.SetVariable{ + Name: "@@session.autocommit", + Value: expression.NewLiteral(int64(0), sql.Int64), + }, + plan.SetVariable{ + Name: "off", + Value: expression.NewLiteral("0", sql.Text), + }, + ), + `SET @@session.autocommit=ON`: plan.NewSet( + plan.SetVariable{ + Name: "@@session.autocommit", + Value: expression.NewLiteral(int64(1), sql.Int64), + }, + ), + `SET autocommit=off`: plan.NewSet( + plan.SetVariable{ + Name: "autocommit", + Value: expression.NewLiteral(int64(0), sql.Int64), + }, + ), + `SET autocommit=true`: plan.NewSet( + plan.SetVariable{ + Name: "autocommit", + Value: expression.NewLiteral(true, sql.Boolean), + }, + ), + `SET autocommit="true"`: plan.NewSet( + plan.SetVariable{ + Name: "autocommit", + Value: expression.NewLiteral(true, sql.Boolean), + }, + ), + `SET autocommit=false`: plan.NewSet( + plan.SetVariable{ + Name: "autocommit", + Value: expression.NewLiteral(false, sql.Boolean), + }, + ), + `SET autocommit="false"`: plan.NewSet( + plan.SetVariable{ + Name: "autocommit", + Value: expression.NewLiteral(false, sql.Boolean), + }, + ), } func TestParse(t *testing.T) { diff --git a/sql/plan/set.go b/sql/plan/set.go index 40b07dfc9..9aea35e22 100644 --- a/sql/plan/set.go +++ b/sql/plan/set.go @@ -5,6 +5,7 @@ import ( "strings" "gopkg.in/src-d/go-mysql-server.v0/sql" + "gopkg.in/src-d/go-vitess.v1/vt/sqlparser" ) // Set configuration variables. Right now, only session variables are supported. @@ -77,13 +78,18 @@ func (s *Set) RowIter(ctx *sql.Context) (sql.RowIter, error) { span, ctx := ctx.Span("plan.Set") defer span.Finish() + const sessionPrefix = sqlparser.SessionStr + "." for _, v := range s.Variables { value, err := v.Value.Eval(ctx, nil) if err != nil { return nil, err } - ctx.Set(strings.TrimLeft(v.Name, "@"), v.Value.Type(), value) + name := strings.TrimLeft(v.Name, "@") + if strings.HasPrefix(name, sessionPrefix) { + name = name[len(sessionPrefix):] + } + ctx.Set(name, v.Value.Type(), value) } return sql.RowsToRowIter(), nil diff --git a/sql/type.go b/sql/type.go index e3c31a537..863af149a 100644 --- a/sql/type.go +++ b/sql/type.go @@ -11,8 +11,8 @@ import ( "github.com/spf13/cast" "gopkg.in/src-d/go-errors.v1" - "gopkg.in/src-d/go-vitess.v0/sqltypes" - "gopkg.in/src-d/go-vitess.v0/vt/proto/query" + "gopkg.in/src-d/go-vitess.v1/sqltypes" + "gopkg.in/src-d/go-vitess.v1/vt/proto/query" ) var ( diff --git a/sql/type_test.go b/sql/type_test.go index 85e4411d1..3fec20b48 100644 --- a/sql/type_test.go +++ b/sql/type_test.go @@ -5,7 +5,7 @@ import ( "time" "github.com/stretchr/testify/require" - "gopkg.in/src-d/go-vitess.v0/sqltypes" + "gopkg.in/src-d/go-vitess.v1/sqltypes" ) func TestText(t *testing.T) {