github.com/GnawNom/sqlinternals@v0.0.0-20200413232442-a2dcc5655e0f/mysqlinternals/mysql.go (about) 1 // sqlinternals for github.com/go-sql-driver/mysql - retrieve column metadata from sql.*Row / sql.*Rows 2 // 3 // Copyright 2013 Arne Hormann. All rights reserved. 4 // 5 // This Source Code Form is subject to the terms of the Mozilla Public 6 // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 // You can obtain one at http://mozilla.org/MPL/2.0/. 8 9 package mysqlinternals 10 11 import ( 12 "database/sql" 13 "errors" 14 "fmt" 15 "math/big" 16 "reflect" 17 "time" 18 19 "github.com/go-sql-driver/mysql" 20 ) 21 22 // Column represents the column of a MySQL result. 23 // The methods below postfixed with (*) return information for MySQL internal flags. 24 // Please note that I can't say if these are trustworthy (esp. IsNotNull), they come directly from MySQL. 25 // At least for SCHEMA information, MySQL can report false metadata, I don't know if this is different for results. 26 type Column interface { 27 28 // mysql.name 29 30 // Name returns the column name, matching that of a call to Columns() in database/sql 31 Name() string 32 33 // derived from mysqlField.fieldType 34 35 // MysqlType returns the raw sql type name without parameters and modifiers 36 MysqlType() string 37 // IsNumber returns true if the column contains numbers (one of integer, decimal or floating point) 38 IsNumber() bool 39 // IsInteger returns true if the column contains integers 40 IsInteger() bool 41 // IsFloatingPoint returns true if the column contains floating point numbers 42 IsFloatingPoint() bool 43 // IsDecimal returns true if the column contains decimal numbers 44 IsDecimal() bool 45 // IsText returns true if the column contains textual data 46 IsText() bool 47 // IsBlob returns true if the column contains binary blobs 48 IsBlob() bool 49 // IsTime returns true if the column contains temporal data 50 IsTime() bool 51 52 // derived from mysqlField.flags 53 // TODO: not quite sure about these, add tests and check them. 54 55 // IsPrimaryKey returns true if the column is marked as part of a primary key (*). 56 IsPrimaryKey() bool 57 // IsUniqueKey returns true if the column is marked as part of a unique key (*). 58 IsUniqueKey() bool 59 // IsMultipleKey returns true if the column is marked as part of a regular key (*). 60 IsMultipleKey() bool 61 // IsNotNull returns true if the column is marked as NOT NULL (*). 62 IsNotNull() bool 63 // IsUnsigned returns true if the column is marked as UNSIGNED (*). 64 IsUnsigned() bool 65 // IsZerofill returns true if the column is marked as ZEROFILL (*). 66 IsZerofill() bool 67 // IsBinary returns true if the column is marked as BINARY (*). 68 IsBinary() bool 69 // IsAutoIncrement returns true if the column is marked as AUTO_INCREMENT (*). 70 IsAutoIncrement() bool 71 72 // derived from mysqlField.decimals 73 Decimals() int 74 75 // size of a varchar, or display width for a fixed size type 76 Length() uint32 77 78 // derived from mysqlField.fieldType and mysqlField.flags 79 80 // MysqlParameters returns the category of parameters the SQL type expects in MysqlDeclaration. 81 MysqlParameters() parameterType 82 // MysqlDeclaration returns a type declaration usable in a CREATE TABLE statement. 83 MysqlDeclaration(params ...interface{}) (string, error) 84 // ReflectGoType returns the smallest Go type able to represent all possible regular values. 85 // The returned types assume a non-NULL value and may cause problems 86 // on conversion (e.g. MySQL DATE "0000-00-00", which is not mappable to Go). 87 ReflectGoType() (reflect.Type, error) 88 // ReflectSqlType returns a Go type able to contain the SQL type, including null values. 89 // The returned types may cause problems on conversion 90 // (e.g. MySQL DATE "0000-00-00", which is not mappable to Go). 91 // The returned type assumes IsNotNull() to be false when forceNullable is set 92 // and attempts to return a nullable type (e.g. sql.NullString instead of string). 93 ReflectSqlType(forceNullable bool) (reflect.Type, error) 94 } 95 96 var _ Column = mysqlField{} 97 98 // name of the column 99 func (f mysqlField) Name() string { 100 return f.name 101 } 102 103 // is a numeric type 104 func (f mysqlField) IsNumber() bool { 105 return f.IsInteger() || f.IsFloatingPoint() || f.IsDecimal() 106 } 107 108 // is a numeric integer type 109 func (f mysqlField) IsInteger() bool { 110 switch f.fieldType { 111 case fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeLong, fieldTypeLongLong: 112 return true 113 } 114 return false 115 } 116 117 // is a numeric binary floating point type 118 func (f mysqlField) IsFloatingPoint() bool { 119 switch f.fieldType { 120 case fieldTypeFloat, fieldTypeDouble: 121 return true 122 } 123 return false 124 } 125 126 // is a numeric decimal type 127 func (f mysqlField) IsDecimal() bool { 128 switch f.fieldType { 129 case fieldTypeDecimal, fieldTypeNewDecimal: 130 return true 131 } 132 return false 133 } 134 135 func (f mysqlField) Length() uint32 { 136 return f.length 137 } 138 139 // is a blob type 140 func (f mysqlField) IsBlob() bool { 141 switch f.fieldType { 142 case fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeBLOB, fieldTypeLongBLOB: 143 return true 144 } 145 return false 146 } 147 148 // is a textual type 149 func (f mysqlField) IsText() bool { 150 switch f.fieldType { 151 case fieldTypeVarChar, fieldTypeVarString, fieldTypeString: 152 return true 153 } 154 return false 155 } 156 157 // is a temporal type 158 func (f mysqlField) IsTime() bool { 159 switch f.fieldType { 160 case fieldTypeYear, fieldTypeDate, fieldTypeNewDate, fieldTypeTime, fieldTypeTimestamp, fieldTypeDateTime: 161 return true 162 } 163 return false 164 } 165 166 // type name in MySQL (includes "NULL", which may not be used in table definitions) 167 func (f mysqlField) MysqlType() string { 168 return mysqlNameFor(f.fieldType) 169 } 170 171 // is part of the primary key 172 func (f mysqlField) IsPrimaryKey() bool { 173 return f.flags&flagPriKey == flagPriKey 174 } 175 176 // is part of a unique key 177 func (f mysqlField) IsUniqueKey() bool { 178 return f.flags&flagUniqueKey == flagUniqueKey 179 } 180 181 // is part of a nonunique key 182 func (f mysqlField) IsMultipleKey() bool { 183 return f.flags&flagMultipleKey == flagMultipleKey 184 } 185 186 // has NOT NULL attribute set 187 func (f mysqlField) IsNotNull() bool { 188 return f.flags&flagNotNULL == flagNotNULL 189 } 190 191 // has UNSIGNED attribute set 192 func (f mysqlField) IsUnsigned() bool { 193 return f.flags&flagUnsigned == flagUnsigned 194 } 195 196 // has ZEROFILL attribute set 197 func (f mysqlField) IsZerofill() bool { 198 return f.flags&flagZeroFill == flagZeroFill 199 } 200 201 // has BINARY attribute set 202 func (f mysqlField) IsBinary() bool { 203 return f.flags&flagBinary == flagBinary 204 } 205 206 // has AUTO_INCREMENT attribute set 207 func (f mysqlField) IsAutoIncrement() bool { 208 return f.flags&flagAutoIncrement == flagAutoIncrement 209 } 210 211 func (f mysqlField) Decimals() int { 212 return int(f.decimals) 213 } 214 215 const ( // base for reflection 216 reflect_uint8 = uint8(0) 217 reflect_uint16 = uint16(0) 218 reflect_uint32 = uint32(0) 219 reflect_uint64 = uint64(0) 220 reflect_int8 = int8(0) 221 reflect_int16 = int16(0) 222 reflect_int32 = int32(0) 223 reflect_int64 = int64(0) 224 reflect_float32 = float32(0) 225 reflect_float64 = float64(0) 226 reflect_string = "" 227 // possible indicators for NULL, SET, ENUM, GEOMETRY? 228 // reflect_empty = struct{}{} 229 // reflect_many = []interface{}{} 230 ) 231 232 var ( // reflect.Types 233 // non-null types 234 typeUint8 = reflect.TypeOf(reflect_uint8) 235 typeUint16 = reflect.TypeOf(reflect_uint16) 236 typeUint32 = reflect.TypeOf(reflect_uint32) 237 typeUint64 = reflect.TypeOf(reflect_uint64) 238 typeInt8 = reflect.TypeOf(reflect_int8) 239 typeInt16 = reflect.TypeOf(reflect_int16) 240 typeInt32 = reflect.TypeOf(reflect_int32) 241 typeInt64 = reflect.TypeOf(reflect_int64) 242 typeFloat32 = reflect.TypeOf(reflect_float32) 243 typeFloat64 = reflect.TypeOf(reflect_float64) 244 typeString = reflect.TypeOf(reflect_string) 245 typeBigint = reflect.TypeOf(big.NewInt(0)) 246 typeBools = reflect.TypeOf([]bool{}) 247 typeBytes = reflect.TypeOf([]byte{}) 248 typeTime = reflect.TypeOf(time.Time{}) 249 // nullable types 250 typeNullInt64 = reflect.TypeOf(sql.NullInt64{}) 251 typeNullFloat64 = reflect.TypeOf(sql.NullFloat64{}) 252 typeNullString = reflect.TypeOf(sql.NullString{}) 253 typeNullTime = reflect.TypeOf(mysql.NullTime{}) 254 // typeNullBool doesn't match in MySQL, boolean is (unsigned?) tinyint(1), 255 // it may have more than 2 states 256 //typeNullBool = reflect.TypeOf(sql.NullBool{}) 257 ) 258 259 // retrieve the best matching reflect.Type for the mysql field. 260 // Returns an error if no matching type exists. 261 func (f mysqlField) ReflectGoType() (reflect.Type, error) { 262 if f.IsUnsigned() { 263 switch f.fieldType { 264 case fieldTypeTiny: 265 return typeUint8, nil 266 case fieldTypeShort: 267 return typeUint16, nil 268 case fieldTypeInt24, fieldTypeLong: 269 return typeUint32, nil 270 case fieldTypeLongLong: 271 return typeUint64, nil 272 } 273 // unsigned non-integer types fall through 274 } 275 switch f.fieldType { 276 case fieldTypeTiny: 277 return typeInt8, nil 278 case fieldTypeShort: 279 return typeInt16, nil 280 case fieldTypeInt24, fieldTypeLong: 281 return typeInt32, nil 282 case fieldTypeLongLong: 283 return typeInt64, nil 284 case fieldTypeFloat: 285 return typeFloat32, nil 286 case fieldTypeDouble: 287 return typeFloat64, nil 288 case fieldTypeDecimal, fieldTypeNewDecimal: 289 return typeBigint, nil 290 case fieldTypeYear, fieldTypeDate, fieldTypeNewDate, fieldTypeTime, fieldTypeTimestamp, fieldTypeDateTime: 291 return typeTime, nil 292 case fieldTypeBit: 293 return typeBools, nil 294 case fieldTypeVarChar, fieldTypeVarString, fieldTypeString: 295 return typeString, nil 296 case fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeBLOB, fieldTypeLongBLOB, 297 fieldTypeJSON: 298 return typeBytes, nil 299 case fieldTypeEnum, fieldTypeSet, fieldTypeGeometry, fieldTypeNULL: 300 return nil, errorTypeMismatch(f.fieldType) 301 } 302 return nil, errors.New("unknown mysql type") 303 } 304 305 // retrieve the best matching reflect.Type for the mysql field. 306 // Returns an error if no matching type exists. 307 func (f mysqlField) ReflectSqlType(forceNullable bool) (reflect.Type, error) { 308 if forceNullable || !f.IsNotNull() { 309 switch { 310 case f.IsInteger(): 311 return typeNullInt64, nil 312 case f.IsFloatingPoint(): 313 return typeNullFloat64, nil 314 case f.IsText(): 315 return typeNullString, nil 316 case f.IsTime(): 317 return typeNullTime, nil 318 case f.IsBlob(): 319 return typeBytes, nil // []byte can be nil on its own 320 } 321 // All other types are not nullable in Go right now 322 return nil, errorTypeMismatch(f.fieldType) 323 } 324 return f.ReflectGoType() 325 } 326 327 type errorTypeMismatch uint8 328 329 func (e errorTypeMismatch) Error() string { 330 return "no matching go type for " + mysqlNameFor(fieldType(e)) 331 } 332 333 func mysqlNameFor(fieldType fieldType) string { 334 switch fieldType { 335 // --- integer --- 336 case fieldTypeTiny: 337 return "TINYINT" 338 case fieldTypeShort: 339 return "SMALLINT" 340 case fieldTypeInt24, fieldTypeLong: 341 return "INT" 342 case fieldTypeLongLong: 343 return "BIGINT" 344 // --- floating point --- 345 case fieldTypeFloat: 346 return "FLOAT" 347 case fieldTypeDouble: 348 return "DOUBLE" 349 // --- decimal --- 350 case fieldTypeDecimal, fieldTypeNewDecimal: 351 return "DECIMAL" 352 // --- date & time --- 353 case fieldTypeYear: 354 return "YEAR" 355 case fieldTypeDate, fieldTypeNewDate: 356 return "DATE" 357 case fieldTypeTime: 358 return "TIME" 359 case fieldTypeTimestamp: 360 return "TIMESTAMP" 361 case fieldTypeDateTime: 362 return "DATETIME" 363 // --- null --- 364 case fieldTypeNULL: 365 return "NULL" 366 // --- bit --- 367 case fieldTypeBit: 368 return "BIT" 369 // --- string --- 370 case fieldTypeVarChar, fieldTypeVarString: 371 return "VARCHAR" 372 case fieldTypeString: 373 return "CHAR" 374 // --- enum --- 375 case fieldTypeEnum: 376 return "ENUM" 377 // --- set --- 378 case fieldTypeSet: 379 return "SET" 380 // --- blob --- 381 case fieldTypeTinyBLOB: 382 return "TINY BLOB" 383 case fieldTypeMediumBLOB: 384 return "MEDIUM BLOB" 385 case fieldTypeBLOB: 386 return "BLOB" 387 case fieldTypeLongBLOB: 388 return "LONG BLOB" 389 // --- geometry --- 390 case fieldTypeGeometry: 391 return "GEOMETRY" 392 // --- JSON --- 393 case fieldTypeJSON: 394 return "JSON" 395 } 396 return "" 397 } 398 399 type parameterType uint 400 401 const ( 402 // unknown type, no information about parameter requirements 403 ParamUnknown parameterType = iota 404 // requires no parameters in MySQL declaration 405 ParamNone 406 // requires no parameters or length (int > 0) in MySQL declaration 407 ParamMayLength 408 // requires length (int > 0) in MySQL declaration 409 ParamMustLength 410 // requires no parameters or length (int > 0) and decimals (int >= 0) in MySQL declaration 411 // OBSOLETE since decimals are contained in mysqlField... 412 // ParamMayLengthAndDecimals 413 _ 414 // requires no parameters or length (int > 0) or length and decimals (int >= 0) in MySQL declaration 415 // OBSOLETE since decimals are contained in mysqlField... 416 // ParamMayLengthMayDecimals 417 _ 418 // requires valid values as parameters in MySQL declaration 419 ParamValues 420 ) 421 422 // retrieve information about parameters used in MysqlDeclaration 423 func (f mysqlField) MysqlParameters() parameterType { 424 switch f.fieldType { 425 case // date types, *BLOB and GEOMETRY declarations have no parameters 426 fieldTypeYear, fieldTypeDate, fieldTypeNewDate, 427 fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeBLOB, fieldTypeLongBLOB, 428 fieldTypeGeometry, fieldTypeJSON, 429 // time types use decimals: microseconds 430 fieldTypeTime, fieldTypeTimestamp, fieldTypeDateTime: 431 return ParamNone 432 case // BIT, *INT* and CHAR declarations have one optional parameter (length) 433 fieldTypeBit, 434 fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeLong, fieldTypeLongLong, 435 fieldTypeString, 436 // DECIMAL and NUMERIC declarations have one optional parameter (length) and may use decimals 437 fieldTypeDecimal, fieldTypeNewDecimal, 438 // REAL, FLOAT and DOUBLE declarations have one optional parameter (length, will also use decimals when length is given) 439 fieldTypeFloat, fieldTypeDouble: 440 return ParamMayLength 441 case // VARCHAR and VARBINARY declarations have one mandatory parameter (length) 442 fieldTypeVarChar, fieldTypeVarString: 443 return ParamMustLength 444 case // ENUM and SET declarations have multiple parameters 445 fieldTypeEnum, fieldTypeSet: 446 return ParamValues 447 } 448 return ParamUnknown 449 } 450 451 type paramErr string 452 453 func (p paramErr) Error() string { 454 return string(p) 455 } 456 457 // mysql type declaration 458 // The declaration includes the type and size and the attributes "NOT NULL", "ZEROFILL" and "BINARY". 459 // It does not include the name, character sets, collations, default value, keys or the attribute auto_increment. 460 // For BIT, all INT types, CHAR and BINARY types, args is optional and may be one int: length. 461 // For VARCHAR and VARBINARY types, args must be one int: length. 462 // For DECIMAL and NUMERIC types, it may be none or one int: length. 463 // For DATETIME, TIME, TIMESTAMP, decimals is used for microseconds. 464 // For FLOAT, DOUBLE and REAL floating point types, it is optional and, when given, must be two ints: length and decimals. 465 // For SETs and ENUMs, it specifies the possible values. 466 // For all other types, args must be empty. 467 func (f mysqlField) MysqlDeclaration(args ...interface{}) (string, error) { 468 const ( 469 unsigned = " UNSIGNED" 470 notNull = " NOT NULL" 471 zerofill = " ZEROFILL" 472 binary = " BINARY" 473 // errors 474 errNil = paramErr("can't create declaration for NULL") 475 errUnknown = paramErr("parameter error, unknown") 476 errNone = paramErr("parameter error, must be none") 477 errMayLength = paramErr("parameter error, must be none or one int (length)") 478 errMustLength = paramErr("parameter error, must be one int (length)") 479 errEnumOrSet = paramErr("parameter error, must be at least one entry") 480 ) 481 // fail fast if we can't provide a declaration 482 if f.fieldType == fieldTypeNULL { 483 return "", errNil 484 } 485 var param, us, nn, zf, bin string 486 if f.IsNotNull() { 487 // any type may be "NOT NULL" 488 nn = notNull 489 } 490 switch f.fieldType { 491 case fieldTypeFloat, fieldTypeDouble, 492 fieldTypeDecimal, fieldTypeNewDecimal: 493 if len(args) == 1 { 494 param = fmt.Sprintf("(%d,%d)", args[0], f.decimals) 495 } 496 fallthrough 497 case // numeric types may be unsigned or zerofill 498 fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeLong, fieldTypeLongLong: 499 if f.IsUnsigned() { 500 us = unsigned 501 } 502 if f.IsZerofill() { 503 zf = zerofill 504 } 505 case fieldTypeBit: 506 if len(args) != 1 { 507 return "", errMustLength 508 } 509 param = fmt.Sprintf("(%d)", args[0]) 510 case fieldTypeYear, fieldTypeDate, fieldTypeNewDate, 511 fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeBLOB, fieldTypeLongBLOB, 512 fieldTypeGeometry, fieldTypeJSON: 513 // nothing to be done for these types 514 case // only string types may be binary 515 fieldTypeVarChar, fieldTypeVarString: 516 if f.IsBinary() { 517 bin = binary 518 } 519 if len(args) != 1 { 520 return "", errMustLength 521 } 522 param = fmt.Sprintf("(%d)", args[0]) 523 case fieldTypeString: 524 if f.IsBinary() { 525 bin = binary 526 } 527 if len(args) == 1 { 528 param = fmt.Sprintf("(%d)", args[0]) 529 } 530 case fieldTypeTime, fieldTypeTimestamp, fieldTypeDateTime: 531 if f.decimals > 0 { 532 param = fmt.Sprintf("(%d)", f.decimals) 533 } 534 535 case fieldTypeEnum, fieldTypeSet: 536 if len(args) == 0 { 537 return "", errEnumOrSet 538 } 539 param = fmt.Sprintf("(%v)", args...) 540 default: 541 return "", errUnknown 542 } 543 return mysqlNameFor(f.fieldType) + param + bin + us + zf + nn, nil 544 }