github.com/RevenueMonster/sqlike@v1.0.6/sql/dialect/mysql/schema.go (about) 1 package mysql 2 3 import ( 4 "reflect" 5 "strconv" 6 "strings" 7 8 "github.com/RevenueMonster/sqlike/reflext" 9 "github.com/RevenueMonster/sqlike/sql/charset" 10 "github.com/RevenueMonster/sqlike/sql/schema" 11 sqlstmt "github.com/RevenueMonster/sqlike/sql/stmt" 12 sqltype "github.com/RevenueMonster/sqlike/sql/type" 13 sqlutil "github.com/RevenueMonster/sqlike/sql/util" 14 "github.com/RevenueMonster/sqlike/sqlike/columns" 15 "github.com/RevenueMonster/sqlike/util" 16 "golang.org/x/text/currency" 17 ) 18 19 var charsetMap = map[string]string{ 20 "utf8mb4": "utf8mb4_unicode_ci", 21 "latin1": "latin1_bin", 22 } 23 24 // mySQLSchema : 25 type mySQLSchema struct { 26 sqlutil.MySQLUtil 27 } 28 29 // SetBuilders : 30 func (s mySQLSchema) SetBuilders(sb *schema.Builder) { 31 sb.SetTypeBuilder(sqltype.Byte, s.ByteDataType) 32 sb.SetTypeBuilder(sqltype.Date, s.DateDataType) 33 sb.SetTypeBuilder(sqltype.Time, s.TimeDataType) 34 sb.SetTypeBuilder(sqltype.DateTime, s.DateTimeDataType) 35 sb.SetTypeBuilder(sqltype.Timestamp, s.DateTimeDataType) 36 sb.SetTypeBuilder(sqltype.UUID, s.UUIDDataType) 37 sb.SetTypeBuilder(sqltype.JSON, s.JSONDataType) 38 sb.SetTypeBuilder(sqltype.Point, s.SpatialDataType("POINT")) 39 sb.SetTypeBuilder(sqltype.LineString, s.SpatialDataType("LINESTRING")) 40 sb.SetTypeBuilder(sqltype.Polygon, s.SpatialDataType("POLYGON")) 41 sb.SetTypeBuilder(sqltype.MultiPoint, s.SpatialDataType("MULTIPOINT")) 42 sb.SetTypeBuilder(sqltype.MultiLineString, s.SpatialDataType("MULTILINESTRING")) 43 sb.SetTypeBuilder(sqltype.MultiPolygon, s.SpatialDataType("MULTIPOLYGON")) 44 sb.SetTypeBuilder(sqltype.String, s.StringDataType) 45 sb.SetTypeBuilder(sqltype.Char, s.CharDataType) 46 sb.SetTypeBuilder(sqltype.Bool, s.BoolDataType) 47 sb.SetTypeBuilder(sqltype.Int, s.IntDataType) 48 sb.SetTypeBuilder(sqltype.Int8, s.IntDataType) 49 sb.SetTypeBuilder(sqltype.Int16, s.IntDataType) 50 sb.SetTypeBuilder(sqltype.Int32, s.IntDataType) 51 sb.SetTypeBuilder(sqltype.Int64, s.IntDataType) 52 sb.SetTypeBuilder(sqltype.Uint, s.UintDataType) 53 sb.SetTypeBuilder(sqltype.Uint8, s.UintDataType) 54 sb.SetTypeBuilder(sqltype.Uint16, s.UintDataType) 55 sb.SetTypeBuilder(sqltype.Uint32, s.UintDataType) 56 sb.SetTypeBuilder(sqltype.Uint64, s.UintDataType) 57 sb.SetTypeBuilder(sqltype.Float32, s.FloatDataType) 58 sb.SetTypeBuilder(sqltype.Float64, s.FloatDataType) 59 sb.SetTypeBuilder(sqltype.Struct, s.JSONDataType) 60 sb.SetTypeBuilder(sqltype.Array, s.ArrayDataType) 61 sb.SetTypeBuilder(sqltype.Slice, s.JSONDataType) 62 sb.SetTypeBuilder(sqltype.Map, s.JSONDataType) 63 } 64 65 func (s mySQLSchema) ByteDataType(sf reflext.StructFielder) (col columns.Column) { 66 col.Name = sf.Name() 67 col.DataType = "MEDIUMBLOB" 68 col.Type = "MEDIUMBLOB" 69 col.Nullable = sf.IsNullable() 70 tag := sf.Tag() 71 if v, ok := tag.LookUp("default"); ok { 72 col.DefaultValue = &v 73 } 74 return 75 } 76 77 func (s mySQLSchema) UUIDDataType(sf reflext.StructFielder) (col columns.Column) { 78 charset, collation := string(charset.UTF8MB4), "utf8mb4_unicode_ci" 79 col.Name = sf.Name() 80 col.DataType = "VARCHAR" 81 col.Type = "VARCHAR(36)" 82 col.Size = 36 83 col.Charset = &charset 84 col.Collation = &collation 85 col.Nullable = sf.IsNullable() 86 return 87 } 88 89 func (s mySQLSchema) DateDataType(sf reflext.StructFielder) (col columns.Column) { 90 col.Name = sf.Name() 91 col.DataType = "DATE" 92 col.Type = "DATE" 93 col.Nullable = sf.IsNullable() 94 return 95 } 96 97 func (s mySQLSchema) TimeDataType(sf reflext.StructFielder) (col columns.Column) { 98 size := "6" 99 if v, exists := sf.Tag().LookUp("size"); exists { 100 if _, err := strconv.Atoi(v); err == nil { 101 size = v 102 } 103 } 104 105 col.Name = sf.Name() 106 col.DataType = "TIME" 107 col.Type = "TIME(" + size + ")" 108 col.Nullable = sf.IsNullable() 109 // col.DefaultValue = &dflt 110 // if _, ok := sf.Tag().LookUp("on_update"); ok { 111 // col.Extra = "ON UPDATE " + dflt 112 // } 113 return 114 } 115 116 func (s mySQLSchema) DateTimeDataType(sf reflext.StructFielder) (col columns.Column) { 117 size := "6" 118 if v, exists := sf.Tag().LookUp("size"); exists { 119 if _, err := strconv.Atoi(v); err == nil { 120 size = v 121 } 122 } 123 124 dflt := "CURRENT_TIMESTAMP(" + size + ")" 125 col.Name = sf.Name() 126 col.DataType = "DATETIME" 127 col.Type = "DATETIME(" + size + ")" 128 col.Nullable = sf.IsNullable() 129 col.DefaultValue = &dflt 130 if _, ok := sf.Tag().LookUp("on_update"); ok { 131 col.Extra = "ON UPDATE " + dflt 132 } 133 return 134 } 135 136 func (s mySQLSchema) JSONDataType(sf reflext.StructFielder) (col columns.Column) { 137 col.Name = sf.Name() 138 col.DataType = "JSON" 139 col.Type = "JSON" 140 col.Nullable = sf.IsNullable() 141 return 142 } 143 144 func (s mySQLSchema) SpatialDataType(dataType string) schema.DataTypeFunc { 145 return func(sf reflext.StructFielder) (col columns.Column) { 146 col.Name = sf.Name() 147 col.DataType = dataType 148 col.Type = dataType 149 if sf.Type().Kind() == reflect.Ptr { 150 col.Nullable = true 151 } 152 if v, ok := sf.Tag().LookUp("srid"); ok { 153 if _, err := strconv.ParseUint(v, 10, 64); err != nil { 154 return 155 } 156 col.Extra = "SRID " + v 157 } 158 return 159 } 160 } 161 162 func (s mySQLSchema) StringDataType(sf reflext.StructFielder) (col columns.Column) { 163 col.Name = sf.Name() 164 col.Nullable = sf.IsNullable() 165 166 charset := "utf8mb4" 167 collation := charsetMap[charset] 168 dflt := "" 169 tag := sf.Tag() 170 cs, ok1 := tag.LookUp("charset") 171 if ok1 { 172 charset = strings.ToLower(cs) 173 collation = charsetMap[charset] 174 } 175 176 col.DefaultValue = &dflt 177 col.Charset = &charset 178 col.Collation = &collation 179 if v, ok := tag.LookUp("default"); ok { 180 col.DefaultValue = &v 181 } 182 183 if enum, ok := tag.LookUp("enum"); ok { 184 paths := strings.Split(enum, "|") 185 if len(paths) < 1 { 186 panic("invalid enum formats") 187 } 188 189 if !ok1 { 190 charset = "utf8mb4" 191 collation = "utf8mb4_unicode_ci" 192 } 193 194 blr := util.AcquireString() 195 defer util.ReleaseString(blr) 196 blr.WriteString("ENUM") 197 blr.WriteRune('(') 198 for i, p := range paths { 199 if i > 0 { 200 blr.WriteRune(',') 201 } 202 blr.WriteString(s.Wrap(p)) 203 } 204 blr.WriteRune(')') 205 206 dflt = paths[0] 207 col.DataType = "ENUM" 208 col.Type = blr.String() 209 col.DefaultValue = &dflt 210 return 211 } else if char, ok := tag.LookUp("char"); ok { 212 if _, err := strconv.Atoi(char); err != nil { 213 panic("invalid value for char data type") 214 } 215 col.DataType = "CHAR" 216 col.Type = "CHAR(" + char + ")" 217 return 218 } else if _, ok := tag.LookUp("longtext"); ok { 219 col.DataType = "TEXT" 220 col.Type = "TEXT" 221 col.DefaultValue = nil 222 col.Charset = nil 223 col.Collation = nil 224 return 225 } 226 227 size, _ := tag.LookUp("size") 228 charLen, _ := strconv.Atoi(size) 229 if charLen < 1 { 230 charLen = 191 231 } 232 233 col.DataType = "VARCHAR" 234 col.Type = "VARCHAR(" + strconv.Itoa(charLen) + ")" 235 return 236 } 237 238 func (s mySQLSchema) CharDataType(sf reflext.StructFielder) (col columns.Column) { 239 dflt := "" 240 switch sf.Type() { 241 case reflect.TypeOf(currency.Unit{}): 242 charset, collation := string(charset.UTF8MB4), "utf8mb4_unicode_ci" 243 col.Type = "CHAR(3)" 244 col.Charset = &charset 245 col.Collation = &collation 246 default: 247 charset, collation := string(charset.UTF8MB4), "utf8mb4_unicode_ci" 248 col.Type = "CHAR(191)" 249 col.Charset = &charset 250 col.Collation = &collation 251 } 252 col.Name = sf.Name() 253 col.DataType = "CHAR" 254 col.Nullable = sf.IsNullable() 255 col.DefaultValue = &dflt 256 return 257 } 258 259 func (s mySQLSchema) BoolDataType(sf reflext.StructFielder) (col columns.Column) { 260 dflt := "0" 261 col.Name = sf.Name() 262 col.DataType = "TINYINT" 263 col.Type = "TINYINT(1)" 264 col.Nullable = sf.IsNullable() 265 col.DefaultValue = &dflt 266 return 267 } 268 269 func (s mySQLSchema) IntDataType(sf reflext.StructFielder) (col columns.Column) { 270 t := sf.Type() 271 tag := sf.Tag() 272 dflt := "0" 273 dataType := s.getIntDataType(reflext.Deref(t)) 274 275 col.Name = sf.Name() 276 col.DataType = dataType 277 col.Type = dataType 278 col.Nullable = sf.IsNullable() 279 col.DefaultValue = &dflt 280 if _, ok := tag.LookUp("auto_increment"); ok { 281 col.Extra = "AUTO_INCREMENT" 282 col.DefaultValue = nil 283 } else if v, ok := tag.LookUp("default"); ok { 284 if _, err := strconv.ParseUint(v, 10, 64); err != nil { 285 panic("int default value should be integer") 286 } 287 col.DefaultValue = &v 288 } 289 return 290 } 291 292 func (s mySQLSchema) UintDataType(sf reflext.StructFielder) (col columns.Column) { 293 t := sf.Type() 294 tag := sf.Tag() 295 dflt := "0" 296 dataType := s.getIntDataType(reflext.Deref(t)) 297 298 col.Name = sf.Name() 299 col.DataType = dataType 300 col.Type = dataType + " UNSIGNED" 301 col.Nullable = sf.IsNullable() 302 col.DefaultValue = &dflt 303 if _, ok := tag.LookUp("auto_increment"); ok { 304 col.Extra = "AUTO_INCREMENT" 305 col.DefaultValue = nil 306 } else if v, ok := tag.LookUp("default"); ok { 307 if _, err := strconv.ParseUint(v, 10, 64); err != nil { 308 panic("uint default value should be unsigned integer") 309 } 310 col.DefaultValue = &v 311 } 312 return 313 } 314 315 func (s mySQLSchema) FloatDataType(sf reflext.StructFielder) (col columns.Column) { 316 dflt := "0" 317 tag := sf.Tag() 318 col.Name = sf.Name() 319 col.DataType = "REAL" 320 col.Type = "REAL" 321 if _, ok := tag.LookUp("unsigned"); ok { 322 col.Type += " UNSIGNED" 323 } 324 col.Nullable = sf.IsNullable() 325 col.DefaultValue = &dflt 326 if v, ok := tag.LookUp("default"); ok { 327 if _, err := strconv.ParseFloat(v, 64); err != nil { 328 panic("float default value should be decimal number") 329 } 330 col.DefaultValue = &v 331 } 332 return 333 } 334 335 func (s mySQLSchema) ArrayDataType(sf reflext.StructFielder) (col columns.Column) { 336 col.Name = sf.Name() 337 col.Nullable = sf.IsNullable() 338 // length := sf.Zero.Len() 339 t := sf.Type().Elem() 340 if t.Kind() == reflect.Uint8 { 341 charset, collation := "ascii", "ascii_general_ci" 342 col.DataType = "VARCHAR" 343 col.Type = "VARCHAR(36)" 344 col.Charset = &charset 345 col.Collation = &collation 346 return 347 } 348 col.DataType = "JSON" 349 col.Type = "JSON" 350 return 351 } 352 353 func (ms MySQL) buildSchemaByColumn(stmt sqlstmt.Stmt, col columns.Column) { 354 stmt.WriteString(ms.Quote(col.Name)) 355 stmt.WriteString(" " + col.Type) 356 if col.Charset != nil { 357 stmt.WriteString(" CHARACTER SET " + *col.Charset) 358 } 359 if col.Collation != nil { 360 stmt.WriteString(" COLLATE " + *col.Collation) 361 } 362 if col.Extra != "" { 363 stmt.WriteString(" " + col.Extra) 364 } 365 if !col.Nullable { 366 stmt.WriteString(" NOT NULL") 367 if col.DefaultValue != nil { 368 stmt.WriteString(" DEFAULT " + ms.WrapOnlyValue(*col.DefaultValue)) 369 } 370 } 371 } 372 373 func (s mySQLSchema) getIntDataType(t reflect.Type) (dataType string) { 374 switch t.Kind() { 375 case reflect.Int8, reflect.Uint8: 376 dataType = "TINYINT" 377 case reflect.Int16, reflect.Uint16: 378 dataType = "SMALLINT" 379 case reflect.Int32, reflect.Uint32: 380 dataType = "INT" 381 case reflect.Int64, reflect.Uint64: 382 dataType = "BIGINT" 383 default: 384 dataType = "INT" 385 } 386 return 387 }