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  }