github.com/matrixorigin/matrixone@v1.2.0/pkg/frontend/show_subsriptions.go (about) 1 // Copyright 2021 Matrix Origin 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package frontend 16 17 import ( 18 "context" 19 "fmt" 20 "go/constant" 21 "sort" 22 "strings" 23 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 "github.com/matrixorigin/matrixone/pkg/defines" 26 "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect/mysql" 27 "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" 28 ) 29 30 const ( 31 getAccountIdNamesSql = "select account_id, account_name from mo_catalog.mo_account where status != 'suspend'" 32 getPubsSql = "select pub_name, database_name, account_list, created_time from mo_catalog.mo_pubs" 33 getSubsFormat = "select datname, dat_createsql, created_time from mo_catalog.mo_database where dat_type = 'subscription' and account_id = %d" 34 ) 35 36 var ( 37 showSubscriptionOutputColumns = [6]Column{ 38 &MysqlColumn{ 39 ColumnImpl: ColumnImpl{ 40 name: "pub_name", 41 columnType: defines.MYSQL_TYPE_VARCHAR, 42 }, 43 }, 44 &MysqlColumn{ 45 ColumnImpl: ColumnImpl{ 46 name: "pub_account", 47 columnType: defines.MYSQL_TYPE_VARCHAR, 48 }, 49 }, 50 &MysqlColumn{ 51 ColumnImpl: ColumnImpl{ 52 name: "pub_database", 53 columnType: defines.MYSQL_TYPE_VARCHAR, 54 }, 55 }, 56 &MysqlColumn{ 57 ColumnImpl: ColumnImpl{ 58 name: "pub_time", 59 columnType: defines.MYSQL_TYPE_TIMESTAMP, 60 }, 61 }, 62 &MysqlColumn{ 63 ColumnImpl: ColumnImpl{ 64 name: "sub_name", 65 columnType: defines.MYSQL_TYPE_VARCHAR, 66 }, 67 }, 68 &MysqlColumn{ 69 ColumnImpl: ColumnImpl{ 70 name: "sub_time", 71 columnType: defines.MYSQL_TYPE_TIMESTAMP, 72 }, 73 }, 74 } 75 ) 76 77 type published struct { 78 pubName string 79 pubAccount string 80 pubDatabase string 81 pubTime string 82 subName string 83 subTime string 84 } 85 86 type subscribed struct { 87 pubName string 88 pubAccount string 89 subName string 90 subTime string 91 } 92 93 func getAccountIdByName(ctx context.Context, ses *Session, bh BackgroundExec, name string) int32 { 94 if accountIds, _, err := getAccountIdNames(ctx, ses, bh, name); err == nil && len(accountIds) > 0 { 95 return accountIds[0] 96 } 97 return -1 98 } 99 100 func getAccountIdNames(ctx context.Context, ses *Session, bh BackgroundExec, likeName string) ([]int32, []string, error) { 101 bh.ClearExecResultBatches() 102 ctx = context.WithValue(ctx, defines.TenantIDKey{}, uint32(sysAccountID)) 103 sql := getAccountIdNamesSql 104 if len(likeName) > 0 { 105 sql += fmt.Sprintf(" and account_name like '%s'", likeName) 106 } 107 if err := bh.Exec(ctx, sql); err != nil { 108 return nil, nil, err 109 } 110 111 var accountIds []int32 112 var accountNames []string 113 for _, batch := range bh.GetExecResultBatches() { 114 mrs := &MysqlResultSet{ 115 Columns: make([]Column, len(batch.Vecs)), 116 } 117 oq := newFakeOutputQueue(mrs) 118 for i := 0; i < batch.RowCount(); i++ { 119 row, err := extractRowFromEveryVector(ctx, ses, batch, i, oq, true) 120 if err != nil { 121 return nil, nil, err 122 } 123 124 // column[0]: account_id 125 accountIds = append(accountIds, row[0].(int32)) 126 // column[1]: account_name 127 accountNames = append(accountNames, string(row[1].([]byte)[:])) 128 } 129 } 130 return accountIds, accountNames, nil 131 } 132 133 func canSub(subAccount, subAccountListStr string) bool { 134 if strings.ToLower(subAccountListStr) == "all" { 135 return true 136 } 137 138 for _, acc := range strings.Split(subAccountListStr, ",") { 139 if acc == subAccount { 140 return true 141 } 142 } 143 return false 144 } 145 146 func getPubs(ctx context.Context, ses *Session, bh BackgroundExec, accountId int32, accountName string, like string, subAccountName string) ([]*published, error) { 147 bh.ClearExecResultBatches() 148 sql := getPubsSql 149 if len(like) > 0 { 150 sql += fmt.Sprintf(" where pub_name like '%s';", like) 151 } 152 ctx = context.WithValue(ctx, defines.TenantIDKey{}, uint32(accountId)) 153 if err := bh.Exec(ctx, sql); err != nil { 154 return nil, err 155 } 156 157 var pubs []*published 158 for _, batch := range bh.GetExecResultBatches() { 159 mrs := &MysqlResultSet{ 160 Columns: make([]Column, len(batch.Vecs)), 161 } 162 oq := newFakeOutputQueue(mrs) 163 for i := 0; i < batch.RowCount(); i++ { 164 row, err := extractRowFromEveryVector(ctx, ses, batch, i, oq, true) 165 if err != nil { 166 return nil, err 167 } 168 169 pubName := string(row[0].([]byte)[:]) 170 pubDatabase := string(row[1].([]byte)[:]) 171 subAccountListStr := string(row[2].([]byte)[:]) 172 pubTime := row[3].(string) 173 if !canSub(subAccountName, subAccountListStr) { 174 continue 175 } 176 177 pub := &published{ 178 pubName: pubName, 179 pubAccount: accountName, 180 pubDatabase: pubDatabase, 181 pubTime: pubTime, 182 } 183 pubs = append(pubs, pub) 184 } 185 } 186 return pubs, nil 187 } 188 189 func getSubInfoFromSql(ctx context.Context, ses FeSession, sql string) (subName, pubAccountName, pubName string, err error) { 190 var lowerAny interface{} 191 if lowerAny, err = ses.GetGlobalVar(ctx, "lower_case_table_names"); err != nil { 192 return 193 } 194 195 var ast []tree.Statement 196 if ast, err = mysql.Parse(ctx, sql, lowerAny.(int64), 0); err != nil { 197 return 198 } 199 defer func() { 200 for _, s := range ast { 201 s.Free() 202 } 203 }() 204 205 subName = string(ast[0].(*tree.CreateDatabase).Name) 206 pubAccountName = string(ast[0].(*tree.CreateDatabase).SubscriptionOption.From) 207 pubName = string(ast[0].(*tree.CreateDatabase).SubscriptionOption.Publication) 208 return 209 } 210 211 func getSubs(ctx context.Context, ses *Session, bh BackgroundExec, accountId uint32) ([]*subscribed, error) { 212 bh.ClearExecResultBatches() 213 ctx = context.WithValue(ctx, defines.TenantIDKey{}, accountId) 214 sql := fmt.Sprintf(getSubsFormat, accountId) 215 if err := bh.Exec(ctx, sql); err != nil { 216 return nil, err 217 } 218 219 var subs []*subscribed 220 for _, batch := range bh.GetExecResultBatches() { 221 mrs := &MysqlResultSet{ 222 Columns: make([]Column, len(batch.Vecs)), 223 } 224 oq := newFakeOutputQueue(mrs) 225 for i := 0; i < batch.RowCount(); i++ { 226 row, err := extractRowFromEveryVector(ctx, ses, batch, i, oq, true) 227 if err != nil { 228 return nil, err 229 } 230 231 subName := string(row[0].([]byte)[:]) 232 createSql := string(row[1].([]byte)[:]) 233 subTime := row[2].(string) 234 _, pubAccountName, pubName, err := getSubInfoFromSql(ctx, ses, createSql) 235 if err != nil { 236 return nil, err 237 } 238 239 sub := &subscribed{ 240 pubName: pubName, 241 pubAccount: pubAccountName, 242 subName: subName, 243 subTime: subTime, 244 } 245 subs = append(subs, sub) 246 } 247 } 248 return subs, nil 249 } 250 251 func doShowSubscriptions(ctx context.Context, ses *Session, ss *tree.ShowSubscriptions) (err error) { 252 bh := GetRawBatchBackgroundExec(ctx, ses) 253 defer bh.Close() 254 255 if err = bh.Exec(ctx, "begin;"); err != nil { 256 return err 257 } 258 defer func() { 259 err = finishTxn(ctx, bh, err) 260 }() 261 262 var like string 263 if ss.Like != nil { 264 right, ok := ss.Like.Right.(*tree.NumVal) 265 if !ok || right.Value.Kind() != constant.String { 266 return moerr.NewInternalError(ctx, "like clause must be a string") 267 } 268 like = constant.StringVal(right.Value) 269 } 270 271 // step 1. get all account 272 var accountIds []int32 273 var accountNames []string 274 if accountIds, accountNames, err = getAccountIdNames(ctx, ses, bh, ""); err != nil { 275 return err 276 } 277 278 // step 2. traversal all accounts, get all published pubs 279 var allPublished []*published 280 for i := 0; i < len(accountIds); i++ { 281 var pubs []*published 282 if pubs, err = getPubs(ctx, ses, bh, accountIds[i], accountNames[i], like, ses.GetTenantName()); err != nil { 283 return err 284 } 285 286 allPublished = append(allPublished, pubs...) 287 } 288 289 // step 3. get current account's subscriptions 290 allSubscribedMap := make(map[string]map[string]*subscribed) 291 subs, err := getSubs(ctx, ses, bh, ses.GetTenantInfo().GetTenantID()) 292 if err != nil { 293 return err 294 } 295 for _, sub := range subs { 296 if _, ok := allSubscribedMap[sub.pubAccount]; !ok { 297 allSubscribedMap[sub.pubAccount] = make(map[string]*subscribed) 298 } 299 allSubscribedMap[sub.pubAccount][sub.pubName] = sub 300 } 301 302 // step 4. join pubs && subs, and sort 303 for _, pub := range allPublished { 304 if sub := allSubscribedMap[pub.pubAccount][pub.pubName]; sub != nil { 305 pub.subName = sub.subName 306 pub.subTime = sub.subTime 307 } 308 } 309 310 if len(like) > 0 { 311 // sort by pub_name asc 312 sort.SliceStable(allPublished, func(i, j int) bool { 313 return allPublished[i].pubName < allPublished[j].pubName 314 }) 315 } else { 316 // sort by sub_time, pub_time desc 317 sort.SliceStable(allPublished, func(i, j int) bool { 318 if allPublished[i].subTime != allPublished[j].subTime { 319 return allPublished[i].subTime > allPublished[j].subTime 320 } 321 return allPublished[i].pubTime > allPublished[j].pubTime 322 }) 323 } 324 325 // step 5. generate result set 326 bh.ClearExecResultBatches() 327 var rs = &MysqlResultSet{} 328 for _, column := range showSubscriptionOutputColumns { 329 rs.AddColumn(column) 330 } 331 for _, pub := range allPublished { 332 if !ss.All && len(pub.subName) == 0 { 333 continue 334 } 335 336 var subName, subTime interface{} 337 subName, subTime = pub.subName, pub.subTime 338 if len(pub.subName) == 0 { 339 subName, subTime = nil, nil 340 } 341 rs.AddRow([]interface{}{pub.pubName, pub.pubAccount, pub.pubDatabase, pub.pubTime, subName, subTime}) 342 } 343 ses.SetMysqlResultSet(rs) 344 return nil 345 }