env.go 18.6 KB
Newer Older
martianzhang's avatar
martianzhang 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package env

import (
	"fmt"
W
WithLin 已提交
21 22 23
	"strings"
	"time"

martianzhang's avatar
martianzhang 已提交
24 25 26 27 28 29 30 31 32 33 34 35 36
	"github.com/XiaoMi/soar/ast"
	"github.com/XiaoMi/soar/common"
	"github.com/XiaoMi/soar/database"

	"github.com/dchest/uniuri"
	"vitess.io/vitess/go/vt/sqlparser"
)

// VirtualEnv SQL优化评审 测试环境
// DB使用的信息从配置文件中获取
type VirtualEnv struct {
	*database.Connector

37
	// 保存 DB 测试环境映射关系,防止 vEnv 环境冲突。
martianzhang's avatar
martianzhang 已提交
38
	DBRef   map[string]string // db -> optimizer_xxx
39 40
	Hash2DB map[string]string // optimizer_xxx -> db
	// 保存 Table 创建关系,防止重复创建表
martianzhang's avatar
martianzhang 已提交
41 42 43 44 45 46 47 48 49 50
	TableMap map[string]map[string]string
	// 错误
	Error error
}

// NewVirtualEnv 初始化一个新的测试环境
func NewVirtualEnv(vEnv *database.Connector) *VirtualEnv {
	return &VirtualEnv{
		Connector: vEnv,
		DBRef:     make(map[string]string),
51
		Hash2DB:   make(map[string]string),
martianzhang's avatar
martianzhang 已提交
52 53 54 55 56 57 58 59
		TableMap:  make(map[string]map[string]string),
	}
}

// BuildEnv 测试环境初始化&连接线上环境检查
// @output *VirtualEnv	测试环境
// @output *database.Connector 线上环境连接句柄
func BuildEnv() (*VirtualEnv, *database.Connector) {
martianzhang's avatar
martianzhang 已提交
60 61
	connTest, err := database.NewConnector(common.Config.TestDSN)
	common.LogIfError(err, "")
martianzhang's avatar
martianzhang 已提交
62
	// 生成测试环境
martianzhang's avatar
martianzhang 已提交
63
	vEnv := NewVirtualEnv(connTest)
martianzhang's avatar
martianzhang 已提交
64 65 66 67 68 69 70 71 72 73 74 75

	// 检查测试环境可用性,并记录数据库版本
	vEnvVersion, err := vEnv.Version()
	common.Config.TestDSN.Version = vEnvVersion
	if err != nil {
		common.Log.Warn("BuildEnv TestDSN: %s:********@%s/%s not available , Error: %s",
			vEnv.User, vEnv.Addr, vEnv.Database, err.Error())
		common.Config.TestDSN.Disable = true
	}

	// 连接线上环境
	// 如果未配置线上环境线测试环境配置为线上环境
martianzhang's avatar
martianzhang 已提交
76
	if common.Config.OnlineDSN.User == "" {
martianzhang's avatar
martianzhang 已提交
77 78 79 80
		common.Log.Warn("BuildEnv AllowOnlineAsTest: OnlineDSN not config, use TestDSN: %s:********@%s/%s as OnlineDSN",
			vEnv.User, vEnv.Addr, vEnv.Database)
		common.Config.OnlineDSN = common.Config.TestDSN
	}
martianzhang's avatar
martianzhang 已提交
81 82
	connOnline, err := database.NewConnector(common.Config.OnlineDSN)
	common.LogIfError(err, "")
martianzhang's avatar
martianzhang 已提交
83 84

	// 检查线上环境可用性版本
martianzhang's avatar
martianzhang 已提交
85
	rEnvVersion, err := connOnline.Version()
martianzhang's avatar
martianzhang 已提交
86 87 88
	common.Config.OnlineDSN.Version = rEnvVersion
	if err != nil {
		common.Log.Warn("BuildEnv OnlineDSN: %s:********@%s/%s not available , Error: %s",
89
			connOnline.User, connOnline.Addr, connOnline.Database, err.Error())
martianzhang's avatar
martianzhang 已提交
90 91 92
		common.Config.TestDSN.Disable = true
	}

martianzhang's avatar
martianzhang 已提交
93
	// 检查是否允许 Online 和 Test 一致,防止误操作
martianzhang's avatar
martianzhang 已提交
94 95 96 97 98 99 100 101
	if common.FormatDSN(common.Config.OnlineDSN) == common.FormatDSN(common.Config.TestDSN) &&
		!common.Config.AllowOnlineAsTest {
		common.Log.Warn("BuildEnv AllowOnlineAsTest: %s:********@%s/%s OnlineDSN can't config as TestDSN",
			vEnv.User, vEnv.Addr, vEnv.Database)
		common.Config.TestDSN.Disable = true
		common.Config.OnlineDSN.Disable = true
	}

102 103 104 105 106 107 108
	// 是否禁用版本检测,禁用后,不再对比测试环境和线上环境的版本大小
	if !common.Config.DisableVersionCheck {
		// 判断测试环境与线上环境版本是否一致,要求测试环境版本不低于线上环境
		if vEnvVersion < rEnvVersion {
			common.Log.Warning("TestDSN MySQL version older than OnlineDSN(%d), TestDSN(%d) will not be used", rEnvVersion, vEnvVersion)
			common.Config.TestDSN.Disable = true
		}
martianzhang's avatar
martianzhang 已提交
109 110
	}

martianzhang's avatar
martianzhang 已提交
111
	return vEnv, connOnline
martianzhang's avatar
martianzhang 已提交
112 113
}

