| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package mysql
- import (
- "bytes"
- "database/sql"
- "fmt"
- "reflect"
- "strconv"
- "strings"
- "time"
- "yunion.io/x/log"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/tristate"
- "yunion.io/x/pkg/utils"
- "yunion.io/x/sqlchemy"
- )
- func columnDefinitionBuffer(c sqlchemy.IColumnSpec) bytes.Buffer {
- var buf bytes.Buffer
- buf.WriteByte('`')
- buf.WriteString(c.Name())
- buf.WriteByte('`')
- buf.WriteByte(' ')
- buf.WriteString(c.ColType())
- extra := c.ExtraDefs()
- if len(extra) > 0 {
- buf.WriteString(" ")
- buf.WriteString(extra)
- }
- if !c.IsNullable() {
- buf.WriteString(" NOT NULL")
- }
- def := c.Default()
- defOk := c.IsSupportDefault()
- if def != "" {
- if !defOk {
- panic(fmt.Errorf("column %q type %q does not support having default value: %q",
- c.Name(), c.ColType(), def,
- ))
- }
- def = sqlchemy.GetStringValue(c.ConvertFromString(def))
- buf.WriteString(" DEFAULT ")
- if c.IsText() {
- buf.WriteByte('\'')
- }
- buf.WriteString(def)
- if c.IsText() {
- buf.WriteByte('\'')
- }
- }
- return buf
- }
- // SBooleanColumn represents a boolean type column, which is a int(1) for mysql, with value of true or false
- type SBooleanColumn struct {
- sqlchemy.SBaseWidthColumn
- }
- // DefinitionString implementation of SBooleanColumn for IColumnSpec
- func (c *SBooleanColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // ConvertFromString implementation of SBooleanColumn for IColumnSpec
- func (c *SBooleanColumn) ConvertFromString(str string) interface{} {
- switch sqlchemy.ConvertValueToBool(str) {
- case true:
- return 1
- default:
- return 0
- }
- }
- // ConvertFromValue implementation of STristateColumn for IColumnSpec
- func (c *SBooleanColumn) ConvertFromValue(val interface{}) interface{} {
- switch sqlchemy.ConvertValueToBool(val) {
- case true:
- return 1
- default:
- return 0
- }
- }
- // IsZero implementation of SBooleanColumn for IColumnSpec
- func (c *SBooleanColumn) IsZero(val interface{}) bool {
- if c.IsPointer() {
- bVal := val.(*bool)
- return bVal == nil
- }
- bVal := val.(bool)
- return bVal == false
- }
- // NewBooleanColumn return an instance of SBooleanColumn
- func NewBooleanColumn(name string, tagmap map[string]string, isPointer bool) SBooleanColumn {
- bc := SBooleanColumn{SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, "TINYINT", tagmap, isPointer)}
- if !bc.IsPointer() && len(bc.Default()) > 0 && bc.ConvertFromString(bc.Default()) == 1 {
- msg := fmt.Sprintf("Non-pointer boolean column should not default true: %s(%s)", name, tagmap)
- panic(msg)
- }
- return bc
- }
- // STristateColumn represents a tristate type column, with value of true, false or none
- type STristateColumn struct {
- sqlchemy.SBaseWidthColumn
- }
- // DefinitionString implementation of STristateColumn for IColumnSpec
- func (c *STristateColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // ConvertFromString implementation of STristateColumn for IColumnSpec
- func (c *STristateColumn) ConvertFromString(str string) interface{} {
- switch sqlchemy.ConvertValueToTriState(str) {
- case tristate.True:
- return 1
- case tristate.False:
- return 0
- default:
- return sql.NullInt32{}
- }
- }
- // ConvertFromValue implementation of STristateColumn for IColumnSpec
- func (c *STristateColumn) ConvertFromValue(val interface{}) interface{} {
- switch sqlchemy.ConvertValueToTriState(val) {
- case tristate.True:
- return 1
- case tristate.False:
- return 0
- default:
- return sql.NullInt32{}
- }
- }
- // IsZero implementation of STristateColumn for IColumnSpec
- func (c *STristateColumn) IsZero(val interface{}) bool {
- if c.IsPointer() {
- bVal := val.(*tristate.TriState)
- return bVal == nil
- }
- bVal := val.(tristate.TriState)
- return bVal == tristate.None
- }
- // NewTristateColumn return an instance of STristateColumn
- func NewTristateColumn(table, name string, tagmap map[string]string, isPointer bool) STristateColumn {
- if _, ok := tagmap[sqlchemy.TAG_NULLABLE]; ok {
- // simply warning, for backward compatiblity reason
- // tristate always nullable
- // delete(tagmap, sqlchemy.TAG_NULLABLE)
- log.Warningf("%s TristateColumn %s should have no nullable tag", table, name)
- }
- bc := STristateColumn{SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, "TINYINT", tagmap, isPointer)}
- return bc
- }
- // SIntegerColumn represents an integer type of column, with value of integer
- type SIntegerColumn struct {
- sqlchemy.SBaseWidthColumn
- // Is this column an autoincrement colmn
- isAutoIncrement bool
- // Is this column is a version column for this records
- isAutoVersion bool
- // Is this column a unsigned integer?
- isUnsigned bool
- // If this column is an autoincrement column, AutoIncrementOffset records the initial offset
- autoIncrementOffset int64
- }
- // IsNumeric implementation of SIntegerColumn for IColumnSpec
- func (c *SIntegerColumn) IsNumeric() bool {
- return true
- }
- // ExtraDefs implementation of SIntegerColumn for IColumnSpec
- func (c *SIntegerColumn) ExtraDefs() string {
- if c.isAutoIncrement {
- return "AUTO_INCREMENT"
- }
- return ""
- }
- // DefinitionString implementation of SIntegerColumn for IColumnSpec
- func (c *SIntegerColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // IsZero implementation of SIntegerColumn for IColumnSpec
- func (c *SIntegerColumn) IsZero(val interface{}) bool {
- if val == nil || (c.IsPointer() && reflect.ValueOf(val).IsNil()) {
- return true
- }
- switch intVal := val.(type) {
- case int8, int16, int32, int64, int, uint, uint8, uint16, uint32, uint64:
- return intVal == 0
- }
- return true
- }
- func (c *SIntegerColumn) int2int(intval int64) interface{} {
- colType := c.ColType()
- parts := strings.Split(colType, "(")
- switch parts[0] {
- case "TINYINT":
- return int8(intval)
- case "SMALLINT":
- return int16(intval)
- case "INT":
- return int(intval)
- case "BIGINT":
- return int64(intval)
- case "UNSIGNED TINYINT":
- return uint8(intval)
- case "UNSIGNED SMALLINT":
- return uint16(intval)
- case "UNSIGNED INT":
- return uint(intval)
- case "UNSIGNED BIGINT":
- return uint64(intval)
- }
- panic(fmt.Sprintf("unsupported type %s", c.ColType()))
- }
- // ConvertFromString implementation of SBooleanColumn for IColumnSpec
- func (c *SIntegerColumn) ConvertFromString(str string) interface{} {
- intval := sqlchemy.ConvertValueToInteger(str)
- return c.int2int(intval)
- }
- // ColType implementation of SIntegerColumn for IColumnSpec
- func (c *SIntegerColumn) ColType() string {
- str := (&c.SBaseWidthColumn).ColType()
- if c.isUnsigned {
- str += " UNSIGNED"
- }
- return str
- }
- func (c *SIntegerColumn) IsAutoVersion() bool {
- return c.isAutoVersion
- }
- func (c *SIntegerColumn) IsAutoIncrement() bool {
- return c.isAutoIncrement
- }
- func (c *SIntegerColumn) AutoIncrementOffset() int64 {
- return c.autoIncrementOffset
- }
- func (c *SIntegerColumn) SetAutoIncrement(on bool) {
- c.isAutoIncrement = on
- }
- func (c *SIntegerColumn) SetAutoIncrementOffset(offset int64) {
- c.autoIncrementOffset = offset
- }
- // NewIntegerColumn return an instance of SIntegerColumn
- func NewIntegerColumn(name string, sqltype string, unsigned bool, tagmap map[string]string, isPointer bool) SIntegerColumn {
- autoinc := false
- autoincBase := int64(0)
- tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_AUTOINCREMENT)
- if ok {
- base, err := strconv.ParseInt(v, 10, 64)
- if err == nil && base > 0 {
- autoinc = true
- autoincBase = base
- } else {
- autoinc = utils.ToBool(v)
- }
- }
- autover := false
- tagmap, v, ok = utils.TagPop(tagmap, sqlchemy.TAG_AUTOVERSION)
- if ok {
- autover = utils.ToBool(v)
- }
- c := SIntegerColumn{
- SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, sqltype, tagmap, isPointer),
- isAutoIncrement: autoinc,
- autoIncrementOffset: autoincBase,
- isAutoVersion: autover,
- isUnsigned: unsigned,
- }
- if autoinc {
- c.SetPrimary(true) // autoincrement column must be primary key
- c.SetNullable(false)
- c.isAutoVersion = false
- } else if autover {
- c.SetPrimary(false)
- c.SetNullable(false)
- if len(c.Default()) == 0 {
- c.SetDefault("0")
- }
- }
- return c
- }
- // SFloatColumn represents a float type column, e.g. float32 or float64
- type SFloatColumn struct {
- sqlchemy.SBaseColumn
- }
- // IsNumeric implementation of SFloatColumn for IColumnSpec
- func (c *SFloatColumn) IsNumeric() bool {
- return true
- }
- // DefinitionString implementation of SFloatColumn for IColumnSpec
- func (c *SFloatColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // IsZero implementation of SFloatColumn for IColumnSpec
- func (c *SFloatColumn) IsZero(val interface{}) bool {
- if c.IsPointer() {
- switch val.(type) {
- case *float32:
- return val.(*float32) == nil
- case *float64:
- return val.(*float64) == nil
- }
- } else {
- switch val.(type) {
- case float32:
- return val.(float32) == 0.0
- case float64:
- return val.(float64) == 0.0
- }
- }
- return true
- }
- // ConvertFromString implementation of SBooleanColumn for IColumnSpec
- func (c *SFloatColumn) ConvertFromString(str string) interface{} {
- return sqlchemy.ConvertValueToFloat(str)
- }
- // NewFloatColumn returns an instance of SFloatColumn
- func NewFloatColumn(name string, sqlType string, tagmap map[string]string, isPointer bool) SFloatColumn {
- return SFloatColumn{SBaseColumn: sqlchemy.NewBaseColumn(name, sqlType, tagmap, isPointer)}
- }
- // SDecimalColumn represents a DECIMAL type of column, i.e. a float with fixed width of digits
- type SDecimalColumn struct {
- sqlchemy.SBaseWidthColumn
- Precision int
- }
- // ColType implementation of SDecimalColumn for IColumnSpec
- func (c *SDecimalColumn) ColType() string {
- str := c.SBaseWidthColumn.ColType()
- if str[len(str)-1] == ')' {
- str = str[:len(str)-1]
- }
- return fmt.Sprintf("%s, %d)", str, c.Precision)
- }
- // IsNumeric implementation of SDecimalColumn for IColumnSpec
- func (c *SDecimalColumn) IsNumeric() bool {
- return true
- }
- // DefinitionString implementation of SDecimalColumn for IColumnSpec
- func (c *SDecimalColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // IsZero implementation of SDecimalColumn for IColumnSpec
- func (c *SDecimalColumn) IsZero(val interface{}) bool {
- if c.IsPointer() {
- switch val.(type) {
- case *float32:
- return val.(*float32) == nil
- case *float64:
- return val.(*float64) == nil
- }
- } else {
- switch val.(type) {
- case float32:
- return val.(float32) == 0.0
- case float64:
- return val.(float64) == 0.0
- }
- }
- return true
- }
- // ConvertFromString implementation of SBooleanColumn for IColumnSpec
- func (c *SDecimalColumn) ConvertFromString(str string) interface{} {
- return sqlchemy.ConvertValueToFloat(str)
- }
- // NewDecimalColumn returns an instance of SDecimalColumn
- func NewDecimalColumn(name string, tagmap map[string]string, isPointer bool) SDecimalColumn {
- tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_PRECISION)
- if !ok {
- panic(fmt.Sprintf("Field %q of float misses precision tag", name))
- }
- prec, err := strconv.Atoi(v)
- if err != nil {
- panic(fmt.Sprintf("Field precision of %q shoud be integer (%q)", name, v))
- }
- return SDecimalColumn{
- SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, "DECIMAL", tagmap, isPointer),
- Precision: prec,
- }
- }
- // STextColumn represents a text type of column
- type STextColumn struct {
- sqlchemy.SBaseWidthColumn
- Charset string
- }
- // IsSupportDefault implementation of STextColumn for IColumnSpec
- func (c *STextColumn) IsSupportDefault() bool {
- // https://stackoverflow.com/questions/3466872/why-cant-a-text-column-have-a-default-value-in-mysql
- // MySQL does not support default for TEXT/BLOB
- if c.SBaseColumn.ColType() == "VARCHAR" {
- return true
- }
- return false
- }
- // ColType implementation of STextColumn for IColumnSpec
- func (c *STextColumn) ColType() string {
- var charset string
- var collate string
- switch c.Charset {
- case "ascii":
- charset = "ascii"
- collate = "ascii_general_ci"
- case "utf8":
- charset = "utf8mb4"
- collate = "utf8mb4_unicode_ci"
- }
- return fmt.Sprintf("%s CHARACTER SET '%s' COLLATE '%s'", c.SBaseWidthColumn.ColType(), charset, collate)
- }
- // IsText implementation of STextColumn for IColumnSpec
- func (c *STextColumn) IsText() bool {
- return true
- }
- // IsSearchable implementation of STextColumn for IColumnSpec
- func (c *STextColumn) IsSearchable() bool {
- return true
- }
- // IsAscii implementation of STextColumn for IColumnSpec
- func (c *STextColumn) IsAscii() bool {
- if c.Charset == "ascii" {
- return true
- }
- return false
- }
- // DefinitionString implementation of STextColumn for IColumnSpec
- func (c *STextColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // IsZero implementation of STextColumn for IColumnSpec
- func (c *STextColumn) IsZero(val interface{}) bool {
- if c.IsPointer() {
- return gotypes.IsNil(val)
- }
- return reflect.ValueOf(val).Len() == 0
- }
- // ConvertFromString implementation of SBooleanColumn for IColumnSpec
- func (c *STextColumn) ConvertFromString(str string) interface{} {
- return str
- }
- func (c *STextColumn) IsString() bool {
- return true
- }
- // NewTextColumn return an instance of STextColumn
- func NewTextColumn(name string, sqlType string, tagmap map[string]string, isPointer bool) STextColumn {
- tagmap, charset, _ := utils.TagPop(tagmap, sqlchemy.TAG_CHARSET)
- if len(charset) == 0 {
- charset = "utf8"
- } else if charset != "utf8" && charset != "ascii" {
- panic(fmt.Sprintf("Unsupported charset %s for %s", charset, name))
- }
- return STextColumn{
- SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, sqlType, tagmap, isPointer),
- Charset: charset,
- }
- }
- // STimeTypeColumn represents a Detetime type of column, e.g. DateTime
- type STimeTypeColumn struct {
- sqlchemy.SBaseColumn
- }
- // IsText implementation of STimeTypeColumn for IColumnSpec
- func (c *STimeTypeColumn) IsText() bool {
- return true
- }
- // DefinitionString implementation of STimeTypeColumn for IColumnSpec
- func (c *STimeTypeColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // IsZero implementation of STimeTypeColumn for IColumnSpec
- func (c *STimeTypeColumn) IsZero(val interface{}) bool {
- if c.IsPointer() {
- bVal := val.(*time.Time)
- return bVal == nil
- }
- bVal := val.(time.Time)
- return bVal.IsZero()
- }
- // ConvertFromString implementation of SBooleanColumn for IColumnSpec
- func (c *STimeTypeColumn) ConvertFromString(str string) interface{} {
- return sqlchemy.ConvertValueToTime(str)
- }
- // ConvertFromValue implementation of STimeTypeColumn for IColumnSpec
- func (c *STimeTypeColumn) ConvertFromValue(val interface{}) interface{} {
- return sqlchemy.ConvertValueToTime(val)
- }
- // NewTimeTypeColumn return an instance of STimeTypeColumn
- func NewTimeTypeColumn(name string, typeStr string, tagmap map[string]string, isPointer bool) STimeTypeColumn {
- dc := STimeTypeColumn{
- sqlchemy.NewBaseColumn(name, typeStr, tagmap, isPointer),
- }
- return dc
- }
- // SDateTimeColumn represents a DateTime type of column
- type SDateTimeColumn struct {
- STimeTypeColumn
- // Is this column a 'created_at' field, whichi records the time of create this record
- isCreatedAt bool
- // Is this column a 'updated_at' field, whichi records the time when this record was updated
- isUpdatedAt bool
- }
- // DefinitionString implementation of SDateTimeColumn for IColumnSpec
- func (c *SDateTimeColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- func (c *SDateTimeColumn) IsCreatedAt() bool {
- return c.isCreatedAt
- }
- func (c *SDateTimeColumn) IsUpdatedAt() bool {
- return c.isUpdatedAt
- }
- func (c *SDateTimeColumn) IsDateTime() bool {
- return true
- }
- // NewDateTimeColumn returns an instance of DateTime column
- func NewDateTimeColumn(name string, tagmap map[string]string, isPointer bool) SDateTimeColumn {
- createdAt := false
- updatedAt := false
- tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_CREATE_TIMESTAMP)
- if ok {
- createdAt = utils.ToBool(v)
- }
- tagmap, v, ok = utils.TagPop(tagmap, sqlchemy.TAG_UPDATE_TIMESTAMP)
- if ok {
- updatedAt = utils.ToBool(v)
- }
- dtc := SDateTimeColumn{
- NewTimeTypeColumn(name, "DATETIME", tagmap, isPointer),
- createdAt, updatedAt,
- }
- return dtc
- }
- // CompoundColumn represents a column of compound tye, e.g. a JSON, an Array, or a struct
- type CompoundColumn struct {
- STextColumn
- sqlchemy.SBaseCompoundColumn
- }
- // DefinitionString implementation of CompoundColumn for IColumnSpec
- func (c *CompoundColumn) DefinitionString() string {
- buf := columnDefinitionBuffer(c)
- return buf.String()
- }
- // IsZero implementation of CompoundColumn for IColumnSpec
- func (c *CompoundColumn) IsZero(val interface{}) bool {
- if val == nil {
- return true
- }
- if c.IsPointer() && reflect.ValueOf(val).IsNil() {
- return true
- }
- return false
- }
- // ConvertFromString implementation of CompoundColumn for IColumnSpec
- func (c *CompoundColumn) ConvertFromString(str string) interface{} {
- return c.SBaseCompoundColumn.ConvertFromString(str)
- }
- // ConvertFromValue implementation of CompoundColumn for IColumnSpec
- func (c *CompoundColumn) ConvertFromValue(val interface{}) interface{} {
- return c.SBaseCompoundColumn.ConvertFromValue(val)
- }
- // NewCompoundColumn returns an instance of CompoundColumn
- func NewCompoundColumn(name string, sqlType string, tagmap map[string]string, isPointer bool) CompoundColumn {
- dtc := CompoundColumn{STextColumn: NewTextColumn(name, sqlType, tagmap, isPointer)}
- return dtc
- }
|