github.com/godaddy-x/freego@v1.0.156/ormx/sqld/model_index.go (about)

     1  package sqld
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  	"github.com/godaddy-x/freego/ormx/sqlc"
     8  	"github.com/godaddy-x/freego/utils"
     9  	"github.com/godaddy-x/freego/zlog"
    10  	"go.mongodb.org/mongo-driver/bson"
    11  	"go.mongodb.org/mongo-driver/mongo"
    12  	"go.mongodb.org/mongo-driver/mongo/options"
    13  	"reflect"
    14  	"sort"
    15  )
    16  
    17  type IndexInfo struct {
    18  	Table        string         // 索引所属的表名
    19  	NonUnique    int            // 索引是否是唯一索引。如果值为 0,则表示索引是唯一索引;如果值为 1,则表示索引是普通索引
    20  	KeyName      string         // 索引的名称
    21  	SeqInIndex   int            // 索引中的列的顺序号
    22  	ColumnName   string         // 索引的列名
    23  	Collation    string         // 索引列的排序规则
    24  	Cardinality  interface{}    // 索引列的基数,即不重复的索引值数量
    25  	SubPart      sql.NullString // 索引的子部分长度。通常用于前缀索引,以指示索引的前缀长度
    26  	Packed       sql.NullString // 索引存储的方式
    27  	Null         interface{}    // 索引列是否可以包含 NULL 值
    28  	IndexType    string         // 索引的类型,如 BTREE、HASH 等
    29  	Comment      string         // 索引的注释信息
    30  	IndexComment string         // 索引的额外注释信息
    31  }
    32  
    33  func readyCollection(object sqlc.Object) {
    34  	db, err := NewMongo(Option{Timeout: 120000})
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  	defer db.Close()
    39  	if err := db.Save(object); err != nil {
    40  		panic(err)
    41  	}
    42  	if err := db.Delete(object); err != nil {
    43  		panic(err)
    44  	}
    45  }
    46  
    47  func dropMongoIndex(object sqlc.Object, index []sqlc.Index) bool {
    48  	readyCollection(object)
    49  	db, err := NewMongo(Option{Timeout: 120000})
    50  	if err != nil {
    51  		panic(err)
    52  	}
    53  	defer db.Close()
    54  	coll, err := db.GetDatabase(object.GetTable())
    55  	if err != nil {
    56  		panic(err)
    57  	}
    58  	cur, err := coll.Indexes().List(context.Background())
    59  	if err != nil {
    60  		panic(err)
    61  	}
    62  	var list []map[string]interface{}
    63  	if err := cur.All(context.Background(), &list); err != nil {
    64  		panic(err)
    65  	}
    66  	oldKey := ""
    67  	for _, v := range list {
    68  		key := v["name"].(string)
    69  		if key == "_id_" {
    70  			continue
    71  		}
    72  		oldKey += key
    73  	}
    74  	newKey := ""
    75  	for _, v := range index {
    76  		newKey += v.Name
    77  	}
    78  	if oldKey == newKey {
    79  		return false
    80  	}
    81  	if _, err := coll.Indexes().DropAll(context.Background()); err != nil {
    82  		panic(err)
    83  	}
    84  	return true
    85  }
    86  
    87  func dropMysqlIndex(object sqlc.Object, index []sqlc.Index) bool {
    88  	db, err := NewMysql(Option{Timeout: 120000})
    89  	if err != nil {
    90  		panic(err)
    91  	}
    92  	defer db.Close()
    93  	// 执行查询获取索引信息
    94  	rows, err := db.Db.Query("SHOW INDEX FROM " + object.GetTable())
    95  	if err != nil {
    96  		panic(err)
    97  	}
    98  	defer rows.Close()
    99  
   100  	// 获取查询结果的字段名称
   101  	columns, err := rows.Columns()
   102  	if err != nil {
   103  		panic(err)
   104  	}
   105  
   106  	// 创建一个动态映射,用于存储字段名和对应的值
   107  	result := make(map[string]interface{})
   108  	values := make([]interface{}, len(columns))
   109  	for i := range columns {
   110  		values[i] = new(sql.RawBytes)
   111  	}
   112  
   113  	var indexes []IndexInfo
   114  	for rows.Next() {
   115  		if err := rows.Scan(values...); err != nil {
   116  			panic(err)
   117  		}
   118  		for i, column := range columns {
   119  			if values[i] == nil {
   120  				result[column] = nil // 或者设置为其他默认值
   121  				continue
   122  			}
   123  			result[column] = values[i]
   124  		}
   125  		var index IndexInfo
   126  		index.Table = string(*result["Table"].(*sql.RawBytes))
   127  		index.KeyName = string(*result["Key_name"].(*sql.RawBytes))
   128  		index.ColumnName = string(*result["Column_name"].(*sql.RawBytes))
   129  		index.IndexType = string(*result["Index_type"].(*sql.RawBytes))
   130  		nonUnique, err := utils.StrToInt(string(*result["Non_unique"].(*sql.RawBytes)))
   131  		if err != nil {
   132  			panic(err)
   133  		}
   134  		index.NonUnique = nonUnique
   135  		indexes = append(indexes, index)
   136  	}
   137  
   138  	check := map[string][]string{}
   139  	for _, v := range indexes {
   140  		key := v.KeyName
   141  		if key == "PRIMARY" {
   142  			continue
   143  		}
   144  		m, b := check[key]
   145  		if b {
   146  			check[v.KeyName] = append(m, v.ColumnName)
   147  		} else {
   148  			check[v.KeyName] = []string{v.ColumnName}
   149  		}
   150  	}
   151  	var drop bool
   152  	for _, v := range index {
   153  		if len(v.Name) == 0 || len(v.Key) == 0 {
   154  			panic("table index name/key invalid: " + object.GetTable())
   155  		}
   156  		key, b := check[v.Name]
   157  		if b {
   158  			sort.Strings(key)
   159  			sort.Strings(v.Key)
   160  			if reflect.DeepEqual(key, v.Key) {
   161  				continue
   162  			}
   163  		}
   164  		drop = true
   165  		break
   166  	}
   167  	if !drop {
   168  		return false
   169  	}
   170  	for k, _ := range check { // 确定删除表所有索引
   171  		if _, err := db.Db.Exec("DROP INDEX `" + k + "` ON " + object.GetTable()); err != nil {
   172  			panic(err)
   173  		}
   174  	}
   175  	return true
   176  }
   177  
   178  func addMongoIndex(object sqlc.Object, index sqlc.Index) error {
   179  	db, err := NewMongo(Option{Timeout: 120000})
   180  	if err != nil {
   181  		panic(err)
   182  	}
   183  	defer db.Close()
   184  	coll, err := db.GetDatabase(object.GetTable())
   185  	if err != nil {
   186  		panic(err)
   187  	}
   188  	bsonD := bson.D{}
   189  	for _, v := range index.Key {
   190  		bsonD = append(bsonD, bson.E{Key: v, Value: 1})
   191  	}
   192  	modelIndex := mongo.IndexModel{
   193  		Keys: bsonD, Options: &options.IndexOptions{Name: &index.Name, Unique: &index.Unique},
   194  	}
   195  	if _, err := coll.Indexes().CreateOne(context.Background(), modelIndex); err != nil {
   196  		panic(err)
   197  	}
   198  	return nil
   199  }
   200  
   201  func addMysqlIndex(object sqlc.Object, index sqlc.Index) error {
   202  	if len(index.Key) == 0 {
   203  		zlog.Warn("addMysqlIndex keys is nil", 0, zlog.Any("object", object))
   204  		return nil
   205  	}
   206  	if len(index.Name) == 0 {
   207  		panic("index key name is nil: " + object.GetTable())
   208  	}
   209  	var columns string
   210  	for _, v := range index.Key {
   211  		if len(v) == 0 {
   212  			panic("index key field is nil: " + object.GetTable())
   213  		}
   214  		columns += utils.AddStr(",`", v, "`")
   215  	}
   216  	sql := "CREATE"
   217  	if index.Unique {
   218  		sql = utils.AddStr(sql, " UNIQUE ")
   219  	}
   220  	sql = utils.AddStr(sql, " INDEX ")
   221  	sql = utils.AddStr(sql, "`", index.Name, "`")
   222  	sql = utils.AddStr(sql, " ON ", object.GetTable(), " (")
   223  	sql = utils.AddStr(sql, columns[1:], ")")
   224  
   225  	db, err := NewMysql(Option{Timeout: 120000})
   226  	if err != nil {
   227  		panic(err)
   228  	}
   229  	defer db.Close()
   230  	if _, err := db.Db.Exec(sql); err != nil {
   231  		panic(err)
   232  	}
   233  	return nil
   234  }
   235  
   236  // RebuildMongoDBIndex 先删除所有表索引,再按配置新建(线上慎用功能)
   237  func RebuildMongoDBIndex() error {
   238  	for _, model := range modelDrivers {
   239  		index := model.Object.NewIndex()
   240  		if index == nil {
   241  			continue
   242  		}
   243  		if !dropMongoIndex(model.Object, index) {
   244  			fmt.Println(fmt.Sprintf("********* [%s] index consistent, skipping *********", model.Object.GetTable()))
   245  			continue
   246  		}
   247  		fmt.Println(fmt.Sprintf("********* [%s] delete all index *********", model.Object.GetTable()))
   248  		for _, v := range index {
   249  			addMongoIndex(model.Object, v)
   250  			fmt.Println(fmt.Sprintf("********* [%s] add index [%s] *********", model.Object.GetTable(), v.Name))
   251  		}
   252  	}
   253  	return nil
   254  }
   255  
   256  func checkMysqlTable(tableName string) (bool, error) {
   257  	db, err := NewMysql(Option{Timeout: 120000})
   258  	if err != nil {
   259  		panic(err)
   260  	}
   261  	defer db.Close()
   262  	var result string
   263  	if err := db.Db.QueryRow("SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1", tableName).Scan(&result); err != nil {
   264  		if err == sql.ErrNoRows {
   265  			return false, nil // 表不存在
   266  		}
   267  		return false, err // 查询出错
   268  	}
   269  	return true, nil // 表存在
   270  }
   271  
   272  func isInt(s string) bool {
   273  	if s == "int64" || s == "int" {
   274  		return true
   275  	}
   276  	return false
   277  }
   278  
   279  func createTable(model *MdlDriver) error {
   280  	sql := utils.AddStr("CREATE TABLE ", model.TableName, "( ")
   281  	var fields string
   282  	for _, v := range model.FieldElem {
   283  		if len(v.FieldDBType) == 0 {
   284  			if isInt(v.FieldType) {
   285  				fields = utils.AddStr(fields, ",`", v.FieldJsonName, "` ", "BIGINT")
   286  			} else {
   287  				fields = utils.AddStr(fields, ",`", v.FieldJsonName, "` ", "VARCHAR(255)")
   288  			}
   289  		} else {
   290  			fields = utils.AddStr(fields, ",`", v.FieldJsonName, "` ", v.FieldDBType)
   291  		}
   292  		if v.Primary {
   293  			fields = utils.AddStr(fields, " NOT NULL PRIMARY KEY")
   294  		}
   295  		if len(v.FieldComment) > 0 {
   296  			fields = utils.AddStr(fields, " COMMENT '", v.FieldComment, "'")
   297  		}
   298  	}
   299  	sql = utils.AddStr(sql, fields[1:], ")")
   300  	sql = utils.AddStr(sql, " ENGINE=InnoDB DEFAULT CHARSET=", model.Charset, " COLLATE=", model.Collate, ";")
   301  	db, err := NewMysql(Option{Timeout: 120000})
   302  	if err != nil {
   303  		panic(err)
   304  	}
   305  	defer db.Close()
   306  	if _, err := db.Db.Exec(sql); err != nil {
   307  		return err
   308  	}
   309  	zlog.Info("create table success", 0, zlog.String("table", model.TableName))
   310  	return nil
   311  }
   312  
   313  // RebuildMysqlDBIndex 先删除所有表索引,再按配置新建(线上慎用功能)
   314  func RebuildMysqlDBIndex() error {
   315  	for _, model := range modelDrivers {
   316  		index := model.Object.NewIndex()
   317  		if len(index) == 0 {
   318  			continue
   319  		}
   320  		exist, err := checkMysqlTable(model.Object.GetTable())
   321  		if err != nil {
   322  			panic(err)
   323  		}
   324  		if !exist {
   325  			zlog.Warn("mysql table not exist", 0, zlog.String("table", model.Object.GetTable()))
   326  			if err := createTable(model); err != nil {
   327  				panic(err)
   328  			}
   329  		}
   330  		if !dropMysqlIndex(model.Object, index) {
   331  			fmt.Println(fmt.Sprintf("********* [%s] index consistent, skipping *********", model.Object.GetTable()))
   332  			continue
   333  		}
   334  		fmt.Println(fmt.Sprintf("********* [%s] delete all index *********", model.Object.GetTable()))
   335  		for _, v := range index {
   336  			addMysqlIndex(model.Object, v)
   337  			fmt.Println(fmt.Sprintf("********* [%s] add index [%s] *********", model.Object.GetTable(), v.Name))
   338  		}
   339  	}
   340  	return nil
   341  }