martianzhang's avatar
martianzhang 已提交
114
// RealDB 从测试环境中获取通过 hash 后的 DB
115 116 117 118 119 120 121
func (vEnv *VirtualEnv) RealDB(hash string) string {
	if _, ok := vEnv.Hash2DB[hash]; ok {
		return vEnv.Hash2DB[hash]
	}
	// hash may be real database name not hash
	if strings.HasPrefix(hash, "optimizer_") {
		common.Log.Warning("RealDB, Hash2DB missing hash map: %s", hash)
martianzhang's avatar
martianzhang 已提交
122 123 124 125
	}
	return hash
}

martianzhang's avatar
martianzhang 已提交
126
// DBHash 从测试环境中根据 DB 找到对应的 hash 值
127 128 129
func (vEnv *VirtualEnv) DBHash(db string) string {
	if _, ok := vEnv.DBRef[db]; ok {
		return vEnv.DBRef[db]
martianzhang's avatar
martianzhang 已提交
130 131 132 133 134
	}
	return db
}

// CleanUp 环境清理
135
func (vEnv *VirtualEnv) CleanUp() bool {
martianzhang's avatar
martianzhang 已提交
136 137
	if !common.Config.TestDSN.Disable && common.Config.DropTestTemporary {
		common.Log.Debug("CleanUp ...")
138 139
		for db := range vEnv.Hash2DB {
			_, err := vEnv.Query(fmt.Sprintf("drop database %s", db))
martianzhang's avatar
martianzhang 已提交
140 141 142 143 144
			if err != nil {
				common.Log.Error("CleanUp failed Error: %s", err)
				return false
			}
		}
145 146 147 148 149
		// cleanup hash map
		vEnv.DBRef = make(map[string]string)
		vEnv.Hash2DB = make(map[string]string)
		vEnv.TableMap = make(map[string]map[string]string)

martianzhang's avatar
martianzhang 已提交
150 151 152 153 154
		common.Log.Debug("CleanUp, done")
	}
	return true
}

