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  }