-
Notifications
You must be signed in to change notification settings - Fork 6.2k
ddl,parser: modify schema to store the partial condition #62759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,7 +61,9 @@ import ( | |
| "github.com/pingcap/tidb/pkg/metrics" | ||
| "github.com/pingcap/tidb/pkg/parser/ast" | ||
| "github.com/pingcap/tidb/pkg/parser/charset" | ||
| "github.com/pingcap/tidb/pkg/parser/format" | ||
| "github.com/pingcap/tidb/pkg/parser/mysql" | ||
| "github.com/pingcap/tidb/pkg/parser/opcode" | ||
| "github.com/pingcap/tidb/pkg/parser/terror" | ||
| "github.com/pingcap/tidb/pkg/sessionctx" | ||
| "github.com/pingcap/tidb/pkg/sessionctx/vardef" | ||
|
|
@@ -398,6 +400,12 @@ func BuildIndexInfo( | |
| idxInfo.Tp = indexOption.Tp | ||
| } | ||
| idxInfo.Global = indexOption.Global | ||
|
|
||
| conditionString, err := CheckAndBuildIndexConditionString(tblInfo, indexOption.Condition) | ||
| if err != nil { | ||
| return nil, errors.Trace(err) | ||
| } | ||
| idxInfo.ConditionExprString = conditionString | ||
| } else { | ||
| // Use btree as default index type. | ||
| idxInfo.Tp = ast.IndexTypeBtree | ||
|
|
@@ -1084,6 +1092,10 @@ func (w *worker) onCreateIndex(jobCtx *jobContext, job *model.Job, isPK bool) (v | |
| return ver, errors.Trace(err) | ||
| } | ||
| allIndexInfos = append(allIndexInfos, indexInfo) | ||
| // The condition in the index option is not marshaled, so we need to set it here. | ||
| if len(arg.ConditionString) > 0 { | ||
| indexInfo.ConditionExprString = arg.ConditionString | ||
| } | ||
| } | ||
|
|
||
| originalState := allIndexInfos[0].State | ||
|
|
@@ -3595,3 +3607,192 @@ func renameHiddenColumns(tblInfo *model.TableInfo, from, to ast.CIStr) { | |
| } | ||
| } | ||
| } | ||
|
|
||
| // CheckAndBuildIndexConditionString validates whether the given expression is compatible with | ||
| // the table schema and returns a string representation of the expression. | ||
| func CheckAndBuildIndexConditionString(tblInfo *model.TableInfo, indexConditionExpr ast.ExprNode) (string, error) { | ||
| if indexConditionExpr == nil { | ||
| return "", nil | ||
| } | ||
|
|
||
| // check partial index condition expression | ||
| err := checkIndexCondition(tblInfo, indexConditionExpr) | ||
| if err != nil { | ||
| return "", errors.Trace(err) | ||
| } | ||
|
|
||
| var sb strings.Builder | ||
| restoreFlags := format.RestoreStringSingleQuotes | format.RestoreKeyWordLowercase | format.RestoreNameBackQuotes | | ||
| format.RestoreSpacesAroundBinaryOperation | format.RestoreWithoutSchemaName | format.RestoreWithoutTableName | ||
| restoreCtx := format.NewRestoreCtx(restoreFlags, &sb) | ||
| sb.Reset() | ||
| err = indexConditionExpr.Restore(restoreCtx) | ||
| if err != nil { | ||
| return "", errors.Trace(err) | ||
| } | ||
|
|
||
| return sb.String(), nil | ||
| } | ||
|
|
||
| func checkIndexCondition(tblInfo *model.TableInfo, indexCondition ast.ExprNode) error { | ||
| // Only the following expressions are supported: | ||
| // 1. column IS NULL | ||
| // 2. column IS NOT NULL | ||
| // 3. column = / != / > / < / >= / <= const | ||
| // The column must be a visible column in the table, and the const must be a literal value with | ||
| // the same type as the column. | ||
| // The column must **NOT** be a generated column. We can loosen this restriction in the future. | ||
| // | ||
| // TODO: support more expressions in the future. | ||
| if indexCondition == nil { | ||
| return nil | ||
| } | ||
|
|
||
| switch cond := indexCondition.(type) { | ||
| case *ast.IsNullExpr: | ||
| // `IS NULL` and `IS NOT NULL` are both in this branch. | ||
| columnName, ok := cond.Expr.(*ast.ColumnNameExpr) | ||
| if !ok { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| "partial index condition must include a column name in the IS NULL expression") | ||
| } | ||
| columnInfo := model.FindColumnInfo(tblInfo.Columns, columnName.Name.Name.L) | ||
| if columnInfo == nil { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("column name %s referenced in partial index condition is not found in table", | ||
| columnName.Name.Name.L)) | ||
| } | ||
| if columnInfo.IsGenerated() { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("generated column %s cannot be used in partial index condition", columnName.Name.Name.L)) | ||
| } | ||
|
|
||
| return nil | ||
| case *ast.BinaryOperationExpr: | ||
| if cond.Op != opcode.EQ && cond.Op != opcode.NE && cond.Op != opcode.GT && | ||
| cond.Op != opcode.LT && cond.Op != opcode.GE && cond.Op != opcode.LE { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("binary operation %s is not supported", cond.Op.String())) | ||
| } | ||
|
|
||
| var columnName *ast.ColumnNameExpr | ||
| var anotherSide ast.ExprNode | ||
| columnName, ok := cond.L.(*ast.ColumnNameExpr) | ||
| if !ok { | ||
| // maybe the right side is a column name | ||
| columnName, ok = cond.R.(*ast.ColumnNameExpr) | ||
| if !ok { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| "partial index condition must include a column name in the binary operation") | ||
| } | ||
|
|
||
| anotherSide = cond.L | ||
| } else { | ||
| anotherSide = cond.R | ||
| } | ||
| columnInfo := model.FindColumnInfo(tblInfo.Columns, columnName.Name.Name.L) | ||
| if columnInfo == nil { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("column name `%s` referenced in partial index condition is not found in table", | ||
| columnName.Name.Name.L)) | ||
| } | ||
| if columnInfo.IsGenerated() { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("generated column %s cannot be used in partial index condition", columnName.Name.Name.L)) | ||
| } | ||
|
|
||
| // The another side must be a literal value, and it must have the same type as the column. | ||
| constantExpr, ok := anotherSide.(ast.ValueExpr) | ||
| if !ok { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| "partial index condition must include a literal value on the other side of the binary operation") | ||
| } | ||
| // Reference `types.DefaultTypeForValue`, they are all possible types for literal values. | ||
| // However, this switch-case still includes more types than the ones we have in that function | ||
| // to avoid breaking in the future. | ||
| // | ||
| // Accept tiny type conversion as the type of the literal value is too limited. We shouldn't | ||
| // force the user to use such a limited range of types. | ||
| // | ||
| // It'll allow precision / length difference in most of the cases. | ||
| switch constantExpr.GetType().GetType() { | ||
| case mysql.TypeTiny, mysql.TypeShort, mysql.TypeLong, mysql.TypeLonglong, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type of an expression can't be tinyint, short, ...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I've added the explaination in the comments. The types of a constant / literal is always defined in I added more conditions here to avoid the case that someone modified the logic of parsing literals (or adding new literals) in the future, and forget to modify this function. It's fine to cover all kinds of integer types because it'll also not cause type conversion. |
||
| mysql.TypeInt24, mysql.TypeBit, mysql.TypeYear: | ||
| // the target column must be an integer type or enum or set | ||
| if columnInfo.GetType() != mysql.TypeTiny && | ||
|
YangKeao marked this conversation as resolved.
|
||
| columnInfo.GetType() != mysql.TypeShort && | ||
| columnInfo.GetType() != mysql.TypeLong && | ||
| columnInfo.GetType() != mysql.TypeLonglong && | ||
| columnInfo.GetType() != mysql.TypeInt24 && | ||
| columnInfo.GetType() != mysql.TypeBit && | ||
| columnInfo.GetType() != mysql.TypeYear && | ||
| columnInfo.GetType() != mysql.TypeEnum && | ||
| columnInfo.GetType() != mysql.TypeSet { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("the type %s of the column `%s` in partial index condition is not compatible with the literal value type %s", | ||
| columnInfo.FieldType.String(), columnName.Name.Name.L, constantExpr.GetType().String())) | ||
| } | ||
| return nil | ||
| case mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal: | ||
| // the target column must be either a float or double type | ||
| // TODO: consider whether need to support decimal type in this branch | ||
| if columnInfo.GetType() != mysql.TypeFloat && | ||
| columnInfo.GetType() != mysql.TypeDouble && | ||
| columnInfo.GetType() != mysql.TypeNewDecimal { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("the type %s of the column `%s` in partial index condition is not compatible with the literal value type %s", | ||
| columnInfo.FieldType.String(), columnName.Name.Name.L, constantExpr.GetType().String())) | ||
| } | ||
| return nil | ||
| case mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeString, | ||
| mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeBlob: | ||
| if types.IsString(columnInfo.GetType()) { | ||
| // check the collation of the column and the literal value | ||
| if columnInfo.FieldType.GetCharset() != constantExpr.GetType().GetCharset() { | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("the charset %s of the column `%s` in partial index condition is not compatible with the literal value charset %s", | ||
| columnInfo.FieldType.GetCharset(), columnName.Name.Name.L, constantExpr.GetType().GetCharset())) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Allow to compare a datetime type column with a string literal, because we don't have a datetime literal. | ||
| // This branch will allow users to use datetime columns in index condition. | ||
| if columnInfo.GetType() == mysql.TypeTimestamp || | ||
| columnInfo.GetType() == mysql.TypeDate || | ||
| columnInfo.GetType() == mysql.TypeDuration || | ||
| columnInfo.GetType() == mysql.TypeNewDate || | ||
| columnInfo.GetType() == mysql.TypeDatetime { | ||
| return nil | ||
| } | ||
|
|
||
| // ENUM and SET are also allowed for string literal. | ||
| if columnInfo.GetType() == mysql.TypeEnum || columnInfo.GetType() == mysql.TypeSet { | ||
| return nil | ||
| } | ||
|
|
||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("the type %s of the column `%s` in partial index condition is not compatible with the literal value type %s", | ||
| columnInfo.FieldType.String(), columnName.Name.Name.L, constantExpr.GetType().String())) | ||
| case mysql.TypeNull: | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| "= NULL is not supported in partial index condition because it is always false") | ||
| case mysql.TypeTimestamp, mysql.TypeDate, mysql.TypeDuration, mysql.TypeNewDate, | ||
| mysql.TypeDatetime, mysql.TypeJSON, mysql.TypeEnum, mysql.TypeSet: | ||
| // The `DATE '2025-07-28'` is actually a `cast` function, so they are also not supported yet. | ||
| intest.Assert(false, "should never generate literal values of these types") | ||
|
|
||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("the type %s of the literal value in partial index condition is not supported", | ||
| constantExpr.GetType().String())) | ||
| default: | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| fmt.Sprintf("the type %s of the literal value in partial index condition is not supported", | ||
| constantExpr.GetType().String())) | ||
| } | ||
| default: | ||
| return dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs( | ||
| "the kind of partial index condition is not supported") | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to check the length of the condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any special limitation for the length of the condition?