155
// CleanupTestDatabase 清除一小时前的环境
156
func (vEnv *VirtualEnv) CleanupTestDatabase() {
157
	common.Log.Debug("CleanupTestDatabase ...")
158
	dbs, err := vEnv.Query("show databases like 'optimizer%%'")
L
liipx 已提交
159 160 161 162 163 164 165
	if err != nil {
		common.Log.Error("CleanupTestDatabase failed Error:%s", err.Error())
		return
	}

	// TODO: 1 hour should be config-able
	minHour := 1
166 167
	for dbs.Rows.Next() {
		var testDatabase string
martianzhang's avatar
martianzhang 已提交
168 169 170 171
		err = dbs.Rows.Scan(&testDatabase)
		if err != nil {
			break
		}
L
liipx 已提交
172 173 174 175 176 177
		// test temporary database format `optimizer_YYMMDDHHmmss_randomString(16)`
		if len(testDatabase) != 39 {
			common.Log.Debug("CleanupTestDatabase by pass %s", testDatabase)
			continue
		}
		s := strings.Split(testDatabase, "_")
178
		pastTime, err := time.ParseInLocation("060102150405", s[1], time.Local)
L
liipx 已提交
179 180 181 182 183
		if err != nil {
			common.Log.Error("CleanupTestDatabase compute  pastTime Error: %s", err.Error())
			continue
		}

martianzhang's avatar
martianzhang 已提交
184
		subHour := time.Since(pastTime).Hours()
L
liipx 已提交
185
		if subHour > float64(minHour) {
186
			if _, err := vEnv.Query(fmt.Sprintf("drop database %s", testDatabase)); err != nil {
L
liipx 已提交
187
				common.Log.Error("CleanupTestDatabase failed Error: %s", err.Error())
martianzhang's avatar
martianzhang 已提交
188
				continue
W
WithLin 已提交
189
			}
L
liipx 已提交
190 191
			common.Log.Debug("CleanupTestDatabase drop database %s success", testDatabase)
			continue
W
WithLin 已提交
192
		}
L
liipx 已提交
193
		common.Log.Debug("CleanupTestDatabase by pass database %s, %.2f less than %d hours", testDatabase, subHour, minHour)
W
WithLin 已提交
194
	}
martianzhang's avatar
martianzhang 已提交
195 196
	err = dbs.Rows.Close()
	common.LogIfError(err, "")
L
liipx 已提交
197
	common.Log.Debug("CleanupTestDatabase done")
W
WithLin 已提交
198 199
}

martianzhang's avatar
martianzhang 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
// ChangeDB use db change dsn Database
func ChangeDB(env *database.Connector, sql string) {
	stmt, err := sqlparser.Parse(sql)
	if err != nil {
		return
	}

	switch stmt := stmt.(type) {
	case *sqlparser.Use:
		if stmt.DBName.String() != "" {
			env.Database = stmt.DBName.String()
		}
	}
}

martianzhang's avatar
fix #98  
martianzhang 已提交
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
func CurrentDB(sql, db string) string {
	stmt, err := sqlparser.Parse(sql)
	if err != nil {
		return common.Config.TestDSN.Schema
	}

	switch stmt := stmt.(type) {
	case *sqlparser.Use:
		if stmt.DBName.String() != "" {
			db = stmt.DBName.String()
		}
	}
	if db == "" {
		db = common.Config.TestDSN.Schema
	}
	return db
}

