github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/binlog-filter/filter.go (about) 1 // Copyright 2018 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package filter 15 16 import ( 17 "regexp" 18 "strings" 19 20 "github.com/pingcap/errors" 21 "github.com/pingcap/log" 22 selector "github.com/pingcap/tidb/pkg/util/table-rule-selector" 23 "go.uber.org/zap" 24 ) 25 26 // ActionType indicates how to handle matched items 27 type ActionType string 28 29 // show that how to handle rules 30 const ( 31 Ignore ActionType = "Ignore" 32 Do ActionType = "Do" 33 Error ActionType = "Error" 34 ) 35 36 // EventType is DML/DDL Event type 37 type EventType string 38 39 // show DML/DDL Events 40 const ( 41 ddl EventType = "ddl" 42 dml EventType = "dml" 43 incompatibleDDL EventType = "incompatible DDL" 44 45 // it indicates all dml/ddl events in rule 46 AllEvent EventType = "all" 47 AllDDL EventType = "all ddl" 48 AllDML EventType = "all dml" 49 50 // it indicates no any dml/ddl events in rule, 51 // and equals empty rule.DDLEvent/DMLEvent 52 NoneEvent EventType = "none" 53 NoneDDL EventType = "none ddl" 54 NoneDML EventType = "none dml" 55 56 InsertEvent EventType = "insert" 57 UpdateEvent EventType = "update" 58 DeleteEvent EventType = "delete" 59 60 CreateDatabase EventType = "create database" 61 DropDatabase EventType = "drop database" 62 AlterDatabase EventType = "alter database" 63 CreateTable EventType = "create table" 64 DropTable EventType = "drop table" 65 TruncateTable EventType = "truncate table" 66 RenameTable EventType = "rename table" 67 CreateIndex EventType = "create index" 68 DropIndex EventType = "drop index" 69 CreateView EventType = "create view" 70 DropView EventType = "drop view" 71 AlterTable EventType = "alter table" 72 73 CreateSchema EventType = "create schema" // alias of CreateDatabase 74 DropSchema EventType = "drop schema" // alias of DropDatabase 75 AlterSchema EventType = "alter schema" // alias of AlterDatabase 76 77 AddTablePartition EventType = "add table partition" 78 DropTablePartition EventType = "drop table partition" 79 TruncateTablePartition EventType = "truncate table partition" 80 // if need, add more AlertTableOption = "alert table option" 81 82 IncompatibleDDLChanges EventType = "incompatible ddl changes" 83 ValueRangeDecrease EventType = "value range decrease" 84 PrecisionDecrease EventType = "precision decrease" 85 ModifyColumn EventType = "modify column" 86 RenameColumn EventType = "rename column" 87 RenameIndex EventType = "rename index" 88 DropColumn EventType = "drop column" 89 DropPrimaryKey EventType = "drop primary key" 90 DropUniqueKey EventType = "drop unique key" 91 ModifyDefaultValue EventType = "modify default value" 92 ModifyConstraint EventType = "modify constraint" 93 ModifyColumnsOrder EventType = "modify columns order" 94 ModifyCharset EventType = "modify charset" 95 ModifyCollation EventType = "modify collation" 96 RemoveAutoIncrement EventType = "remove auto increment" 97 ModifyStorageEngine EventType = "modify storage engine" 98 ReorganizePartition EventType = "reorganize table partition" 99 RebuildPartition EventType = "rebuild table partition" 100 CoalescePartition EventType = "coalesce table partition" 101 SplitPartition EventType = "split table partition" 102 ExchangePartition EventType = "exchange table partition" 103 104 ModifySchemaCharsetAndCollate EventType = "modify schema charset and collate" 105 ModifyTableCharsetAndCollate EventType = "modify table charset and collate" 106 ModifyTableComment EventType = "modify table comment" 107 RecoverTable EventType = "recover table" 108 AlterTablePartitioning EventType = "alter table partitioning" 109 RemovePartitioning EventType = "remove table partitioning" 110 AddColumn EventType = "add column" 111 SetDefaultValue EventType = "set default value" 112 RebaseAutoID EventType = "rebase auto id" 113 AddPrimaryKey EventType = "add primary key" 114 AlterIndexVisibility EventType = "alter index visibility" 115 AlterTTLInfo EventType = "alter ttl info" 116 AlterTTLRemove EventType = "alter ttl remove" 117 MultiSchemaChange EventType = "multi schema change" 118 119 // NullEvent is used to represents unsupported ddl event type when we 120 // convert a ast.StmtNode or a string to EventType. 121 NullEvent EventType = "" 122 ) 123 124 // ClassifyEvent classify event into dml/ddl 125 func ClassifyEvent(event EventType) (EventType, error) { 126 switch event { 127 case InsertEvent, 128 UpdateEvent, 129 DeleteEvent: 130 return dml, nil 131 case CreateDatabase, 132 AlterDatabase, 133 AlterSchema, 134 CreateTable, 135 CreateIndex, 136 CreateView, 137 DropView, 138 AlterTable, 139 CreateSchema, 140 AddTablePartition: 141 return ddl, nil 142 case NullEvent: 143 return NullEvent, nil 144 case ValueRangeDecrease, 145 PrecisionDecrease, 146 ModifyColumn, 147 RenameColumn, 148 RenameIndex, 149 DropColumn, 150 DropPrimaryKey, 151 DropUniqueKey, 152 ModifyDefaultValue, 153 ModifyConstraint, 154 ModifyColumnsOrder, 155 ModifyCharset, 156 ModifyCollation, 157 RemoveAutoIncrement, 158 ModifyStorageEngine, 159 ReorganizePartition, 160 RebuildPartition, 161 CoalescePartition, 162 SplitPartition, 163 ExchangePartition, 164 165 DropDatabase, 166 DropTable, 167 DropIndex, 168 RenameTable, 169 TruncateTable, 170 DropSchema, 171 DropTablePartition, 172 TruncateTablePartition, 173 174 ModifySchemaCharsetAndCollate, 175 ModifyTableCharsetAndCollate, 176 ModifyTableComment, 177 RecoverTable, 178 AlterTablePartitioning, 179 RemovePartitioning, 180 AddColumn, 181 SetDefaultValue, 182 RebaseAutoID, 183 AddPrimaryKey, 184 AlterIndexVisibility, 185 AlterTTLInfo, 186 AlterTTLRemove, 187 MultiSchemaChange: 188 return incompatibleDDL, nil 189 default: 190 return NullEvent, errors.NotValidf("event type %s", event) 191 } 192 } 193 194 // BinlogEventRule is a rule to filter binlog events 195 type BinlogEventRule struct { 196 SchemaPattern string `json:"schema-pattern" toml:"schema-pattern" yaml:"schema-pattern"` 197 TablePattern string `json:"table-pattern" toml:"table-pattern" yaml:"table-pattern"` 198 Events []EventType `json:"events" toml:"events" yaml:"events"` 199 SQLPattern []string `json:"sql-pattern" toml:"sql-pattern" yaml:"sql-pattern"` // regular expression 200 sqlRegularExp *regexp.Regexp 201 202 Action ActionType `json:"action" toml:"action" yaml:"action"` 203 } 204 205 // ToLower covert schema/table pattern to lower case 206 func (b *BinlogEventRule) ToLower() { 207 b.SchemaPattern = strings.ToLower(b.SchemaPattern) 208 b.TablePattern = strings.ToLower(b.TablePattern) 209 } 210 211 // Valid checks validity of rule. 212 func (b *BinlogEventRule) Valid() error { 213 if len(b.SQLPattern) > 0 { 214 reg, err := regexp.Compile("(?i)" + strings.Join(b.SQLPattern, "|")) 215 if err != nil { 216 return errors.Annotatef(err, "compile regular expression %+v", b.SQLPattern) 217 } 218 b.sqlRegularExp = reg 219 } 220 221 if b.Action != Do && b.Action != Ignore && b.Action != Error { 222 return errors.Errorf("action of binlog event rule %+v should not be empty", b) 223 } 224 225 for i := range b.Events { 226 et, err := toEventType(string(b.Events[i])) 227 if err != nil { 228 return errors.NotValidf("event type %s", b.Events[i]) 229 } 230 b.Events[i] = et 231 } 232 return nil 233 } 234 235 // BinlogEvent filters binlog events by given rules 236 type BinlogEvent struct { 237 selector.Selector 238 239 caseSensitive bool 240 } 241 242 // NewBinlogEvent returns a binlog event filter 243 func NewBinlogEvent(caseSensitive bool, rules []*BinlogEventRule) (*BinlogEvent, error) { 244 b := &BinlogEvent{ 245 Selector: selector.NewTrieSelector(), 246 caseSensitive: caseSensitive, 247 } 248 249 for _, rule := range rules { 250 if err := b.AddRule(rule); err != nil { 251 log.Error("invalid binlog event rule", zap.Any("rule", rule), zap.Error(err)) 252 } 253 } 254 255 return b, nil 256 } 257 258 // AddRule adds a rule into binlog event filter 259 func (b *BinlogEvent) AddRule(rule *BinlogEventRule) error { 260 if b == nil || rule == nil { 261 return nil 262 } 263 err := rule.Valid() 264 if err != nil { 265 return errors.Trace(err) 266 } 267 if !b.caseSensitive { 268 rule.ToLower() 269 } 270 271 err = b.Insert(rule.SchemaPattern, rule.TablePattern, rule, selector.Insert) 272 if err != nil { 273 return errors.Annotatef(err, "add rule %+v into binlog event filter", rule) 274 } 275 276 return nil 277 } 278 279 // UpdateRule updates binlog event filter rule 280 func (b *BinlogEvent) UpdateRule(rule *BinlogEventRule) error { 281 if b == nil || rule == nil { 282 return nil 283 } 284 err := rule.Valid() 285 if err != nil { 286 return errors.Trace(err) 287 } 288 if !b.caseSensitive { 289 rule.ToLower() 290 } 291 292 err = b.Insert(rule.SchemaPattern, rule.TablePattern, rule, selector.Replace) 293 if err != nil { 294 return errors.Annotatef(err, "update rule %+v into binlog event filter", rule) 295 } 296 297 return nil 298 } 299 300 // RemoveRule removes a rule from binlog event filter 301 func (b *BinlogEvent) RemoveRule(rule *BinlogEventRule) error { 302 if b == nil || rule == nil { 303 return nil 304 } 305 if !b.caseSensitive { 306 rule.ToLower() 307 } 308 309 err := b.Remove(rule.SchemaPattern, rule.TablePattern) 310 if err != nil { 311 return errors.Annotatef(err, "remove rule %+v", rule) 312 } 313 314 return nil 315 } 316 317 // Filter filters events or queries by given rules 318 // returns action and error 319 func (b *BinlogEvent) Filter(schema, table string, event EventType, rawQuery string) (ActionType, error) { 320 if b == nil { 321 return Do, nil 322 } 323 324 tp, err := ClassifyEvent(event) 325 if err != nil { 326 return Ignore, errors.Trace(err) 327 } 328 329 schemaL, tableL := schema, table 330 if !b.caseSensitive { 331 schemaL, tableL = strings.ToLower(schema), strings.ToLower(table) 332 } 333 334 rules := b.Match(schemaL, tableL) 335 if len(rules) == 0 { 336 return Do, nil 337 } 338 339 for _, rule := range rules { 340 binlogEventRule, ok := rule.(*BinlogEventRule) 341 if !ok { 342 return "", errors.NotValidf("rule %+v", rule) 343 } 344 345 if tp != NullEvent { 346 matched := b.matchEvent(tp, event, binlogEventRule.Events) 347 348 if matched { 349 // ignore has highest priority 350 if binlogEventRule.Action == Ignore { 351 return Ignore, nil 352 } 353 if binlogEventRule.Action == Error { 354 return Error, nil 355 } 356 } else { 357 if binlogEventRule.Action == Do { 358 return Ignore, nil 359 } 360 } 361 } 362 363 if len(rawQuery) > 0 { 364 if len(binlogEventRule.SQLPattern) == 0 { 365 // sql pattern is disabled , just continue 366 continue 367 } 368 369 matched := binlogEventRule.sqlRegularExp.FindStringIndex(rawQuery) != nil 370 if matched { 371 // Ignore has highest priority 372 if binlogEventRule.Action == Ignore { 373 return Ignore, nil 374 } 375 if binlogEventRule.Action == Error { 376 return Error, nil 377 } 378 } else { 379 if binlogEventRule.Action == Do { 380 return Ignore, nil 381 } 382 } 383 } 384 } 385 386 return Do, nil 387 } 388 389 func (b *BinlogEvent) matchEvent(tp, event EventType, rules []EventType) bool { 390 for _, rule := range rules { 391 if rule == AllEvent { 392 return true 393 } 394 395 if rule == NoneEvent { 396 return false 397 } 398 399 if tp == ddl || tp == incompatibleDDL { 400 if rule == AllDDL { 401 return true 402 } 403 404 if rule == NoneDDL { 405 return false 406 } 407 } 408 409 if tp == dml { 410 if rule == AllDML { 411 return true 412 } 413 414 if rule == NoneDML { 415 return false 416 } 417 } 418 419 if tp == incompatibleDDL { 420 if rule == IncompatibleDDLChanges { 421 return true 422 } 423 } 424 425 if rule == event { 426 return true 427 } 428 } 429 430 return false 431 }