github.com/matrixorigin/matrixone@v1.2.0/pkg/bootstrap/versions/upgrade_strategy.go (about) 1 // Copyright 2024 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package versions 16 17 import ( 18 "fmt" 19 "go.uber.org/zap" 20 21 "github.com/matrixorigin/matrixone/pkg/catalog" 22 "github.com/matrixorigin/matrixone/pkg/common/moerr" 23 "github.com/matrixorigin/matrixone/pkg/container/vector" 24 "github.com/matrixorigin/matrixone/pkg/util/executor" 25 ) 26 27 const ( 28 T_any = "ANY" 29 T_bool = "BOOL" 30 T_bit = "BIT" 31 T_int8 = "TINYINT" 32 T_int16 = "SMALLINT" 33 T_int32 = "INT" 34 T_int64 = "BIGINT" 35 T_uint8 = "TINYINT UNSIGNED" 36 T_uint16 = "SMALLINT UNSIGNED" 37 T_uint32 = "INT UNSIGNED" 38 T_uint64 = "BIGINT UNSIGNED" 39 T_float32 = "FLOAT" 40 T_float64 = "DOUBLE" 41 T_date = "DATE" 42 T_datetime = "DATETIME" 43 T_time = "TIME" 44 T_timestamp = "TIMESTAMP" 45 T_char = "CHAR" 46 T_varchar = "VARCHAR" 47 T_binary = "BINARY" 48 T_varbinary = "VARBINARY" 49 T_json = "JSON" 50 T_tuple = "TUPLE" 51 T_decimal64 = "DECIMAL64" 52 T_decimal128 = "DECIMAL128" 53 T_decimal256 = "DECIMAL256" 54 T_blob = "BLOB" 55 T_text = "TEXT" 56 T_TS = "TRANSACTION TIMESTAMP" 57 T_Rowid = "ROWID" 58 T_uuid = "UUID" 59 T_Blockid = "BLOCKID" 60 T_interval = "INTERVAL" 61 T_array_float32 = "VECF32" 62 T_array_float64 = "VECF64" 63 T_enum = "ENUM" 64 ) 65 66 type UpgradeType int8 67 68 const ( 69 // alter table structure 70 ADD_COLUMN UpgradeType = iota 71 DROP_COLUMN 72 CHANGE_COLUMN 73 MODIFY_COLUMN 74 RENAME_COLUMN 75 ALTER_COLUMN_DEFAULT 76 ADD_INDEX 77 DROP_INDEX 78 ALTER_INDEX_VISIBLE 79 ADD_CONSTRAINT_UNIQUE_INDEX 80 ADD_CONSTRAINT_PRIMARY_KEY 81 ADD_CONSTRAINT_FOREIGN_KEY 82 DROP_CONSTRAINT 83 DROP_PRIMARY_KEY 84 DROP_FOREIGN_KEY 85 CREATE_NEW_TABLE 86 87 // alter view definition 88 MODIFY_VIEW 89 CREATE_VIEW 90 DROP_VIEW 91 92 CREATE_DATABASE 93 MODIFY_METADATA 94 ) 95 96 // ColumnInfo Describe the detailed information of the table column 97 type ColumnInfo struct { 98 IsExits bool 99 Name string 100 Nullable bool 101 ColType string 102 ChatLength int64 103 Precision int64 104 Scale int64 105 datetimePrecision int64 106 Position int32 107 Default string 108 Extra string 109 Comment string 110 } 111 112 // UpgradeEntry is used to designate a specific upgrade entity. 113 // Users must provide `UpgSql` and `CheckFunc` implementations 114 type UpgradeEntry struct { 115 Schema string 116 TableName string 117 // UpgType declare the type of upgrade 118 UpgType UpgradeType 119 // UpgSql is used to perform upgrade operations 120 UpgSql string 121 // CheckFunc was used to check whether an upgrade is required 122 // return true if the system is already in the final state and does not need to be upgraded, 123 // otherwise return false 124 CheckFunc func(txn executor.TxnExecutor, accountId uint32) (bool, error) 125 PreSql string 126 PostSql string 127 } 128 129 // Upgrade entity execution upgrade entrance 130 func (u *UpgradeEntry) Upgrade(txn executor.TxnExecutor, accountId uint32) error { 131 exist, err := u.CheckFunc(txn, accountId) 132 if err != nil { 133 getLogger().Error("execute upgrade entry check error", zap.Error(err), zap.String("upgrade entry", u.String())) 134 return err 135 } 136 137 if exist { 138 return nil 139 } else { 140 // 1. First, judge whether there is prefix sql 141 if u.PreSql != "" { 142 res, err := txn.Exec(u.PreSql, executor.StatementOption{}.WithAccountID(accountId)) 143 if err != nil { 144 return err 145 } 146 res.Close() 147 } 148 149 // 2. Second, Execute upgrade sql 150 res, err := txn.Exec(u.UpgSql, executor.StatementOption{}.WithAccountID(accountId)) 151 if err != nil { 152 getLogger().Error("execute upgrade entry sql error", zap.Error(err), zap.String("upgrade entry", u.String())) 153 return err 154 } 155 res.Close() 156 157 // 2. Third, after the upgrade is completed, judge whether there is post-sql 158 if u.PostSql != "" { 159 res, err = txn.Exec(u.PostSql, executor.StatementOption{}.WithAccountID(accountId)) 160 if err != nil { 161 return err 162 } 163 res.Close() 164 } 165 } 166 return nil 167 } 168 169 func (u *UpgradeEntry) String() string { 170 return fmt.Sprintf("UpgradeEntry type:%v schema: %s.%s, upgrade sql: %s", 171 u.UpgType, 172 u.Schema, 173 u.TableName, 174 u.UpgSql) 175 } 176 177 // CheckTableColumn Check if the columns in the table exist, and if so, 178 // return the detailed information of the column 179 func CheckTableColumn(txn executor.TxnExecutor, 180 accountId uint32, 181 schema string, 182 tableName string, 183 columnName string) (ColumnInfo, error) { 184 185 checkInput := func(input string) bool { 186 switch input { 187 case "YES", "yes": 188 return true 189 case "NO", "no": 190 return false 191 default: 192 return false 193 } 194 } 195 196 colInfo := ColumnInfo{ 197 IsExits: false, 198 Name: columnName, 199 } 200 201 sql := fmt.Sprintf(`SELECT mo_show_visible_bin(atttyp, 2) AS DATA_TYPE, 202 CASE WHEN attnotnull != 0 THEN 'NO' ELSE 'YES' END AS IS_NULLABLE, 203 internal_char_length(atttyp) AS CHARACTER_MAXIMUM_LENGTH, 204 internal_numeric_precision(atttyp) AS NUMERIC_PRECISION, 205 internal_numeric_scale(atttyp) AS NUMERIC_SCALE, 206 internal_datetime_scale(atttyp) AS DATETIME_PRECISION, 207 attnum AS ORDINAL_POSITION, 208 mo_show_visible_bin(att_default, 1) AS COLUMN_DEFAULT, 209 CASE WHEN att_is_auto_increment = 1 THEN 'auto_increment' ELSE '' END AS EXTRA, 210 att_comment AS COLUMN_COMMENT FROM mo_catalog.mo_columns 211 WHERE att_relname != 'mo_increment_columns' AND att_relname NOT LIKE '__mo_cpkey_%%' 212 AND attname != '__mo_rowid' 213 AND att_database = '%s' and att_relname = '%s' and attname = '%s';`, schema, tableName, columnName) 214 215 if accountId == catalog.System_Account { 216 sql = fmt.Sprintf(`SELECT mo_show_visible_bin(atttyp, 2) AS DATA_TYPE, 217 CASE WHEN attnotnull != 0 THEN 'NO' ELSE 'YES' END AS IS_NULLABLE, 218 internal_char_length(atttyp) AS CHARACTER_MAXIMUM_LENGTH, 219 internal_numeric_precision(atttyp) AS NUMERIC_PRECISION, 220 internal_numeric_scale(atttyp) AS NUMERIC_SCALE, 221 internal_datetime_scale(atttyp) AS DATETIME_PRECISION, 222 attnum AS ORDINAL_POSITION, 223 mo_show_visible_bin(att_default, 1) AS COLUMN_DEFAULT, 224 CASE WHEN att_is_auto_increment = 1 THEN 'auto_increment' ELSE '' END AS EXTRA, 225 att_comment AS COLUMN_COMMENT FROM mo_catalog.mo_columns 226 WHERE att_relname != 'mo_increment_columns' AND att_relname NOT LIKE '__mo_cpkey_%%' 227 AND attname != '__mo_rowid' AND account_id = 0 228 AND att_database = '%s' and att_relname = '%s' and attname = '%s';`, schema, tableName, columnName) 229 } 230 231 res, err := txn.Exec(sql, executor.StatementOption{}.WithAccountID(accountId)) 232 if err != nil { 233 return colInfo, err 234 } 235 defer res.Close() 236 237 res.ReadRows(func(rows int, cols []*vector.Vector) bool { 238 data_type := cols[0].GetStringAt(0) 239 is_nullable := cols[1].GetStringAt(0) 240 character_length := vector.GetFixedAt[int64](cols[2], 0) 241 numeric_precision := vector.GetFixedAt[int64](cols[3], 0) 242 numeric_scale := vector.GetFixedAt[int64](cols[4], 0) 243 datetime_precision := vector.GetFixedAt[int64](cols[5], 0) 244 ordinal_position := vector.GetFixedAt[int32](cols[6], 0) 245 column_default := cols[7].GetStringAt(0) 246 extra := cols[8].GetStringAt(0) 247 column_comment := cols[9].GetStringAt(0) 248 249 colInfo.IsExits = true 250 colInfo.ColType = data_type 251 colInfo.Nullable = checkInput(is_nullable) 252 colInfo.ChatLength = character_length 253 colInfo.Precision = numeric_precision 254 colInfo.Scale = numeric_scale 255 colInfo.datetimePrecision = datetime_precision 256 colInfo.Position = ordinal_position 257 colInfo.Default = column_default 258 colInfo.Extra = extra 259 colInfo.Comment = column_comment 260 261 return false 262 }) 263 return colInfo, nil 264 } 265 266 // CheckViewDefinition Check if the view exists, if so, return true and return the view definition 267 func CheckViewDefinition(txn executor.TxnExecutor, accountId uint32, schema string, viewName string) (bool, string, error) { 268 sql := fmt.Sprintf("SELECT tbl.rel_createsql AS `VIEW_DEFINITION` FROM mo_catalog.mo_tables tbl LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id WHERE tbl.relkind = 'v' AND tbl.reldatabase = '%s' AND tbl.relname = '%s'", schema, viewName) 269 if accountId == catalog.System_Account { 270 sql = fmt.Sprintf("SELECT tbl.rel_createsql AS `VIEW_DEFINITION` FROM mo_catalog.mo_tables tbl LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id WHERE tbl.relkind = 'v' AND account_id = 0 AND tbl.reldatabase = '%s' AND tbl.relname = '%s'", schema, viewName) 271 } 272 273 res, err := txn.Exec(sql, executor.StatementOption{}.WithAccountID(accountId)) 274 if err != nil { 275 return false, "", err 276 } 277 defer res.Close() 278 279 view_def := "" 280 loaded := false 281 n := 0 282 res.ReadRows(func(rows int, cols []*vector.Vector) bool { 283 view_def = cols[0].GetStringAt(0) 284 n++ 285 loaded = true 286 return false 287 }) 288 289 if loaded && n > 1 { 290 panic("BUG: Duplicate column names in table") 291 } 292 return loaded, view_def, nil 293 } 294 295 // CheckTableDefinition is used to check if the specified table definition exists in the specified database. If it exists, 296 // return true; otherwise, return false. 297 func CheckTableDefinition(txn executor.TxnExecutor, accountId uint32, schema string, tableName string) (bool, error) { 298 if schema == "" || tableName == "" { 299 return false, moerr.NewInternalErrorNoCtx("schema name or table name is empty") 300 } 301 302 sql := fmt.Sprintf(`SELECT reldatabase, relname, account_id FROM mo_catalog.mo_tables tbl 303 WHERE tbl.relname NOT LIKE '__mo_index_%%' AND tbl.relkind != 'partition' 304 AND reldatabase = '%s' AND relname = '%s'`, schema, tableName) 305 if accountId == catalog.System_Account { 306 sql = fmt.Sprintf(`SELECT reldatabase, relname, account_id FROM mo_catalog.mo_tables tbl 307 WHERE tbl.relname NOT LIKE '__mo_index_%%' AND tbl.relkind != 'partition' 308 AND account_id = 0 AND reldatabase = '%s' AND relname = '%s'`, schema, tableName) 309 } 310 311 res, err := txn.Exec(sql, executor.StatementOption{}.WithAccountID(accountId)) 312 if err != nil { 313 return false, err 314 } 315 defer res.Close() 316 317 loaded := false 318 res.ReadRows(func(rows int, cols []*vector.Vector) bool { 319 loaded = true 320 return false 321 }) 322 323 return loaded, nil 324 } 325 326 // CheckDatabaseDefinition This function is used to check if the database definition exists. 327 // If it exists, return true; otherwise, return false. 328 func CheckDatabaseDefinition(txn executor.TxnExecutor, accountId uint32, schema string) (bool, error) { 329 if schema == "" { 330 return false, moerr.NewInternalErrorNoCtx("schema name is empty") 331 } 332 333 sql := fmt.Sprintf(`select datname from mo_catalog.mo_database where datname = '%s'`, schema) 334 if accountId == catalog.System_Account { 335 sql = fmt.Sprintf(`select datname from mo_catalog.mo_database where account_id = 0 and datname = '%s'`, schema) 336 } 337 338 res, err := txn.Exec(sql, executor.StatementOption{}.WithAccountID(accountId)) 339 if err != nil { 340 return false, err 341 } 342 defer res.Close() 343 344 loaded := false 345 res.ReadRows(func(rows int, cols []*vector.Vector) bool { 346 loaded = true 347 return false 348 }) 349 350 return loaded, nil 351 } 352 353 // CheckTableDataExist Used to checks whether a table contains specific data 354 // This function executes the given SQL query, returns true if the result set is not empty, otherwise returns false. 355 func CheckTableDataExist(txn executor.TxnExecutor, accountId uint32, sql string) (bool, error) { 356 if sql == "" { 357 return false, moerr.NewInternalErrorNoCtx("check table data sql is empty") 358 } 359 360 res, err := txn.Exec(sql, executor.StatementOption{}.WithAccountID(accountId)) 361 if err != nil { 362 return false, err 363 } 364 defer res.Close() 365 366 loaded := false 367 res.ReadRows(func(rows int, cols []*vector.Vector) bool { 368 loaded = true 369 return false 370 }) 371 372 return loaded, nil 373 } 374 375 // CheckIndexDefinition Used to check if a certain index is defined in the table 376 // This function executes the given SQL query, returns true if the result set is not empty, otherwise returns false. 377 func CheckIndexDefinition(txn executor.TxnExecutor, accountId uint32, schema string, tableName string, indexName string) (bool, error) { 378 if schema == "" || tableName == "" || indexName == "" { 379 return false, moerr.NewInternalErrorNoCtx("schema name or table name or indexName is empty") 380 } 381 382 sql := fmt.Sprintf("select distinct `idx`.`name` from `mo_catalog`.`mo_indexes` `idx` "+ 383 "left join `mo_catalog`.`mo_tables` `tbl` on `idx`.`table_id` = `tbl`.`rel_id` "+ 384 "where `tbl`.`reldatabase` = '%s' AND `tbl`.`relname` = '%s' AND `idx`.`name` = '%s'", 385 schema, tableName, indexName) 386 if accountId == catalog.System_Account { 387 sql = fmt.Sprintf("select distinct `idx`.`name` from `mo_catalog`.`mo_indexes` `idx` "+ 388 "left join `mo_catalog`.`mo_tables` `tbl` on `idx`.`table_id` = `tbl`.`rel_id` "+ 389 "where `tbl`.`account_id` = %d AND `tbl`.`reldatabase` = '%s' AND `tbl`.`relname` = '%s' AND `idx`.`name` = '%s'", 390 accountId, schema, tableName, indexName) 391 } 392 res, err := txn.Exec(sql, executor.StatementOption{}.WithAccountID(accountId)) 393 if err != nil { 394 return false, err 395 } 396 defer res.Close() 397 398 loaded := false 399 res.ReadRows(func(rows int, cols []*vector.Vector) bool { 400 loaded = true 401 return false 402 }) 403 return loaded, nil 404 }