2012-10-22 20:49:05 +04:00
|
|
|
// Copyright 2012, Google Inc. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package mysqlctl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2012-10-24 21:30:10 +04:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
2012-10-22 20:49:05 +04:00
|
|
|
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log "github.com/golang/glog"
|
2015-11-10 04:46:17 +03:00
|
|
|
|
2015-11-11 22:48:29 +03:00
|
|
|
"github.com/youtube/vitess/go/vt/mysqlctl/tmutils"
|
2015-11-10 04:46:17 +03:00
|
|
|
tabletmanagerdatapb "github.com/youtube/vitess/go/vt/proto/tabletmanagerdata"
|
2012-10-22 20:49:05 +04:00
|
|
|
)
|
|
|
|
|
2013-03-23 04:11:43 +04:00
|
|
|
var autoIncr = regexp.MustCompile(" AUTO_INCREMENT=\\d+")
|
2012-10-24 21:30:10 +04:00
|
|
|
|
2013-01-25 01:54:22 +04:00
|
|
|
// GetSchema returns the schema for database for tables listed in
|
|
|
|
// tables. If tables is empty, return the schema for all tables.
|
2015-11-10 04:46:17 +03:00
|
|
|
func (mysqld *Mysqld) GetSchema(dbName string, tables, excludeTables []string, includeViews bool) (*tabletmanagerdatapb.SchemaDefinition, error) {
|
|
|
|
sd := &tabletmanagerdatapb.SchemaDefinition{}
|
2013-03-26 02:26:47 +04:00
|
|
|
|
|
|
|
// get the database creation command
|
2015-06-28 02:15:11 +03:00
|
|
|
qr, fetchErr := mysqld.FetchSuperQuery("SHOW CREATE DATABASE IF NOT EXISTS " + dbName)
|
2013-03-26 02:26:47 +04:00
|
|
|
if fetchErr != nil {
|
|
|
|
return nil, fetchErr
|
|
|
|
}
|
2013-05-25 04:11:07 +04:00
|
|
|
if len(qr.Rows) == 0 {
|
2013-03-26 02:26:47 +04:00
|
|
|
return nil, fmt.Errorf("empty create database statement for %v", dbName)
|
|
|
|
}
|
2013-05-25 04:11:07 +04:00
|
|
|
sd.DatabaseSchema = strings.Replace(qr.Rows[0][1].String(), "`"+dbName+"`", "`{{.DatabaseName}}`", 1)
|
2013-03-26 02:26:47 +04:00
|
|
|
|
|
|
|
// get the list of tables we're interested in
|
2014-07-02 02:34:25 +04:00
|
|
|
sql := "SELECT table_name, table_type, data_length, table_rows FROM information_schema.tables WHERE table_schema = '" + dbName + "'"
|
2013-03-08 03:07:24 +04:00
|
|
|
if !includeViews {
|
2015-11-11 22:48:29 +03:00
|
|
|
sql += " AND table_type = '" + tmutils.TableBaseTable + "'"
|
2013-03-08 03:07:24 +04:00
|
|
|
}
|
2015-05-20 00:31:54 +03:00
|
|
|
qr, err := mysqld.FetchSuperQuery(sql)
|
2013-03-08 03:07:24 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-05-25 04:11:07 +04:00
|
|
|
if len(qr.Rows) == 0 {
|
2013-03-26 02:26:47 +04:00
|
|
|
return sd, nil
|
2013-03-08 03:07:24 +04:00
|
|
|
}
|
|
|
|
|
2015-11-10 04:46:17 +03:00
|
|
|
sd.TableDefinitions = make([]*tabletmanagerdatapb.TableDefinition, 0, len(qr.Rows))
|
2014-05-20 01:57:25 +04:00
|
|
|
for _, row := range qr.Rows {
|
2013-03-08 03:07:24 +04:00
|
|
|
tableName := row[0].String()
|
|
|
|
tableType := row[1].String()
|
2014-05-20 01:57:25 +04:00
|
|
|
|
|
|
|
// compute dataLength
|
2013-03-19 22:55:52 +04:00
|
|
|
var dataLength uint64
|
|
|
|
if !row[2].IsNull() {
|
|
|
|
// dataLength is NULL for views, then we use 0
|
|
|
|
dataLength, err = row[2].ParseUint64()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2012-10-22 20:49:05 +04:00
|
|
|
|
2014-07-02 02:34:25 +04:00
|
|
|
// get row count
|
|
|
|
var rowCount uint64
|
|
|
|
if !row[3].IsNull() {
|
|
|
|
rowCount, err = row[3].ParseUint64()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-20 00:31:54 +03:00
|
|
|
qr, fetchErr := mysqld.FetchSuperQuery("SHOW CREATE TABLE " + dbName + "." + tableName)
|
2012-10-22 20:49:05 +04:00
|
|
|
if fetchErr != nil {
|
|
|
|
return nil, fetchErr
|
|
|
|
}
|
2013-05-25 04:11:07 +04:00
|
|
|
if len(qr.Rows) == 0 {
|
2012-10-22 20:49:05 +04:00
|
|
|
return nil, fmt.Errorf("empty create table statement for %v", tableName)
|
|
|
|
}
|
|
|
|
|
2012-10-24 21:30:10 +04:00
|
|
|
// Normalize & remove auto_increment because it changes on every insert
|
|
|
|
// FIXME(alainjobart) find a way to share this with
|
|
|
|
// vt/tabletserver/table_info.go:162
|
2013-05-25 04:11:07 +04:00
|
|
|
norm := qr.Rows[0][1].String()
|
2013-05-02 02:03:04 +04:00
|
|
|
norm = autoIncr.ReplaceAllLiteralString(norm, "")
|
2015-11-11 22:48:29 +03:00
|
|
|
if tableType == tmutils.TableView {
|
2013-05-02 02:03:04 +04:00
|
|
|
// Views will have the dbname in there, replace it
|
|
|
|
// with {{.DatabaseName}}
|
|
|
|
norm = strings.Replace(norm, "`"+dbName+"`", "`{{.DatabaseName}}`", -1)
|
|
|
|
}
|
2012-10-24 21:30:10 +04:00
|
|
|
|
2015-11-10 04:46:17 +03:00
|
|
|
td := &tabletmanagerdatapb.TableDefinition{}
|
2014-05-20 01:57:25 +04:00
|
|
|
td.Name = tableName
|
|
|
|
td.Schema = norm
|
2013-02-13 00:23:00 +04:00
|
|
|
|
2014-05-20 01:57:25 +04:00
|
|
|
td.Columns, err = mysqld.GetColumns(dbName, tableName)
|
2013-12-19 09:05:45 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-05-20 01:57:25 +04:00
|
|
|
td.PrimaryKeyColumns, err = mysqld.GetPrimaryKeyColumns(dbName, tableName)
|
2013-02-13 00:23:00 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-05-20 01:57:25 +04:00
|
|
|
td.Type = tableType
|
|
|
|
td.DataLength = dataLength
|
2014-07-02 02:34:25 +04:00
|
|
|
td.RowCount = rowCount
|
2014-05-20 01:57:25 +04:00
|
|
|
sd.TableDefinitions = append(sd.TableDefinitions, td)
|
2012-10-22 20:49:05 +04:00
|
|
|
}
|
|
|
|
|
2015-11-11 22:48:29 +03:00
|
|
|
sd, err = tmutils.FilterTables(sd, tables, excludeTables, includeViews)
|
2015-02-14 03:39:14 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-11-11 22:48:29 +03:00
|
|
|
tmutils.GenerateSchemaVersion(sd)
|
2012-10-22 20:49:05 +04:00
|
|
|
return sd, nil
|
|
|
|
}
|
2012-11-06 03:06:00 +04:00
|
|
|
|
2014-05-20 01:57:25 +04:00
|
|
|
// ResolveTables returns a list of actual tables+views matching a list
|
|
|
|
// of regexps
|
2015-05-20 01:04:04 +03:00
|
|
|
func ResolveTables(mysqld MysqlDaemon, dbName string, tables []string) ([]string, error) {
|
2014-06-24 01:22:47 +04:00
|
|
|
sd, err := mysqld.GetSchema(dbName, tables, nil, true)
|
2014-05-20 01:57:25 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result := make([]string, len(sd.TableDefinitions))
|
|
|
|
for i, td := range sd.TableDefinitions {
|
|
|
|
result[i] = td.Name
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2013-02-13 00:23:00 +04:00
|
|
|
// GetColumns returns the columns of table.
|
2013-02-18 02:25:17 +04:00
|
|
|
func (mysqld *Mysqld) GetColumns(dbName, table string) ([]string, error) {
|
2014-10-16 09:09:44 +04:00
|
|
|
conn, err := mysqld.dbaPool.Get(0)
|
2013-02-13 00:23:00 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-06-27 01:46:52 +04:00
|
|
|
defer conn.Recycle()
|
2013-05-01 00:00:43 +04:00
|
|
|
qr, err := conn.ExecuteFetch(fmt.Sprintf("select * from %v.%v where 1=0", dbName, table), 0, true)
|
2013-02-13 00:23:00 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
columns := make([]string, len(qr.Fields))
|
|
|
|
for i, field := range qr.Fields {
|
|
|
|
columns[i] = field.Name
|
|
|
|
}
|
|
|
|
return columns, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-12-19 09:05:45 +04:00
|
|
|
// GetPrimaryKeyColumns returns the primary key columns of table.
|
|
|
|
func (mysqld *Mysqld) GetPrimaryKeyColumns(dbName, table string) ([]string, error) {
|
2014-10-16 09:09:44 +04:00
|
|
|
conn, err := mysqld.dbaPool.Get(0)
|
2013-12-19 09:05:45 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-06-27 01:46:52 +04:00
|
|
|
defer conn.Recycle()
|
2013-12-19 09:05:45 +04:00
|
|
|
qr, err := conn.ExecuteFetch(fmt.Sprintf("show index from %v.%v", dbName, table), 100, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
keyNameIndex := -1
|
|
|
|
seqInIndexIndex := -1
|
|
|
|
columnNameIndex := -1
|
|
|
|
for i, field := range qr.Fields {
|
|
|
|
switch field.Name {
|
|
|
|
case "Key_name":
|
|
|
|
keyNameIndex = i
|
|
|
|
case "Seq_in_index":
|
|
|
|
seqInIndexIndex = i
|
|
|
|
case "Column_name":
|
|
|
|
columnNameIndex = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if keyNameIndex == -1 || seqInIndexIndex == -1 || columnNameIndex == -1 {
|
|
|
|
return nil, fmt.Errorf("Unknown columns in 'show index' result: %v", qr.Fields)
|
|
|
|
}
|
|
|
|
|
|
|
|
columns := make([]string, 0, 5)
|
|
|
|
var expectedIndex int64 = 1
|
|
|
|
for _, row := range qr.Rows {
|
|
|
|
// skip non-primary keys
|
|
|
|
if row[keyNameIndex].String() != "PRIMARY" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// check the Seq_in_index is always increasing
|
|
|
|
seqInIndex, err := row[seqInIndexIndex].ParseInt64()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if seqInIndex != expectedIndex {
|
|
|
|
return nil, fmt.Errorf("Unexpected index: %v != %v", seqInIndex, expectedIndex)
|
|
|
|
}
|
|
|
|
expectedIndex++
|
|
|
|
|
|
|
|
columns = append(columns, row[columnNameIndex].String())
|
|
|
|
}
|
|
|
|
return columns, err
|
|
|
|
}
|
|
|
|
|
2014-06-27 01:46:52 +04:00
|
|
|
// PreflightSchemaChange will apply the schema change to a fake
|
|
|
|
// database that has the same schema as the target database, see if it
|
|
|
|
// works.
|
2015-11-11 22:48:29 +03:00
|
|
|
func (mysqld *Mysqld) PreflightSchemaChange(dbName string, change string) (*tmutils.SchemaChangeResult, error) {
|
2012-11-08 23:05:26 +04:00
|
|
|
// gather current schema on real database
|
2014-06-24 01:22:47 +04:00
|
|
|
beforeSchema, err := mysqld.GetSchema(dbName, nil, nil, true)
|
2012-11-08 23:05:26 +04:00
|
|
|
if err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// populate temporary database with it
|
|
|
|
sql := "SET sql_log_bin = 0;\n"
|
|
|
|
sql += "DROP DATABASE IF EXISTS _vt_preflight;\n"
|
|
|
|
sql += "CREATE DATABASE _vt_preflight;\n"
|
|
|
|
sql += "USE _vt_preflight;\n"
|
2013-02-20 22:15:25 +04:00
|
|
|
for _, td := range beforeSchema.TableDefinitions {
|
2015-11-11 22:48:29 +03:00
|
|
|
if td.Type == tmutils.TableBaseTable {
|
2013-03-08 03:07:24 +04:00
|
|
|
sql += td.Schema + ";\n"
|
|
|
|
}
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
2015-11-10 02:33:54 +03:00
|
|
|
if err = mysqld.executeMysqlCommands(mysqld.dba.Uname, sql); err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// apply schema change to the temporary database
|
|
|
|
sql = "SET sql_log_bin = 0;\n"
|
|
|
|
sql += "USE _vt_preflight;\n"
|
|
|
|
sql += change
|
2015-11-10 02:33:54 +03:00
|
|
|
if err = mysqld.executeMysqlCommands(mysqld.dba.Uname, sql); err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// get the result
|
2014-06-24 01:22:47 +04:00
|
|
|
afterSchema, err := mysqld.GetSchema("_vt_preflight", nil, nil, true)
|
2012-11-08 23:05:26 +04:00
|
|
|
if err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// and clean up the extra database
|
|
|
|
sql = "SET sql_log_bin = 0;\n"
|
|
|
|
sql += "DROP DATABASE _vt_preflight;\n"
|
2015-11-10 02:33:54 +03:00
|
|
|
if err = mysqld.executeMysqlCommands(mysqld.dba.Uname, sql); err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
2015-11-11 22:48:29 +03:00
|
|
|
return &tmutils.SchemaChangeResult{BeforeSchema: beforeSchema, AfterSchema: afterSchema}, nil
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
2014-06-27 01:46:52 +04:00
|
|
|
// ApplySchemaChange will apply the schema change to the given database.
|
2015-11-11 22:48:29 +03:00
|
|
|
func (mysqld *Mysqld) ApplySchemaChange(dbName string, change *tmutils.SchemaChange) (*tmutils.SchemaChangeResult, error) {
|
2012-11-06 03:06:00 +04:00
|
|
|
// check current schema matches
|
2014-06-24 01:22:47 +04:00
|
|
|
beforeSchema, err := mysqld.GetSchema(dbName, nil, nil, false)
|
2012-11-06 03:06:00 +04:00
|
|
|
if err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-06 03:06:00 +04:00
|
|
|
}
|
|
|
|
if change.BeforeSchema != nil {
|
2015-11-11 22:48:29 +03:00
|
|
|
schemaDiffs := tmutils.DiffSchemaToArray("actual", beforeSchema, "expected", change.BeforeSchema)
|
2012-11-08 23:05:26 +04:00
|
|
|
if len(schemaDiffs) > 0 {
|
|
|
|
for _, msg := range schemaDiffs {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("BeforeSchema differs: %v", msg)
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// let's see if the schema was already applied
|
|
|
|
if change.AfterSchema != nil {
|
2015-11-11 22:48:29 +03:00
|
|
|
schemaDiffs = tmutils.DiffSchemaToArray("actual", beforeSchema, "expected", change.AfterSchema)
|
2012-11-08 23:05:26 +04:00
|
|
|
if len(schemaDiffs) == 0 {
|
|
|
|
// no diff between the schema we expect
|
|
|
|
// after the change and the current
|
|
|
|
// schema, we already applied it
|
2015-11-11 22:48:29 +03:00
|
|
|
return &tmutils.SchemaChangeResult{
|
|
|
|
BeforeSchema: beforeSchema,
|
|
|
|
AfterSchema: beforeSchema}, nil
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-06 03:06:00 +04:00
|
|
|
if change.Force {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("BeforeSchema differs, applying anyway")
|
2012-11-06 03:06:00 +04:00
|
|
|
} else {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, fmt.Errorf("BeforeSchema differs")
|
2012-11-06 03:06:00 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 20:36:43 +03:00
|
|
|
sql := change.SQL
|
2012-11-06 03:06:00 +04:00
|
|
|
if !change.AllowReplication {
|
|
|
|
sql = "SET sql_log_bin = 0;\n" + sql
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a 'use XXX' in front of the SQL
|
2012-11-08 23:05:26 +04:00
|
|
|
sql = "USE " + dbName + ";\n" + sql
|
2012-11-06 03:06:00 +04:00
|
|
|
|
|
|
|
// execute the schema change using an external mysql process
|
|
|
|
// (to benefit from the extra commands in mysql cli)
|
2015-11-10 02:33:54 +03:00
|
|
|
if err = mysqld.executeMysqlCommands(mysqld.dba.Uname, sql); err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-06 03:06:00 +04:00
|
|
|
}
|
|
|
|
|
2013-02-20 22:15:25 +04:00
|
|
|
// get AfterSchema
|
2014-06-24 01:22:47 +04:00
|
|
|
afterSchema, err := mysqld.GetSchema(dbName, nil, nil, false)
|
2012-11-06 03:06:00 +04:00
|
|
|
if err != nil {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, err
|
2012-11-06 03:06:00 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// compare to the provided AfterSchema
|
|
|
|
if change.AfterSchema != nil {
|
2015-11-11 22:48:29 +03:00
|
|
|
schemaDiffs := tmutils.DiffSchemaToArray("actual", afterSchema, "expected", change.AfterSchema)
|
2012-11-08 23:05:26 +04:00
|
|
|
if len(schemaDiffs) > 0 {
|
|
|
|
for _, msg := range schemaDiffs {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("AfterSchema differs: %v", msg)
|
2012-11-08 23:05:26 +04:00
|
|
|
}
|
2012-11-06 03:06:00 +04:00
|
|
|
if change.Force {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("AfterSchema differs, not reporting error")
|
2012-11-06 03:06:00 +04:00
|
|
|
} else {
|
2013-02-20 22:15:25 +04:00
|
|
|
return nil, fmt.Errorf("AfterSchema differs")
|
2012-11-06 03:06:00 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 22:48:29 +03:00
|
|
|
return &tmutils.SchemaChangeResult{BeforeSchema: beforeSchema, AfterSchema: afterSchema}, nil
|
2012-11-06 03:06:00 +04:00
|
|
|
}
|