func (engine *Engine) mapType(v reflect.Value) *core.Table { t := v.Type() table := engine.newTable() method := v.MethodByName("TableName") if !method.IsValid() { if v.CanAddr() { method = v.Addr().MethodByName("TableName") } } if method.IsValid() { params := []reflect.Value{} results := method.Call(params) if len(results) == 1 { table.Name = results[0].Interface().(string) } } if table.Name == "" { table.Name = engine.TableMapper.Obj2Table(t.Name()) } table.Type = t var idFieldColName string var err error for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) var col *core.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() if ormTagStr != "" { col = &core.Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, IsAutoIncrement: false, MapType: core.TWOSIDES, Indexes: make(map[string]bool)} tags := strings.Split(ormTagStr, " ") if len(tags) > 0 { if tags[0] == "-" { continue } if strings.ToUpper(tags[0]) == "EXTENDS" { if fieldValue.Kind() == reflect.Struct { parentTable := engine.mapType(fieldValue) for _, col := range parentTable.Columns() { col.FieldName = fmt.Sprintf("%v.%v", t.Field(i).Name, col.FieldName) table.AddColumn(col) } continue } else if fieldValue.Kind() == reflect.Ptr { f := fieldValue.Type().Elem() if f.Kind() == reflect.Struct { fieldValue = fieldValue.Elem() if !fieldValue.IsValid() || fieldValue.IsNil() { fieldValue = reflect.New(f).Elem() } } parentTable := engine.mapType(fieldValue) for _, col := range parentTable.Columns() { col.FieldName = fmt.Sprintf("%v.%v", t.Field(i).Name, col.FieldName) table.AddColumn(col) } continue } //TODO: warning } indexNames := make(map[string]int) var isIndex, isUnique bool var preKey string for j, key := range tags { k := strings.ToUpper(key) switch { case k == "<-": col.MapType = core.ONLYFROMDB case k == "->": col.MapType = core.ONLYTODB case k == "PK": col.IsPrimaryKey = true col.Nullable = false case k == "NULL": col.Nullable = (strings.ToUpper(tags[j-1]) != "NOT") /*case strings.HasPrefix(k, "AUTOINCR(") && strings.HasSuffix(k, ")"): col.IsAutoIncrement = true autoStart := k[len("AUTOINCR")+1 : len(k)-1] autoStartInt, err := strconv.Atoi(autoStart) if err != nil { engine.LogError(err) } col.AutoIncrStart = autoStartInt*/ case k == "AUTOINCR": col.IsAutoIncrement = true //col.AutoIncrStart = 1 case k == "DEFAULT": col.Default = tags[j+1] case k == "CREATED": col.IsCreated = true case k == "VERSION": col.IsVersion = true col.Default = "1" case k == "UPDATED": col.IsUpdated = true case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): indexName := k[len("INDEX")+1 : len(k)-1] indexNames[indexName] = core.IndexType case k == "INDEX": isIndex = true case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"): indexName := k[len("UNIQUE")+1 : len(k)-1] indexNames[indexName] = core.UniqueType case k == "UNIQUE": isUnique = true case k == "NOTNULL": col.Nullable = false case k == "NOT": default: if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { fs := strings.Split(k, "(") if _, ok := core.SqlTypes[fs[0]]; !ok { preKey = k continue } col.SQLType = core.SQLType{fs[0], 0, 0} if fs[0] == core.Enum && fs[1][0] == '\'' { //enum options := strings.Split(fs[1][0:len(fs[1])-1], ",") col.EnumOptions = make(map[string]int) for k, v := range options { v = strings.TrimSpace(v) v = strings.Trim(v, "'") col.EnumOptions[v] = k } } else { fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",") if len(fs2) == 2 { col.Length, err = strconv.Atoi(fs2[0]) if err != nil { engine.LogError(err) } col.Length2, err = strconv.Atoi(fs2[1]) if err != nil { engine.LogError(err) } } else if len(fs2) == 1 { col.Length, err = strconv.Atoi(fs2[0]) if err != nil { engine.LogError(err) } } } } else { if _, ok := core.SqlTypes[k]; ok { col.SQLType = core.SQLType{k, 0, 0} } else if key != col.Default { col.Name = key } } engine.dialect.SqlType(col) } preKey = k } if col.SQLType.Name == "" { col.SQLType = core.Type2SQLType(fieldType) } if col.Length == 0 { col.Length = col.SQLType.DefaultLength } if col.Length2 == 0 { col.Length2 = col.SQLType.DefaultLength2 } if col.Name == "" { col.Name = engine.ColumnMapper.Obj2Table(t.Field(i).Name) } if isUnique { indexNames[col.Name] = core.UniqueType } else if isIndex { indexNames[col.Name] = core.IndexType } for indexName, indexType := range indexNames { addIndex(indexName, table, col, indexType) } } } else { var sqlType core.SQLType if fieldValue.CanAddr() { if _, ok := fieldValue.Addr().Interface().(core.Conversion); ok { sqlType = core.SQLType{core.Text, 0, 0} } } if _, ok := fieldValue.Interface().(core.Conversion); ok { sqlType = core.SQLType{core.Text, 0, 0} } else { sqlType = core.Type2SQLType(fieldType) } col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, sqlType.DefaultLength, sqlType.DefaultLength2, true) } if col.IsAutoIncrement { col.Nullable = false } table.AddColumn(col) if fieldType.Kind() == reflect.Int64 && (col.FieldName == "Id" || strings.HasSuffix(col.FieldName, ".Id")) { idFieldColName = col.Name } } if idFieldColName != "" && len(table.PrimaryKeys) == 0 { col := table.GetColumn(idFieldColName) col.IsPrimaryKey = true col.IsAutoIncrement = true col.Nullable = false table.PrimaryKeys = append(table.PrimaryKeys, col.Name) table.AutoIncrement = col.Name } return table }
func (engine *Engine) mapType(v reflect.Value) *core.Table { t := v.Type() table := engine.newTable() if tb, ok := v.Interface().(TableName); ok { table.Name = tb.TableName() } else { if v.CanAddr() { if tb, ok = v.Addr().Interface().(TableName); ok { table.Name = tb.TableName() } } if table.Name == "" { table.Name = engine.TableMapper.Obj2Table(t.Name()) } } table.Type = t var idFieldColName string var err error var hasCacheTag, hasNoCacheTag bool for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) var col *core.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() if ormTagStr != "" { col = &core.Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, IsAutoIncrement: false, MapType: core.TWOSIDES, Indexes: make(map[string]int)} tags := splitTag(ormTagStr) if len(tags) > 0 { if tags[0] == "-" { continue } if strings.ToUpper(tags[0]) == "EXTENDS" { switch fieldValue.Kind() { case reflect.Ptr: f := fieldValue.Type().Elem() if f.Kind() == reflect.Struct { fieldPtr := fieldValue fieldValue = fieldValue.Elem() if !fieldValue.IsValid() || fieldPtr.IsNil() { fieldValue = reflect.New(f).Elem() } } fallthrough case reflect.Struct: parentTable := engine.mapType(fieldValue) for _, col := range parentTable.Columns() { col.FieldName = fmt.Sprintf("%v.%v", t.Field(i).Name, col.FieldName) table.AddColumn(col) for indexName, indexType := range col.Indexes { addIndex(indexName, table, col, indexType) } } continue default: //TODO: warning } } indexNames := make(map[string]int) var isIndex, isUnique bool var preKey string for j, key := range tags { k := strings.ToUpper(key) switch { case k == "<-": col.MapType = core.ONLYFROMDB case k == "->": col.MapType = core.ONLYTODB case k == "PK": col.IsPrimaryKey = true col.Nullable = false case k == "NULL": if j == 0 { col.Nullable = true } else { col.Nullable = (strings.ToUpper(tags[j-1]) != "NOT") } // TODO: for postgres how add autoincr? /*case strings.HasPrefix(k, "AUTOINCR(") && strings.HasSuffix(k, ")"): col.IsAutoIncrement = true autoStart := k[len("AUTOINCR")+1 : len(k)-1] autoStartInt, err := strconv.Atoi(autoStart) if err != nil { engine.LogError(err) } col.AutoIncrStart = autoStartInt*/ case k == "AUTOINCR": col.IsAutoIncrement = true //col.AutoIncrStart = 1 case k == "DEFAULT": col.Default = tags[j+1] case k == "CREATED": col.IsCreated = true case k == "VERSION": col.IsVersion = true col.Default = "1" case k == "UTC": col.TimeZone = time.UTC case k == "LOCAL": col.TimeZone = time.Local case strings.HasPrefix(k, "LOCALE(") && strings.HasSuffix(k, ")"): location := k[len("INDEX")+1 : len(k)-1] col.TimeZone, err = time.LoadLocation(location) if err != nil { engine.logger.Error(err) } case k == "UPDATED": col.IsUpdated = true case k == "DELETED": col.IsDeleted = true case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): indexName := k[len("INDEX")+1 : len(k)-1] indexNames[indexName] = core.IndexType case k == "INDEX": isIndex = true case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"): indexName := k[len("UNIQUE")+1 : len(k)-1] indexNames[indexName] = core.UniqueType case k == "UNIQUE": isUnique = true case k == "NOTNULL": col.Nullable = false case k == "CACHE": if !hasCacheTag { hasCacheTag = true } case k == "NOCACHE": if !hasNoCacheTag { hasNoCacheTag = true } case k == "NOT": default: if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { if preKey != "DEFAULT" { col.Name = key[1 : len(key)-1] } } else if strings.Contains(k, "(") && strings.HasSuffix(k, ")") { fs := strings.Split(k, "(") if _, ok := core.SqlTypes[fs[0]]; !ok { preKey = k continue } col.SQLType = core.SQLType{Name: fs[0]} if fs[0] == core.Enum && fs[1][0] == '\'' { //enum options := strings.Split(fs[1][0:len(fs[1])-1], ",") col.EnumOptions = make(map[string]int) for k, v := range options { v = strings.TrimSpace(v) v = strings.Trim(v, "'") col.EnumOptions[v] = k } } else if fs[0] == core.Set && fs[1][0] == '\'' { //set options := strings.Split(fs[1][0:len(fs[1])-1], ",") col.SetOptions = make(map[string]int) for k, v := range options { v = strings.TrimSpace(v) v = strings.Trim(v, "'") col.SetOptions[v] = k } } else { fs2 := strings.Split(fs[1][0:len(fs[1])-1], ",") if len(fs2) == 2 { col.Length, err = strconv.Atoi(fs2[0]) if err != nil { engine.logger.Error(err) } col.Length2, err = strconv.Atoi(fs2[1]) if err != nil { engine.logger.Error(err) } } else if len(fs2) == 1 { col.Length, err = strconv.Atoi(fs2[0]) if err != nil { engine.logger.Error(err) } } } } else { if _, ok := core.SqlTypes[k]; ok { col.SQLType = core.SQLType{Name: k} } else if key != col.Default { col.Name = key } } engine.dialect.SqlType(col) } preKey = k } if col.SQLType.Name == "" { col.SQLType = core.Type2SQLType(fieldType) } if col.Length == 0 { col.Length = col.SQLType.DefaultLength } if col.Length2 == 0 { col.Length2 = col.SQLType.DefaultLength2 } if col.Name == "" { col.Name = engine.ColumnMapper.Obj2Table(t.Field(i).Name) } if isUnique { indexNames[col.Name] = core.UniqueType } else if isIndex { indexNames[col.Name] = core.IndexType } for indexName, indexType := range indexNames { addIndex(indexName, table, col, indexType) } } } else { var sqlType core.SQLType if fieldValue.CanAddr() { if _, ok := fieldValue.Addr().Interface().(core.Conversion); ok { sqlType = core.SQLType{Name: core.Text} } } if _, ok := fieldValue.Interface().(core.Conversion); ok { sqlType = core.SQLType{Name: core.Text} } else { sqlType = core.Type2SQLType(fieldType) } col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, sqlType.DefaultLength, sqlType.DefaultLength2, true) } if col.IsAutoIncrement { col.Nullable = false } table.AddColumn(col) if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { idFieldColName = col.Name } } // end for if idFieldColName != "" && len(table.PrimaryKeys) == 0 { col := table.GetColumn(idFieldColName) col.IsPrimaryKey = true col.IsAutoIncrement = true col.Nullable = false table.PrimaryKeys = append(table.PrimaryKeys, col.Name) table.AutoIncrement = col.Name } if hasCacheTag { if engine.Cacher != nil { // !nash! use engine's cacher if provided engine.logger.Info("enable cache on table:", table.Name) table.Cacher = engine.Cacher } else { engine.logger.Info("enable LRU cache on table:", table.Name) table.Cacher = NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) // !nashtsai! HACK use LRU cacher for now } } if hasNoCacheTag { engine.logger.Info("no cache on table:", table.Name) table.Cacher = nil } return table }
func (db *tidb) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { args := []interface{}{db.DbName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + " `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" rows, err := db.DB().Query(s, args...) db.LogSQL(s, args) if err != nil { return nil, nil, err } defer rows.Close() cols := make(map[string]*core.Column) colSeq := make([]string, 0) for rows.Next() { col := new(core.Column) col.Indexes = make(map[string]int) var columnName, isNullable, colType, colKey, extra string var colDefault *string err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra) if err != nil { return nil, nil, err } col.Name = strings.Trim(columnName, "` ") if "YES" == isNullable { col.Nullable = true } if colDefault != nil { col.Default = *colDefault if col.Default == "" { col.DefaultIsEmpty = true } } cts := strings.Split(colType, "(") colName := cts[0] colType = strings.ToUpper(colName) var len1, len2 int if len(cts) == 2 { idx := strings.Index(cts[1], ")") if colType == core.Enum && cts[1][0] == '\'' { //enum options := strings.Split(cts[1][0:idx], ",") col.EnumOptions = make(map[string]int) for k, v := range options { v = strings.TrimSpace(v) v = strings.Trim(v, "'") col.EnumOptions[v] = k } } else if colType == core.Set && cts[1][0] == '\'' { options := strings.Split(cts[1][0:idx], ",") col.SetOptions = make(map[string]int) for k, v := range options { v = strings.TrimSpace(v) v = strings.Trim(v, "'") col.SetOptions[v] = k } } else { lens := strings.Split(cts[1][0:idx], ",") len1, err = strconv.Atoi(strings.TrimSpace(lens[0])) if err != nil { return nil, nil, err } if len(lens) == 2 { len2, err = strconv.Atoi(lens[1]) if err != nil { return nil, nil, err } } } } if colType == "FLOAT UNSIGNED" { colType = "FLOAT" } col.Length = len1 col.Length2 = len2 if _, ok := core.SqlTypes[colType]; ok { col.SQLType = core.SQLType{colType, len1, len2} } else { return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType)) } if colKey == "PRI" { col.IsPrimaryKey = true } if colKey == "UNI" { //col.is } if extra == "auto_increment" { col.IsAutoIncrement = true } if col.SQLType.IsText() || col.SQLType.IsTime() { if col.Default != "" { col.Default = "'" + col.Default + "'" } else { if col.DefaultIsEmpty { col.Default = "''" } } } cols[col.Name] = col colSeq = append(colSeq, col.Name) } return colSeq, cols, nil }