vitess.io/vitess@v0.16.2/go/mysql/collations/remote/collation.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package remote 18 19 import ( 20 "encoding/hex" 21 "fmt" 22 "io" 23 "math" 24 "strings" 25 "sync" 26 27 "vitess.io/vitess/go/bytes2" 28 "vitess.io/vitess/go/mysql" 29 "vitess.io/vitess/go/mysql/collations" 30 "vitess.io/vitess/go/mysql/collations/internal/charset" 31 "vitess.io/vitess/go/sqltypes" 32 ) 33 34 // Collation is a generic implementation of the Collation interface 35 // that supports any collation in MySQL by performing the collation 36 // operation directly on a remote `mysqld` instance. It is not particularly 37 // efficient compared to the native Collation implementations in Vitess, 38 // but it offers authoritative results for all collation types and can be 39 // used as a fallback or as a way to test our native implementations. 40 type Collation struct { 41 name string 42 id collations.ID 43 44 charset string 45 prefix string 46 suffix string 47 48 mu sync.Mutex 49 conn *mysql.Conn 50 sql bytes2.Buffer 51 hex io.Writer 52 err error 53 } 54 55 var _ collations.Collation = (*Collation)(nil) 56 57 func makeRemoteCollation(conn *mysql.Conn, collid collations.ID, collname string) *Collation { 58 charset := collname 59 if idx := strings.IndexByte(collname, '_'); idx >= 0 { 60 charset = collname[:idx] 61 } 62 63 coll := &Collation{ 64 name: collname, 65 id: collid, 66 conn: conn, 67 charset: charset, 68 } 69 70 coll.prefix = fmt.Sprintf("_%s X'", charset) 71 coll.suffix = fmt.Sprintf("' COLLATE %q", collname) 72 coll.hex = hex.NewEncoder(&coll.sql) 73 return coll 74 } 75 76 func NewCollation(conn *mysql.Conn, collname string) *Collation { 77 return makeRemoteCollation(conn, collations.Unknown, collname) 78 } 79 80 func (c *Collation) LastError() error { 81 c.mu.Lock() 82 defer c.mu.Unlock() 83 return c.err 84 } 85 86 func (c *Collation) Init() {} 87 88 func (c *Collation) ID() collations.ID { 89 return c.id 90 } 91 92 func (c *Collation) IsBinary() bool { 93 return false 94 } 95 96 func (c *Collation) Name() string { 97 return c.name 98 } 99 100 func (c *Collation) Charset() charset.Charset { 101 return makeRemoteCharset(c.conn, &c.mu, c.charset) 102 } 103 104 func (c *Collation) Collate(left, right []byte, isPrefix bool) int { 105 if isPrefix { 106 panic("unsupported: isPrefix with remote.Collation") 107 } 108 109 c.mu.Lock() 110 defer c.mu.Unlock() 111 112 c.sql.Reset() 113 c.sql.WriteString("SELECT STRCMP(") 114 c.sql.WriteString(c.prefix) 115 c.hex.Write(left) 116 c.sql.WriteString(c.suffix) 117 c.sql.WriteString(", ") 118 c.sql.WriteString(c.prefix) 119 c.hex.Write(right) 120 c.sql.WriteString(c.suffix) 121 c.sql.WriteString(")") 122 123 var cmp int64 124 if result := c.performRemoteQuery(); result != nil { 125 cmp, c.err = result[0].ToInt64() 126 } 127 return int(cmp) 128 } 129 130 func (c *Collation) performRemoteQuery() []sqltypes.Value { 131 res, err := c.conn.ExecuteFetch(c.sql.StringUnsafe(), 1, false) 132 if err != nil { 133 c.err = err 134 return nil 135 } 136 if len(res.Rows) != 1 { 137 c.err = fmt.Errorf("unexpected result from MySQL: %d rows returned", len(res.Rows)) 138 return nil 139 } 140 c.err = nil 141 return res.Rows[0] 142 } 143 144 func (c *Collation) WeightString(dst, src []byte, numCodepoints int) []byte { 145 if numCodepoints == math.MaxInt32 { 146 panic("unsupported: PadToMax with remote.Collation") 147 } 148 149 c.mu.Lock() 150 defer c.mu.Unlock() 151 152 c.sql.Reset() 153 c.sql.WriteString("SELECT WEIGHT_STRING(") 154 c.sql.WriteString(c.prefix) 155 c.hex.Write(src) 156 c.sql.WriteString(c.suffix) 157 if numCodepoints > 0 { 158 fmt.Fprintf(&c.sql, " AS CHAR(%d)", numCodepoints) 159 } 160 c.sql.WriteString(")") 161 162 if result := c.performRemoteQuery(); result != nil { 163 resultBytes, _ := result[0].ToBytes() 164 if dst == nil { 165 dst = resultBytes 166 } else { 167 dst = append(dst, resultBytes...) 168 } 169 } 170 return dst 171 } 172 173 func (c *Collation) Hash(_ []byte, _ int) collations.HashCode { 174 panic("unsupported: Hash for remote collations") 175 } 176 177 type remotePattern struct { 178 remote *Collation 179 pattern string 180 escape rune 181 } 182 183 func (rp *remotePattern) Match(in []byte) bool { 184 c := rp.remote 185 186 c.mu.Lock() 187 defer c.mu.Unlock() 188 189 c.sql.Reset() 190 c.sql.WriteString("SELECT ") 191 c.sql.WriteString(c.prefix) 192 c.hex.Write(in) 193 c.sql.WriteString(c.suffix) 194 c.sql.WriteString(" LIKE ") 195 c.sql.WriteString(rp.pattern) 196 197 if rp.escape != 0 && rp.escape != '\\' { 198 fmt.Fprintf(&c.sql, " ESCAPE X'%x'", string(rp.escape)) 199 } 200 201 var match bool 202 if result := c.performRemoteQuery(); result != nil { 203 match, c.err = result[0].ToBool() 204 } 205 return match 206 } 207 208 func (c *Collation) Wildcard(pat []byte, _ rune, _ rune, escape rune) collations.WildcardPattern { 209 return &remotePattern{ 210 pattern: fmt.Sprintf("_%s X'%x'", c.charset, pat), 211 remote: c, 212 escape: escape, 213 } 214 } 215 216 func (c *Collation) WeightStringLen(_ int) int { 217 return 0 218 }