martianzhang's avatar
martianzhang 已提交
233 234
// BuildVirtualEnv rEnv 为 SQL 源环境,DB 使用的信息从接口获取
// 注意:如果是 USE, DDL 等语句,执行完第一条就会返回,后面的 SQL 不会执行
235
func (vEnv *VirtualEnv) BuildVirtualEnv(rEnv *database.Connector, SQLs ...string) bool {
martianzhang's avatar
martianzhang 已提交
236 237 238 239
	var stmt sqlparser.Statement
	var err error

	// 置空错误信息
240
	vEnv.Error = nil
martianzhang's avatar
martianzhang 已提交
241
	// 检测是否已经创建初始数据库,如果未创建则创建一个名称 hash 过的映射数据库
242
	err = vEnv.createDatabase(rEnv)
martianzhang's avatar
martianzhang 已提交
243 244 245 246 247 248 249 250
	common.LogIfWarn(err, "")

	// 测试环境检测
	if common.Config.TestDSN.Disable {
		common.Log.Info("BuildVirtualEnv TestDSN not config")
		return true
	}

martianzhang's avatar
martianzhang 已提交
251
	// 判断 rEnv 中是否指定了 DB
martianzhang's avatar
martianzhang 已提交
252 253 254 255 256 257 258 259
	if rEnv.Database == "" {
		common.Log.Error("BuildVirtualEnv no database specified, TestDSN init failed")
		return false
	}

	// 库表提取
	meta := make(map[string]*common.DB)
	for _, sql := range SQLs {
260
		common.Log.Debug("BuildVirtualEnv Database&TableName Mapping, SQL: %s", sql)
martianzhang's avatar
martianzhang 已提交
261 262 263 264 265 266 267 268 269 270 271
		stmt, err = sqlparser.Parse(sql)
		if err != nil {
			common.Log.Error("BuildVirtualEnv Error : %v", err)
			return false
		}

		// 语句类型判断
		switch stmt := stmt.(type) {
		case *sqlparser.Use:
			// 如果是use语句,则更改基础环配置
			if _, ok := meta[stmt.DBName.String()]; !ok {
martianzhang's avatar
martianzhang 已提交
272
				// 如果USE了一个线上环境不存在的数据库,将创建该数据库
martianzhang's avatar
martianzhang 已提交
273 274 275 276
				meta[stmt.DBName.String()] = common.NewDB(stmt.DBName.String())
				rEnv.Database = stmt.DBName.String()

				// use DB 后检查 DB是否已经创建,如果没有创建则创建DB
277
				err = vEnv.createDatabase(rEnv)
martianzhang's avatar
martianzhang 已提交
278 279 280 281 282 283
				common.LogIfWarn(err, "")
			}
			return true
		case *sqlparser.DDL:
			// 如果是DDL,则先获取DDL对应的表结构,然后直接在测试环境接执行SQL
			// 为不影响其他SQL操作,复制一个Connector对象,将数据库切换到对应的DB上直接执行
284
			vEnv.Database = vEnv.DBRef[rEnv.Database]
martianzhang's avatar
martianzhang 已提交
285

martianzhang's avatar
martianzhang 已提交
286
			// 为了支持并发,需要将DB进行映射,但 db.table 这种形式无法保证 DB 的映射是正确的
martianzhang's avatar
martianzhang 已提交
287
			// TODO:暂不支持 create db.tableName (id int) 形式的建表语句
L
liipx 已提交
288
			if stmt.Table.Qualifier.String() != "" {
martianzhang's avatar
martianzhang 已提交
289
				common.Log.Error("BuildVirtualEnv DDL Not support db.tb format")
martianzhang's avatar
martianzhang 已提交
290 291 292
				return false
			}

L
liipx 已提交
293 294
			for _, tb := range stmt.FromTables {
				if tb.Qualifier.String() != "" {
martianzhang's avatar
martianzhang 已提交
295
					common.Log.Error("BuildVirtualEnv DDL Not support db.tb format")
L
liipx 已提交
296 297 298 299 300 301
					return false
				}
			}

			for _, tb := range stmt.ToTables {
				if tb.Qualifier.String() != "" {
martianzhang's avatar
martianzhang 已提交
302
					common.Log.Error("BuildVirtualEnv DDL Not support db.tb format")
L
liipx 已提交
303 304 305 306
					return false
				}
			}

martianzhang's avatar
martianzhang 已提交
307 308 309
			// 拉取表结构
			table := stmt.Table.Name.String()
			if table != "" {
310
				err = vEnv.createTable(rEnv, table)
L
liipx 已提交
311 312 313 314 315 316
				// 这里如果报错可能有两种可能:
				// 1. SQL 是 Create 语句,线上环境并没有相关的库表结构
				// 2. 在测试环境中执行 SQL 报错
				// 如果是因为 Create 语句报错,后续会在测试环境中直接执行 create 语句,不会对程序有负面影响
				// 如果是因为执行 SQL 报错,那么其他地方执行 SQL 的时候也一定会报错
				// 所以这里不需要 `return false`,可以继续执行
martianzhang's avatar
martianzhang 已提交
317
				if err != nil {
318
					common.Log.Warning("BuildVirtualEnv Error : %v", err)
martianzhang's avatar
martianzhang 已提交
319 320 321
				}
			}

322
			_, err = vEnv.Query(sql)
martianzhang's avatar
martianzhang 已提交
323 324 325 326
			if err != nil {
				switch stmt.Action {
				case "create", "alter":
					// 如果是创建或者修改语句,且报错信息为如重复建表、重复索引等信息,将错误反馈到上一次层输出建议
327
					vEnv.Error = err
martianzhang's avatar
martianzhang 已提交
328 329 330 331 332 333 334 335 336
				default:
					common.Log.Error("BuildVirtualEnv DDL Execute Error : %v", err)
				}
			}
			return true
		}

		meta := ast.GetMeta(stmt, nil)

martianzhang's avatar
martianzhang 已提交
337
		// 由于 DB 环境可能是变的,所以需要每一次都单独的提取库表结构,整体随着 rEnv 的变动而发生变化
martianzhang's avatar
martianzhang 已提交
338 339 340 341
		for db, table := range meta {
			if db == "" {
				db = rEnv.Database
			}
342
			rEnv.Database = db
martianzhang's avatar
martianzhang 已提交
343 344 345 346 347 348 349 350

			// 创建数据库环境
			for _, tb := range table.Table {
				if tb.TableName == "" {
					continue
				}

				// 视图检查
351 352
				common.Log.Debug("BuildVirtualEnv Checking view -- %s.%s", rEnv.Database, tb.TableName)
				tbStatus, err := rEnv.ShowTableStatus(tb.TableName)
martianzhang's avatar
martianzhang 已提交
353 354 355 356 357 358
				if err != nil {
					common.Log.Error("BuildVirtualEnv ShowTableStatus Error : %v", err)
					return false
				}

				// 如果是视图,解析语句
359
				if len(tbStatus.Rows) > 0 && string(tbStatus.Rows[0].Comment) == "VIEW" {
martianzhang's avatar
martianzhang 已提交
360
					var viewDDL string
361
					viewDDL, err = rEnv.ShowCreateTable(tb.TableName)
martianzhang's avatar
martianzhang 已提交
362 363 364 365 366 367
					if err != nil {
						common.Log.Error("BuildVirtualEnv create view failed: %v", err)
						return false
					}

					startIdx := strings.Index(viewDDL, "AS")
martianzhang's avatar
martianzhang 已提交
368 369 370 371
					if startIdx < 0 || viewDDL == "" {
						common.Log.Error("BuildVirtualEnv '%s' got '%s', Index: %d", tb.TableName, viewDDL, startIdx)
						return false
					}
martianzhang's avatar
martianzhang 已提交
372
					viewDDL = viewDDL[startIdx+2:]
373
					if !vEnv.BuildVirtualEnv(rEnv, viewDDL) {
martianzhang's avatar
martianzhang 已提交
374 375 376 377
						return false
					}
				}

378
				err = vEnv.createTable(rEnv, tb.TableName)
martianzhang's avatar
martianzhang 已提交
379
				if err != nil {
380
					common.Log.Error("BuildVirtualEnv %s.%s Error : %v", rEnv.Database, tb.TableName, err)
martianzhang's avatar
martianzhang 已提交
381 382 383 384 385 386 387 388
					return false
				}
			}
		}
	}
	return true
}

