vitess.io/vitess@v0.16.2/go/vt/vttablet/vexec/vexec.go (about) 1 package vexec 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "vitess.io/vitess/go/vt/sqlparser" 9 ) 10 11 const ( 12 // TableQualifier is the standard schema used by VExec commands 13 TableQualifier = "_vt" 14 ) 15 16 var ( 17 // ErrColumNotFound is returned when we expect some column to exist and it does not 18 ErrColumNotFound = errors.New("Column not found") 19 ) 20 21 // ValColumns map column name to Literal, for col=Val expressions in a WHERE clause 22 type ValColumns map[string](*sqlparser.Literal) 23 24 // TabletVExec is a utility structure, created when a VExec command is intercepted on the tablet. 25 // This structure will parse and analyze the query, and make available some useful data. 26 // VExec interceptors receive an instance of this struct so they can run more analysis/checks 27 // on the given query, and potentially modify it. 28 type TabletVExec struct { 29 Workflow string 30 Keyspace string 31 Query string 32 Stmt sqlparser.Statement 33 TableName string 34 WhereCols ValColumns 35 UpdateCols ValColumns 36 InsertCols ValColumns 37 } 38 39 // NewTabletVExec creates a new instance of TabletVExec 40 func NewTabletVExec(workflow, keyspace string) *TabletVExec { 41 return &TabletVExec{ 42 Workflow: workflow, 43 Keyspace: keyspace, 44 } 45 } 46 47 // ToStringVal converts a string to a string -typed Literal 48 func (e *TabletVExec) ToStringVal(val string) *sqlparser.Literal { 49 return &sqlparser.Literal{ 50 Type: sqlparser.StrVal, 51 Val: val, 52 } 53 } 54 55 // ColumnStringVal returns a string value from a given column, or error if the column is not found 56 func (e *TabletVExec) ColumnStringVal(columns ValColumns, colName string) (string, error) { 57 val, ok := columns[colName] 58 if !ok { 59 return "", fmt.Errorf("Could not find value for column %s", colName) 60 } 61 return string(val.Val), nil 62 } 63 64 // SetColumnStringVal modifies a column value into a given string 65 func (e *TabletVExec) SetColumnStringVal(columns ValColumns, colName string, val string) { 66 columns[colName] = e.ToStringVal(val) 67 } 68 69 // analyzeWhereColumns identifies column names in a WHERE clause that have a comparison expression 70 // e.g. will return `keyspace` in a "WHERE keyspace='abc'" 71 // will not return `keyspace` in a "WHERE keyspace LIKE '%'" 72 func (e *TabletVExec) analyzeWhereEqualsColumns(where *sqlparser.Where) ValColumns { 73 cols := ValColumns{} 74 if where == nil { 75 return cols 76 } 77 exprs := sqlparser.SplitAndExpression(nil, where.Expr) 78 for _, expr := range exprs { 79 switch expr := expr.(type) { 80 case *sqlparser.ComparisonExpr: 81 if expr.Operator != sqlparser.EqualOp { 82 continue 83 } 84 qualifiedName, ok := expr.Left.(*sqlparser.ColName) 85 if !ok { 86 continue 87 } 88 if val, ok := expr.Right.(*sqlparser.Literal); ok { 89 cols[qualifiedName.Name.String()] = val 90 } 91 } 92 } 93 return cols 94 } 95 96 // analyzeUpdateColumns analyses the columns modified by an UPDATE statement. 97 // it returns the columns that are updated with a literal 98 // e.g. in this statement: UPDATE tbl SET name='foo', val=3, status=other_column+2 99 // the function returns name: 'foo' and val: 3, but does not return `status` column 100 func (e *TabletVExec) analyzeUpdateColumns(update *sqlparser.Update) ValColumns { 101 cols := ValColumns{} 102 for _, col := range update.Exprs { 103 if val, ok := col.Expr.(*sqlparser.Literal); ok { 104 cols[col.Name.Name.Lowered()] = val 105 } 106 } 107 return cols 108 } 109 110 // analyzeInsertColumns analyses the columns and values given in an INSERT statement 111 func (e *TabletVExec) analyzeInsertColumns(insert *sqlparser.Insert) ValColumns { 112 cols := ValColumns{} 113 114 rows, ok := insert.Rows.(sqlparser.Values) 115 if !ok { 116 return cols 117 } 118 119 if len(rows) != 1 { 120 return cols 121 } 122 for i, col := range insert.Columns { 123 expr := rows[0][i] 124 if val, ok := expr.(*sqlparser.Literal); ok { 125 cols[col.Lowered()] = val 126 } 127 } 128 return cols 129 } 130 131 // ReplaceInsertColumnVal manipulates the existing INSERT statement to replace a column value 132 // into a given value 133 func (e *TabletVExec) ReplaceInsertColumnVal(colName string, val *sqlparser.Literal) error { 134 insert, ok := e.Stmt.(*sqlparser.Insert) 135 if !ok { 136 return fmt.Errorf("Not an INSERT statement") 137 } 138 rows, ok := insert.Rows.(sqlparser.Values) 139 if !ok { 140 return fmt.Errorf("Not a Values type INSERT") 141 } 142 if len(rows) != 1 { 143 return fmt.Errorf("Not a single row INSERT") 144 } 145 for i, col := range insert.Columns { 146 if col.Lowered() == colName { 147 rows[0][i] = val 148 e.InsertCols[colName] = val 149 e.Query = sqlparser.String(e.Stmt) 150 return nil 151 } 152 } 153 return ErrColumNotFound 154 } 155 156 // AddOrReplaceInsertColumnVal manipulates the existing INSERT statement to replace a column value 157 // into a given value 158 func (e *TabletVExec) AddOrReplaceInsertColumnVal(colName string, val *sqlparser.Literal) error { 159 if err := e.ReplaceInsertColumnVal(colName, val); err != ErrColumNotFound { 160 return err 161 } 162 // We know the query is a valid single row INSERT 163 // We know column is not found. We need to add it. 164 165 insert, _ := e.Stmt.(*sqlparser.Insert) 166 rows, _ := insert.Rows.(sqlparser.Values) 167 rows[0] = append(rows[0], val) 168 insert.Columns = append(insert.Columns, sqlparser.NewIdentifierCI(colName)) 169 e.InsertCols[colName] = val 170 e.Query = sqlparser.String(e.Stmt) 171 172 return nil 173 } 174 175 // analyzeStatement analyzes a given statement and produces the following ingredients, useful for 176 // VExec interceptors: 177 // - table name 178 // - column names with values, for col=VAL in a WHERE expression 179 // e.g. in "UPDATE my_table SET ... WHERE keyspace='test' AND shard='-80' AND status > 2", the 180 // ValColumns are "keyspace" and "shard" with matching values. `status` is a range operator therefore 181 // not included.package vexec 182 // Equals operator is of special importance because it is known to filter results. An interceptor may 183 // require, for example, that a `DELETE` statement includes a WHERE with a UNIQUE KEY column with Equals operator 184 // to ensure we're not doing anything too risky. 185 func (e *TabletVExec) analyzeStatement() error { 186 switch stmt := e.Stmt.(type) { 187 case *sqlparser.Update: 188 e.TableName = sqlparser.String(stmt.TableExprs) 189 e.WhereCols = e.analyzeWhereEqualsColumns(stmt.Where) 190 e.UpdateCols = e.analyzeUpdateColumns(stmt) 191 case *sqlparser.Delete: 192 e.TableName = sqlparser.String(stmt.TableExprs) 193 e.WhereCols = e.analyzeWhereEqualsColumns(stmt.Where) 194 case *sqlparser.Insert: 195 e.TableName = sqlparser.String(stmt.Table) 196 e.InsertCols = e.analyzeInsertColumns(stmt) 197 case *sqlparser.Select: 198 e.TableName = sqlparser.ToString(stmt.From) 199 e.WhereCols = e.analyzeWhereEqualsColumns(stmt.Where) 200 default: 201 return fmt.Errorf("query not supported by vexec: %+v", sqlparser.String(stmt)) 202 } 203 return nil 204 } 205 206 // AnalyzeQuery analyzes a given statement and produces the following ingredients, useful for 207 // VExec interceptors: 208 // - parsed statement 209 // - table name 210 // - column names with values, for col=VAL in a WHERE expression 211 // e.g. in "UPDATE my_table SET ... WHERE keyspace='test' AND shard='-80' AND status > 2", the 212 // ValColumns are "keyspace" and "shard" with matching values. `status` is a range operator therefore 213 // not included.package vexec 214 // Equals operator is of special importance because it is known to filter results. An interceptor may 215 // require, for example, that a `DELETE` statement includes a WHERE with a UNIQUE KEY column with Equals operator 216 // to ensure we're not doing anything too risky. 217 func (e *TabletVExec) AnalyzeQuery(ctx context.Context, query string) (err error) { 218 if e.Stmt, err = sqlparser.Parse(query); err != nil { 219 return err 220 } 221 e.Query = query 222 if err := e.analyzeStatement(); err != nil { 223 return err 224 } 225 return nil 226 }