func (sei *session) handleSimpleSelectFunc(stmt *sqlparser.Select, f *sqlparser.FuncExpr) error { expr := stmt.SelectExprs[0].(*sqlparser.NonStarExpr) switch f.Name { case "database": if f.Exprs != nil { return sei.writeError(mysql.NewError(mysql.ER_UNKNOWN_ERROR, "not support stmt")) } // get now database var name []string if expr.As == "" { name = []string{"DATABASE()"} } else { name = []string{string(expr.As)} } var values [][]interface{} if sei.db == "" { values = [][]interface{}{ []interface{}{"NULL"}, } } else { values = [][]interface{}{ []interface{}{sei.db}, } } r, err := sei.buildResultset(nil, name, values) if err != nil { return err } return sei.writeResultset(sei.status, r) } return sei.writeError(mysql.NewError(mysql.ER_UNKNOWN_ERROR, "not support stmt")) }
func (sei *session) dispatch(data []byte) error { cmd := data[0] glog.Infof("cmd type: %v", cmd) data = data[1:] switch cmd { case mysql.COM_QUIT: return sei.Close() case mysql.COM_QUERY: return sei.handleQuery(data) case mysql.COM_PING: return sei.writeOK(nil) case mysql.COM_INIT_DB: return sei.useDB(string(data)) case mysql.COM_FIELD_LIST: return sei.handleFieldList(data) case mysql.COM_STMT_PREPARE: case mysql.COM_STMT_EXECUTE: case mysql.COM_STMT_CLOSE: case mysql.COM_STMT_SEND_LONG_DATA: case mysql.COM_STMT_RESET: case mysql.COM_SET_OPTION: default: msg := fmt.Sprintf("command %d not supported", cmd) glog.Errorf("session(%v) dispatch %s", sei.id, msg) return mysql.NewError(mysql.ER_UNKNOWN_ERROR, msg) } return nil }
func (sei *session) handleSimpleSelect(stmt *sqlparser.Select) error { switch expr := stmt.SelectExprs[0].(type) { case *sqlparser.NonStarExpr: switch f := expr.Expr.(type) { case *sqlparser.FuncExpr: return sei.handleSimpleSelectFunc(stmt, f) } case *sqlparser.StarExpr: } return sei.writeError(mysql.NewError(mysql.ER_UNKNOWN_ERROR, "not support stmt")) }
func (sei *session) handleSelect(stmt *sqlparser.Select) error { if stmt.From == nil { return sei.handleSimpleSelect(stmt) } if len(stmt.From) > 1 { return sei.writeError(mysql.NewDefaultError(mysql.ER_SYNTAX_ERROR)) } plan, err := sei.buildPlan(stmt) if err != nil { return sei.writeError(mysql.NewError(mysql.ER_UNKNOWN_ERROR, err.Error())) } rs, err := sei.executePlan(plan) if err != nil { return sei.writeError(mysql.NewError(mysql.ER_UNKNOWN_ERROR, err.Error())) } offset, count := plan.getOffetCount() // merge select rs return sei.mergeSelectResult(rs, stmt, offset, count) }
func (sei *session) handleNormalExecute(stmt sqlparser.Statement) error { if sei.db == "" { return sei.writeError(mysql.NewDefaultError(mysql.ER_NO_DB_ERROR)) } plan, err := sei.buildPlan(stmt) if err != nil { return sei.writeError(err) } rs, err := sei.executePlan(plan) if err != nil { return sei.writeError(mysql.NewError(mysql.ER_UNKNOWN_ERROR, err.Error())) } // merge results return sei.mergeExecResult(rs) }
func (sei *session) writeError(e error) error { var m *mysql.SqlError var ok bool if m, ok = e.(*mysql.SqlError); !ok { m = mysql.NewError(mysql.ER_UNKNOWN_ERROR, e.Error()) } data := make([]byte, 4, 16+len(m.Message)) data = append(data, mysql.ERR_HEADER) data = append(data, byte(m.Code), byte(m.Code>>8)) if sei.capability&mysql.CLIENT_PROTOCOL_41 > 0 { data = append(data, '#') data = append(data, m.State...) } data = append(data, m.Message...) return sei.pkg.WritePacket(data) }
func (sei *session) buildInsertPlan(stmt *sqlparser.Insert) (*Plan, error) { plan := &Plan{} isNoCol := false isNeedInitCol := true colLen := len(stmt.Columns) // get table table, err := sei.getMeta().GetTable(sei.user, sei.db, string(stmt.Table.Name)) if err != nil { return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, err.Error()) } plan.Table = table if colLen == 0 { isNoCol = true } // get sqls values, ok := stmt.Rows.(sqlparser.Values) if !ok { return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unsupport") } for row, value := range values { tuple, ok := value.(sqlparser.ValTuple) if !ok { return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unsupport") } if (isNoCol && len(tuple) != table.Table.ColsLen) || !isNoCol && len(tuple) != colLen { return nil, mysql.NewDefaultError(mysql.ER_WRONG_VALUE_COUNT_ON_ROW, row+1) } // rewrite auto key if isNoCol { // rewrite by index for _, autokey := range table.Table.AutoKeys { id, err := table.GetKey(autokey.Name, config.Config.Proxy.AutoKeyInterval, sei.server.info) if err != nil { glog.Warningf("Get autokey(%v) get error(%v)", autokey.Name, err) return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, err.Error()) } if autokey.Type == meta.TypeKeyInt { tuple[autokey.Index] = sqlparser.NumVal(id) } else { tuple[autokey.Index] = sqlparser.StrVal(id) } } } else { for _, autokey := range table.Table.AutoKeys { isExist := false id, err := table.GetKey(autokey.Name, config.Config.Proxy.AutoKeyInterval, sei.server.info) if err != nil { glog.Warningf("Get autokey(%v) get error(%v)", autokey.Name, err) return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, err.Error()) } for n, column := range stmt.Columns { col, ok := column.(*sqlparser.NonStarExpr) if !ok { return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unsupport") } colName, ok := col.Expr.(*sqlparser.ColName) if !ok { return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unsupport") } if autokey.Name == string(colName.Name) && n < len(tuple) { isExist = true if autokey.Type == meta.TypeKeyInt { tuple[n] = sqlparser.NumVal(id) } else { tuple[n] = sqlparser.StrVal(id) } } } if !isExist { if isNeedInitCol { stmt.Columns = append(stmt.Columns, &sqlparser.NonStarExpr{ Expr: &sqlparser.ColName{ Name: sqlparser.SQLName(autokey.Name), }, }) } if autokey.Type == meta.TypeKeyInt { tuple = append(tuple, sqlparser.NumVal(id)) } else { tuple = append(tuple, sqlparser.StrVal(id)) } } } } values[row] = tuple isNeedInitCol = false } stmt.Rows = values sqls := make(map[int]sqlparser.InsertRows) // split stmt for row, value := range values { tuple := value.(sqlparser.ValTuple) var value string if isNoCol { // get hash key switch v := tuple[table.Table.PartitionKey.Index].(type) { case sqlparser.NumVal: value = string(v) case sqlparser.StrVal: value = string(v) default: return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unknow partition key type") } } else { for col, column := range stmt.Columns { name := string(column.(*sqlparser.NonStarExpr).Expr.(*sqlparser.ColName).Name) if name == table.Table.PartitionKey.Name { switch v := tuple[col].(type) { case sqlparser.NumVal: value = string(v) case sqlparser.StrVal: value = string(v) default: return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unknow partition key type") } } } } if value == "" { glog.Warning("Get partition key error value is null") return nil, mysql.NewError(mysql.ER_UNKNOWN_ERROR, "unknow partition key type") } index := FindForKey(value, len(table.Backends)) s, ok := sqls[index] if !ok { sqls[index] = sqlparser.Values{values[row]} } else { sqls[index] = append(s.(sqlparser.Values), values[row]) } } var lastIDs []uint64 if len(table.Table.AutoIns) > 0 { index := 0 if isNoCol { // range column index = table.Table.AutoIns[0].Index } else { // get index index = sort.Search(len(stmt.Columns), func(i int) bool { return string(stmt.Columns[i].(*sqlparser.NonStarExpr).Expr.(*sqlparser.ColName).Name) == table.Table.AutoIns[0].Name }) } lastIDs = make([]uint64, len(sqls)) n := 0 for _, sql := range sqls { switch v := sql.(sqlparser.Values)[0].(sqlparser.ValTuple)[index].(type) { case sqlparser.NumVal: glog.Info(n, len(lastIDs)) lastIDs[n], _ = strconv.ParseUint(string(v), 10, 64) case sqlparser.StrVal: lastIDs[n], _ = strconv.ParseUint(string(v), 10, 64) } n++ } } for k, v := range sqls { stmt.Rows = v plan.generateOneSQL(stmt, k) } return plan, nil }
func (sei *session) handleShow(stmt *sqlparser.Show) (*mysql.Resultset, error) { var ( r = new(mysql.Resultset) name []string values [][]interface{} err error ) switch strings.ToLower(stmt.Key) { case "databases": name = append(name, "Database") // get databases/tables from etcd var dbs []string dbs, err = sei.server.info.ShowDatabases(sei.user) if err != nil { return nil, err } if len(dbs) > 0 { values = make([][]interface{}, 0, len(dbs)) } else { glog.Info("empty databases") r, err = sei.buildEmptySet(name, []interface{}{""}) break } for _, db := range dbs { values = append(values, []interface{}{db}) } r, err = sei.buildResultset(nil, name, values) case "tables": if sei.db == "" { return nil, mysql.NewError(mysql.ER_NO_DB_ERROR, mysql.MySQLErrName[mysql.ER_NO_DB_ERROR]) } // get talbbes name = append(name, fmt.Sprintf("Tables_in_%s", sei.db)) // get databases from etcd var dbs []string dbs, err = sei.server.info.ShowTables(sei.user, sei.db) if err != nil { return nil, err } if len(dbs) > 0 { values = make([][]interface{}, 0, len(dbs)) } else { glog.Info("empty tables") r, err = sei.buildEmptySet(name, []interface{}{""}) break } for _, db := range dbs { values = append(values, []interface{}{db}) } r, err = sei.buildResultset(nil, name, values) case "variables": case "ddl_task": // get tasks // TODO } return r, err }
func (sei *session) handleDDL(sql string) error { stmt, err := parser.ParseOneStmt(sql, "", "") if err != nil { glog.Infof("parse ddl sql(%s) error:%v", sql, err) return sei.writeError(mysql.NewDefaultError(mysql.ER_SYNTAX_ERROR)) } switch v := stmt.(type) { case *ast.CreateDatabaseStmt: dbname := v.Name id, rows, err := sei.ddlManage().CreateDatabase(sei.user, dbname, sei.dbnum) glog.Infof("DDL plan id(%v)", id) if err != nil { glog.Infof("CREATE DATABASE has an error(%v)", err) err = sei.writeError(err) } else { // one time only creata a db r := &mysql.Result{ AffectedRows: rows, } err = sei.writeOK(r) } return err case *ast.CreateTableStmt: if sei.db == "" { return sei.writeError(mysql.NewDefaultError(mysql.ER_NO_DB_ERROR)) } table := &meta.Table{ Scheme: "hash", Name: v.Table.Name.String(), PartitionKey: &meta.Key{ Name: sei.partitionKey, }, ColsLen: len(v.Cols), } existMap := make(map[string]bool) // get constraints for _, constraint := range v.Constraints { if constraint.Tp == ast.ConstraintPrimaryKey || constraint.Tp == ast.ConstraintUniq { if len(constraint.Keys) > 1 { err := mysql.NewError(mysql.ER_SYNTAX_ERROR, "not support constraint keys' length > 1") return sei.writeError(err) } // get type name := constraint.Keys[0].Column.Name.String() index, typ := sei.getFieldType(v.Cols, name) if typ == meta.TypeKeyUnknow { err := mysql.NewError(mysql.ER_SYNTAX_ERROR, "unsupport key's type ") return sei.writeError(err) } if constraint.Tp == ast.ConstraintPrimaryKey && sei.partitionKey == "" { // set primary key for partition key table.PartitionKey.Name = name table.PartitionKey.Type = typ table.PartitionKey.Index = index } table.AutoKeys = append(table.AutoKeys, &meta.Key{ Name: name, Type: typ, Index: index, }) existMap[name] = true } } // check auto increment for _, col := range v.Cols { for n, option := range col.Options { t := sei.getOneFeildType(col) if t == meta.TypeKeyUnknow { err := mysql.NewError(mysql.ER_SYNTAX_ERROR, "unsupport key's type ") return sei.writeError(err) } switch option.Tp { case ast.ColumnOptionAutoIncrement, ast.ColumnOptionPrimaryKey, ast.ColumnOptionUniq: if ast.ColumnOptionPrimaryKey == option.Tp && table.PartitionKey.Name == "" { table.PartitionKey.Name = col.Name.Name.String() table.PartitionKey.Type = t table.PartitionKey.Index = n } // check if exist not append if existMap[col.Name.Name.String()] { continue } table.AutoKeys = append(table.AutoKeys, &meta.Key{ Name: col.Name.Name.String(), Type: t, Index: n, }) existMap[col.Name.Name.String()] = true } if option.Tp == ast.ColumnOptionAutoIncrement { glog.Infof("record auto increment option index(%v)", n) // record table.AutoIns = append(table.AutoIns, &meta.Key{ Name: col.Name.Name.String(), Type: t, Index: n, }) } } } // check partition key if table.PartitionKey.Name == "" { err := mysql.NewError(mysql.ER_SYNTAX_ERROR, "partitionKey is null") return sei.writeError(err) } data, _ := json.MarshalIndent(table, "", "\t") glog.Info("table is\n", string(data)) id, rows, err := sei.ddlManage().CreateTable(sei.user, sei.db, sql, table) glog.Infof("DDL plan id(%v)", id) if err != nil { glog.Infof("CREATE TABLE has an error(%v)", err) err = sei.writeError(err) } else { // one time only creata a db r := &mysql.Result{ AffectedRows: rows, } err = sei.writeOK(r) } return err default: return fmt.Errorf("create statement %T not support now", stmt) } }