389
func (vEnv *VirtualEnv) createDatabase(rEnv *database.Connector) error {
martianzhang's avatar
martianzhang 已提交
390
	// 生成映射关系
391 392
	if _, ok := vEnv.DBRef[rEnv.Database]; ok {
		common.Log.Debug("createDatabase, Database `%s` has created, mapping from `%s`", vEnv.DBRef[rEnv.Database], rEnv.Database)
martianzhang's avatar
martianzhang 已提交
393 394 395
		return nil
	}

L
liipx 已提交
396
	// optimizer_YYMMDDHHmmss_xxxx
397
	dbHash := fmt.Sprintf("optimizer_%s_%s", // Total 39 bytes
martianzhang's avatar
martianzhang 已提交
398
		time.Now().Format("060102150405"), // 12 Bytes 180102030405
399
		strings.ToLower(uniuri.New()))     // 16 Bytes random string
400 401
	common.Log.Debug("createDatabase, mapping `%s` :`%s`-->`%s`", rEnv.Database, rEnv.Database, dbHash)
	ddl, err := rEnv.ShowCreateDatabase(rEnv.Database)
martianzhang's avatar
martianzhang 已提交
402 403
	if err != nil {
		common.Log.Warning("createDatabase, rEnv.ShowCreateDatabase Error : %v", err)
martianzhang's avatar
martianzhang 已提交
404
		ddl = fmt.Sprintf("create database `%s` character set %s", rEnv.Database, rEnv.Charset)
martianzhang's avatar
martianzhang 已提交
405 406
	}

407
	ddl = strings.Replace(ddl, rEnv.Database, dbHash, -1)
martianzhang's avatar
martianzhang 已提交
408
	if ddl == "" {
409
		return fmt.Errorf("dbName: '%s' get create info error", rEnv.Database)
martianzhang's avatar
martianzhang 已提交
410
	}
411
	res, err := vEnv.Query(ddl)
martianzhang's avatar
martianzhang 已提交
412 413 414 415
	if err != nil {
		common.Log.Warning("createDatabase, Error : %v", err)
		return err
	}
416 417
	err = res.Rows.Close()
	common.LogIfWarn(err, "")
martianzhang's avatar
martianzhang 已提交
418 419

	// 创建成功,添加映射记录
420 421
	vEnv.DBRef[rEnv.Database] = dbHash
	vEnv.Hash2DB[dbHash] = rEnv.Database
martianzhang's avatar
martianzhang 已提交
422 423 424 425
	return nil
}

