github.com/matrixorigin/matrixone@v1.2.0/pkg/frontend/connector.go (about) 1 // Copyright 2021 - 2023 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 "sort" 21 "strings" 22 "time" 23 24 plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" 25 26 "github.com/matrixorigin/matrixone/pkg/pb/plan" 27 "github.com/matrixorigin/matrixone/pkg/pb/timestamp" 28 "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" 29 30 "github.com/matrixorigin/matrixone/pkg/common/moerr" 31 "github.com/matrixorigin/matrixone/pkg/defines" 32 pb "github.com/matrixorigin/matrixone/pkg/pb/task" 33 "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" 34 moconnector "github.com/matrixorigin/matrixone/pkg/stream/connector" 35 "github.com/matrixorigin/matrixone/pkg/taskservice" 36 ) 37 38 const ( 39 defaultConnectorTaskMaxRetryTimes = 10 40 defaultConnectorTaskRetryInterval = int64(time.Second * 10) 41 ) 42 43 func handleCreateDynamicTable(ctx context.Context, ses *Session, st *tree.CreateTable) error { 44 ts := getGlobalPu().TaskService 45 if ts == nil { 46 return moerr.NewInternalError(ctx, "no task service is found") 47 } 48 dbName := string(st.Table.Schema()) 49 if dbName == "" { 50 dbName = ses.GetDatabaseName() 51 } 52 tableName := string(st.Table.Name()) 53 _, tableDef := ses.GetTxnCompileCtx().Resolve(dbName, tableName, plan2.Snapshot{TS: ×tamp.Timestamp{}}) 54 if tableDef == nil { 55 return moerr.NewNoSuchTable(ctx, dbName, tableName) 56 } 57 options := make(map[string]string) 58 for _, option := range st.DTOptions { 59 switch opt := option.(type) { 60 case *tree.CreateSourceWithOption: 61 key := string(opt.Key) 62 val := opt.Val.(*tree.NumVal).OrigString() 63 options[key] = val 64 } 65 } 66 67 generatedPlan, err := buildPlan(ctx, ses, ses.GetTxnCompileCtx(), st.AsSource) 68 if err != nil { 69 return err 70 } 71 query := generatedPlan.GetQuery() 72 if query != nil { // Checking if query is not nil 73 for _, node := range query.Nodes { 74 if node.NodeType == plan.Node_SOURCE_SCAN { 75 //collect the stream tableDefs 76 streamTableDef := node.TableDef.Defs 77 for _, def := range streamTableDef { 78 if propertiesDef, ok := def.Def.(*plan.TableDef_DefType_Properties); ok { 79 for _, property := range propertiesDef.Properties.Properties { 80 options[property.Key] = property.Value 81 } 82 } 83 } 84 } 85 } 86 } 87 88 options[moconnector.OptConnectorSql] = tree.String(st.AsSource, dialect.MYSQL) 89 if err := createConnector( 90 ctx, 91 ses.GetTenantInfo().GetTenantID(), 92 ses.GetTenantName(), 93 ses.GetUserName(), 94 ts, 95 dbName+"."+tableName, 96 options, 97 st.IfNotExists, 98 ); err != nil { 99 return err 100 } 101 return nil 102 } 103 104 func handleCreateConnector(ctx context.Context, ses *Session, st *tree.CreateConnector) error { 105 ts := getGlobalPu().TaskService 106 if ts == nil { 107 return moerr.NewInternalError(ctx, "no task service is found") 108 } 109 dbName := string(st.TableName.Schema()) 110 tableName := string(st.TableName.Name()) 111 _, tableDef := ses.GetTxnCompileCtx().Resolve(dbName, tableName, plan2.Snapshot{TS: ×tamp.Timestamp{}}) 112 if tableDef == nil { 113 return moerr.NewNoSuchTable(ctx, dbName, tableName) 114 } 115 options := make(map[string]string) 116 for _, opt := range st.Options { 117 options[string(opt.Key)] = opt.Val.String() 118 } 119 if err := createConnector( 120 ctx, 121 ses.GetTenantInfo().GetTenantID(), 122 ses.GetTenantName(), 123 ses.GetUserName(), 124 ts, 125 dbName+"."+tableName, 126 options, 127 false, 128 ); err != nil { 129 return err 130 } 131 return nil 132 } 133 134 func connectorTaskMetadata() pb.TaskMetadata { 135 return pb.TaskMetadata{ 136 ID: "-", 137 Executor: pb.TaskCode_ConnectorKafkaSink, 138 Options: pb.TaskOptions{ 139 MaxRetryTimes: defaultConnectorTaskMaxRetryTimes, 140 RetryInterval: defaultConnectorTaskRetryInterval, 141 DelayDuration: 0, 142 Concurrency: 0, 143 }, 144 } 145 } 146 147 func isSameValue(a, b map[string]string, field string) bool { 148 v1, ok := a[field] 149 if !ok { 150 return false 151 } 152 v2, ok := b[field] 153 if !ok { 154 return false 155 } 156 return strings.EqualFold(v1, v2) 157 } 158 159 func duplicate(t pb.DaemonTask, options map[string]string) bool { 160 if t.TaskStatus == pb.TaskStatus_Canceled { 161 return false 162 } 163 dup := true 164 switch d := t.Details.Details.(type) { 165 case *pb.Details_Connector: 166 checkFields := []string{ 167 moconnector.OptConnectorType, 168 moconnector.OptConnectorTopic, 169 moconnector.OptConnectorServers, 170 } 171 for _, field := range checkFields { 172 dup = dup && isSameValue(d.Connector.Options, options, field) 173 } 174 } 175 return dup 176 } 177 178 func createConnector( 179 ctx context.Context, 180 accountID uint32, 181 account string, 182 username string, 183 ts taskservice.TaskService, 184 tableName string, 185 rawOpts map[string]string, 186 ifNotExists bool, 187 ) error { 188 options, err := moconnector.MakeStmtOpts(ctx, rawOpts) 189 if err != nil { 190 return err 191 } 192 tasks, err := ts.QueryDaemonTask(ctx, 193 taskservice.WithTaskType(taskservice.EQ, 194 pb.TaskType_TypeKafkaSinkConnector.String()), 195 taskservice.WithAccountID(taskservice.EQ, 196 accountID), 197 ) 198 if err != nil { 199 return err 200 } 201 for _, t := range tasks { 202 dc, ok := t.Details.Details.(*pb.Details_Connector) 203 if !ok { 204 return moerr.NewInternalError(ctx, fmt.Sprintf("invalid task type %s", 205 t.TaskType.String())) 206 } 207 if dc.Connector.TableName == tableName && duplicate(t, options) { 208 // do not return error if ifNotExists is true since the table is not actually created 209 if ifNotExists { 210 return nil 211 } 212 return moerr.NewErrDuplicateConnector(ctx, tableName) 213 } 214 } 215 details := &pb.Details{ 216 AccountID: accountID, 217 Account: account, 218 Username: username, 219 Details: &pb.Details_Connector{ 220 Connector: &pb.ConnectorDetails{ 221 TableName: tableName, 222 Options: options, 223 }, 224 }, 225 } 226 if err := ts.CreateDaemonTask(ctx, connectorTaskMetadata(), details); err != nil { 227 return err 228 } 229 return nil 230 } 231 232 func handleDropConnector(ctx context.Context, ses *Session, st *tree.DropConnector) error { 233 //todo: handle Create connector 234 return nil 235 } 236 237 func handleDropDynamicTable(ctx context.Context, ses *Session, st *tree.DropTable) error { 238 if getGlobalPu() == nil || getGlobalPu().TaskService == nil { 239 return moerr.NewInternalError(ctx, "task service not ready yet") 240 } 241 ts := getGlobalPu().TaskService 242 243 // Query all relevant tasks belonging to the current tenant 244 tasks, err := ts.QueryDaemonTask(ctx, 245 taskservice.WithTaskType(taskservice.EQ, pb.TaskType_TypeKafkaSinkConnector.String()), 246 taskservice.WithAccountID(taskservice.EQ, ses.GetAccountId()), 247 taskservice.WithTaskStatusCond(pb.TaskStatus_Running, pb.TaskStatus_Created, pb.TaskStatus_Paused, pb.TaskStatus_PauseRequested), 248 ) 249 if err != nil || len(tasks) == 0 { 250 return err 251 } 252 253 // Filter the tasks within the loop 254 for _, tn := range st.Names { 255 dbName := string(tn.Schema()) 256 if dbName == "" { 257 dbName = ses.GetDatabaseName() 258 } 259 fullTableName := dbName + "." + string(tn.Name()) 260 261 for _, task := range tasks { 262 if task.Details.Details.(*pb.Details_Connector).Connector.TableName == fullTableName { 263 if err := handleCancelDaemonTask(ctx, ses, task.ID); err != nil { 264 return err 265 } 266 } 267 } 268 } 269 return nil 270 } 271 272 func handleShowConnectors(ctx context.Context, ses *Session) error { 273 var err error 274 if err := showConnectors(ctx, ses); err != nil { 275 return err 276 } 277 return err 278 } 279 280 var connectorCols = []Column{ 281 &MysqlColumn{ 282 ColumnImpl: ColumnImpl{ 283 name: "task_id", 284 columnType: defines.MYSQL_TYPE_LONG, 285 }, 286 }, 287 &MysqlColumn{ 288 ColumnImpl: ColumnImpl{ 289 name: "task_type", 290 columnType: defines.MYSQL_TYPE_VARCHAR, 291 }, 292 }, 293 &MysqlColumn{ 294 ColumnImpl: ColumnImpl{ 295 name: "task_runner", 296 columnType: defines.MYSQL_TYPE_VARCHAR, 297 }, 298 }, 299 &MysqlColumn{ 300 ColumnImpl: ColumnImpl{ 301 name: "task_status", 302 columnType: defines.MYSQL_TYPE_VARCHAR, 303 }, 304 }, 305 &MysqlColumn{ 306 ColumnImpl: ColumnImpl{ 307 name: "table_name", 308 columnType: defines.MYSQL_TYPE_VARCHAR, 309 }, 310 }, 311 &MysqlColumn{ 312 ColumnImpl: ColumnImpl{ 313 name: "options", 314 columnType: defines.MYSQL_TYPE_VARCHAR, 315 }, 316 }, 317 &MysqlColumn{ 318 ColumnImpl: ColumnImpl{ 319 name: "last_heartbeat", 320 columnType: defines.MYSQL_TYPE_VARCHAR, 321 }, 322 }, 323 &MysqlColumn{ 324 ColumnImpl: ColumnImpl{ 325 name: "created_at", 326 columnType: defines.MYSQL_TYPE_VARCHAR, 327 }, 328 }, 329 &MysqlColumn{ 330 ColumnImpl: ColumnImpl{ 331 name: "updated_at", 332 columnType: defines.MYSQL_TYPE_VARCHAR, 333 }, 334 }, 335 &MysqlColumn{ 336 ColumnImpl: ColumnImpl{ 337 name: "end_at", 338 columnType: defines.MYSQL_TYPE_VARCHAR, 339 }, 340 }, 341 &MysqlColumn{ 342 ColumnImpl: ColumnImpl{ 343 name: "last_run", 344 columnType: defines.MYSQL_TYPE_VARCHAR, 345 }, 346 }, 347 &MysqlColumn{ 348 ColumnImpl: ColumnImpl{ 349 name: "error", 350 columnType: defines.MYSQL_TYPE_VARCHAR, 351 }, 352 }, 353 } 354 355 func showConnectors(ctx context.Context, ses FeSession) error { 356 ts := getGlobalPu().TaskService 357 if ts == nil { 358 return moerr.NewInternalError(ctx, 359 "task service not ready yet, please try again later.") 360 } 361 tasks, err := ts.QueryDaemonTask(ctx, 362 taskservice.WithTaskType(taskservice.EQ, 363 pb.TaskType_TypeKafkaSinkConnector.String()), 364 taskservice.WithAccountID(taskservice.EQ, 365 ses.GetAccountId()), 366 ) 367 if err != nil { 368 return err 369 } 370 mrs := ses.GetMysqlResultSet() 371 for _, col := range connectorCols { 372 mrs.AddColumn(col) 373 } 374 for _, t := range tasks { 375 row := make([]interface{}, 12) 376 row[0] = t.ID 377 row[1] = t.TaskType.String() 378 row[2] = t.TaskRunner 379 row[3] = t.TaskStatus.String() 380 details := t.Details.Details.(*pb.Details_Connector) 381 row[4] = details.Connector.TableName 382 row[5] = optionString(details.Connector.Options) 383 if t.LastHeartbeat.IsZero() { 384 row[6] = "" 385 } else { 386 row[6] = t.LastHeartbeat.String() 387 } 388 row[7] = t.CreateAt.String() 389 row[8] = t.UpdateAt.String() 390 if t.EndAt.IsZero() { 391 row[9] = "" 392 } else { 393 row[9] = t.EndAt.String() 394 } 395 if t.LastRun.IsZero() { 396 row[10] = "" 397 } else { 398 row[10] = t.LastRun.String() 399 } 400 row[11] = t.Details.Error 401 mrs.AddRow(row) 402 } 403 return nil 404 } 405 406 func optionString(options map[string]string) string { 407 items := make([]string, 0, len(options)) 408 for key, value := range options { 409 items = append(items, fmt.Sprintf("%s=%s", key, value)) 410 } 411 sort.Strings(items) 412 return strings.Join(items, ",") 413 }