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  }