/*
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
@input:

	database.Connector 为一个线上环境数据库连接句柄的复制,因为在处理SQL时需要对上下文进行关联处理,
	所以存在修改DB连接参数(主要是数据库名称变更)的可能性,为了不影响整体上下文的环境,所以需要一个镜像句柄来做当前环境的操作。

	dbName, tbName: 需要在环境中操作的库表名称,

@output:

	return 执行过程中的错误

NOTE:

	该函数会将线上环境中使用到的库表结构复制到测试环境中,为后续操作提供基础环境。
	传入的库表名称均来自于对AST的解析,库表名称的获取遵循以下原则:
		如果未在SQL中指定数据库名称,则数据库一定是配置文件(或命令行参数传入DSN)中指定的数据库
		如果一个SQL中存在多个数据库,则只能有一个数据库是没有在SQL中被显示指定的(即DSN中指定的数据库)

TODO:

	在一些可能的情况下,由于数据库配置的不一致(如SQL_MODE不同)导致remote环境的库表无法正确的在测试环境进行同步,
	soar 能够做出判断并进行 session 级别的修改,但是这一阶段可用性保证应该是由用户提供两个完全相同(或测试环境兼容线上环境)
	的数据库环境来实现的。
martianzhang's avatar
martianzhang 已提交
449
*/
450 451 452 453 454 455 456 457
func (vEnv *VirtualEnv) createTable(rEnv *database.Connector, tbName string) error {
	// 判断数据库是否已经创建
	if vEnv.DBRef[rEnv.Database] == "" {
		// 若没创建,则创建数据库
		err := vEnv.createDatabase(rEnv)
		if err != nil {
			return err
		}
martianzhang's avatar
martianzhang 已提交
458 459
	}

460 461
	if vEnv.TableMap[rEnv.Database] == nil {
		vEnv.TableMap[rEnv.Database] = make(map[string]string)
martianzhang's avatar
martianzhang 已提交
462 463 464 465 466 467 468
	}

	if strings.ToLower(tbName) == "dual" {
		common.Log.Debug("createTable, %s no need create", tbName)
		return nil
	}

469 470
	if vEnv.TableMap[rEnv.Database][tbName] != "" {
		common.Log.Debug("createTable, `%s`.`%s` has created, mapping from `%s`.`%s`", vEnv.DBRef[rEnv.Database], tbName, rEnv.Database, tbName)
martianzhang's avatar
martianzhang 已提交
471 472 473
		return nil
	}

474
	common.Log.Debug("createTable, Database: %s, TableName: %s", vEnv.DBRef[rEnv.Database], tbName)
martianzhang's avatar
martianzhang 已提交
475 476 477 478

	//  TODO:查看是否有外键关联(done),对外键的支持 (未解决循环依赖的问题)

	// 记录Table创建信息
479
	vEnv.TableMap[rEnv.Database][tbName] = tbName
martianzhang's avatar
martianzhang 已提交
480 481 482 483 484 485 486 487 488 489 490

	// 生成建表语句
	common.Log.Debug("createTable DSN(%s/%s): generate ddl", rEnv.Addr, rEnv.Database)
	ddl, err := rEnv.ShowCreateTable(tbName)
	if err != nil {
		// 有可能是用户新建表,因此线上环境查不到
		common.Log.Error("createTable, %s DDL Error : %v", tbName, err)
		return err
	}

	// 改变数据环境
491 492
	vEnv.Database = vEnv.DBRef[rEnv.Database]
	res, err := vEnv.Query(ddl)
martianzhang's avatar
martianzhang 已提交
493 494
	if err != nil {
		// 有可能是用户新建表,因此线上环境查不到
martianzhang's avatar
martianzhang 已提交
495
		common.Log.Error("createTable: %s Error : %v", tbName, err)
martianzhang's avatar
martianzhang 已提交
496 497
		return err
	}
498 499
	err = res.Rows.Close()
	common.LogIfWarn(err, "")
martianzhang's avatar
martianzhang 已提交
500 501 502

	// 泵取数据
	if common.Config.Sampling {
503 504
		common.Log.Debug("createTable, Start Sampling data from %s.%s to %s.%s ...", rEnv.Database, tbName, vEnv.DBRef[rEnv.Database], tbName)
		err = vEnv.SamplingData(rEnv, tbName)
martianzhang's avatar
martianzhang 已提交
505
	}
martianzhang's avatar
martianzhang 已提交
506
	return err
martianzhang's avatar
martianzhang 已提交
507 508
}

martianzhang's avatar
martianzhang 已提交
509
// GenTableColumns 为 Rewrite 提供的结构体初始化
510
func (vEnv *VirtualEnv) GenTableColumns(meta common.Meta) common.TableColumns {
martianzhang's avatar
martianzhang 已提交
511 512 513 514 515 516 517
	tableColumns := make(common.TableColumns)
	for dbName, db := range meta {
		for _, tb := range db.Table {
			// 防止传入非预期值
			if tb == nil {
				break
			}
518
			td, err := vEnv.Connector.ShowColumns(tb.TableName)
martianzhang's avatar
martianzhang 已提交
519 520 521 522 523 524 525
			if err != nil {
				common.Log.Warn("GenTableColumns, ShowColumns Error: " + err.Error())
				break
			}

			// tableColumns 初始化
			if dbName == "" {
526
				dbName = vEnv.RealDB(vEnv.Connector.Database)
martianzhang's avatar
martianzhang 已提交
527 528 529 530 531 532 533 534 535 536 537
			}

			if _, ok := tableColumns[dbName]; !ok {
				tableColumns[dbName] = make(map[string][]*common.Column)
			}

			if _, ok := tableColumns[dbName][tb.TableName]; !ok {
				tableColumns[dbName][tb.TableName] = make([]*common.Column, 0)
			}

			if len(tb.Column) == 0 {
martianzhang's avatar
martianzhang 已提交
538
				// tb.column 为空说明 SQL 里这个表是用的*来查询
martianzhang's avatar
martianzhang 已提交
539 540 541 542 543 544 545 546 547 548 549
				if err != nil {
					common.Log.Error("ast.Rewrite ShowColumns, Error: %v", err)
					break
				}

				for _, colInfo := range td.DescValues {
					tableColumns[dbName][tb.TableName] = append(tableColumns[dbName][tb.TableName], &common.Column{
						Name:       colInfo.Field,
						DB:         dbName,
						Table:      tb.TableName,
						DataType:   colInfo.Type,
550
						Character:  string(colInfo.Collation),
martianzhang's avatar
martianzhang 已提交
551
						Key:        colInfo.Key,
552
						Default:    string(colInfo.Default),
martianzhang's avatar
martianzhang 已提交
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
						Extra:      colInfo.Extra,
						Comment:    colInfo.Comment,
						Privileges: colInfo.Privileges,
						Null:       colInfo.Null,
					})
				}
			} else {
				// tb.column如果不为空则需要把使用到的列填写进去
				var columns []*common.Column
				for _, col := range tb.Column {
					for _, colInfo := range td.DescValues {
						if col.Name == colInfo.Field {
							// 根据获取的信息将列的信息补全
							col.DB = dbName
							col.Table = tb.TableName
							col.DataType = colInfo.Type
569
							col.Character = string(colInfo.Collation)
martianzhang's avatar
martianzhang 已提交
570
							col.Key = colInfo.Key
571
							col.Default = string(colInfo.Default)
martianzhang's avatar
martianzhang 已提交
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
							col.Extra = colInfo.Extra
							col.Comment = colInfo.Comment
							col.Privileges = colInfo.Privileges
							col.Null = colInfo.Null

							columns = append(columns, col)
							break
						}
					}
				}
				tableColumns[dbName][tb.TableName] = columns
			}
		}
	}
	return tableColumns
}