column.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package dameng
  15. import (
  16. "bytes"
  17. "database/sql"
  18. "fmt"
  19. "reflect"
  20. "strconv"
  21. "time"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/gotypes"
  24. "yunion.io/x/pkg/tristate"
  25. "yunion.io/x/pkg/utils"
  26. "yunion.io/x/sqlchemy"
  27. )
  28. func columnDefinitionBuffer(c sqlchemy.IColumnSpec) bytes.Buffer {
  29. var buf bytes.Buffer
  30. buf.WriteByte('"')
  31. buf.WriteString(c.Name())
  32. buf.WriteByte('"')
  33. buf.WriteByte(' ')
  34. buf.WriteString(c.ColType())
  35. extra := c.ExtraDefs()
  36. if len(extra) > 0 {
  37. buf.WriteString(" ")
  38. buf.WriteString(extra)
  39. }
  40. def := c.Default()
  41. if hasDefault(c) {
  42. def = sqlchemy.GetStringValue(c.ConvertFromString(def))
  43. buf.WriteString(" DEFAULT ")
  44. if c.IsText() {
  45. buf.WriteByte('\'')
  46. }
  47. buf.WriteString(def)
  48. if c.IsText() {
  49. buf.WriteByte('\'')
  50. }
  51. }
  52. if !c.IsNullable() {
  53. buf.WriteString(" NOT NULL")
  54. }
  55. return buf
  56. }
  57. func hasDefault(c sqlchemy.IColumnSpec) bool {
  58. // if c is NOT NULL, default value must be set
  59. // if datetime, empty default value is allowed for not null
  60. return (c.Default() != "" || (!c.IsNullable() && !c.IsDateTime())) && !c.IsAutoIncrement()
  61. }
  62. // SBooleanColumn represents a boolean type column, which is a int(1) for mysql, with value of true or false
  63. type SBooleanColumn struct {
  64. sqlchemy.SBaseWidthColumn
  65. }
  66. // DefinitionString implementation of SBooleanColumn for IColumnSpec
  67. func (c *SBooleanColumn) DefinitionString() string {
  68. buf := columnDefinitionBuffer(c)
  69. return buf.String()
  70. }
  71. // ConvertFromString implementation of SBooleanColumn for IColumnSpec
  72. func (c *SBooleanColumn) ConvertFromString(str string) interface{} {
  73. switch sqlchemy.ConvertValueToBool(str) {
  74. case true:
  75. return 1
  76. default:
  77. return 0
  78. }
  79. }
  80. // ConvertFromValue implementation of STristateColumn for IColumnSpec
  81. func (c *SBooleanColumn) ConvertFromValue(val interface{}) interface{} {
  82. switch sqlchemy.ConvertValueToBool(val) {
  83. case true:
  84. return 1
  85. default:
  86. return 0
  87. }
  88. }
  89. // IsZero implementation of SBooleanColumn for IColumnSpec
  90. func (c *SBooleanColumn) IsZero(val interface{}) bool {
  91. if c.IsPointer() {
  92. bVal := val.(*bool)
  93. return bVal == nil
  94. }
  95. bVal := val.(bool)
  96. return bVal == false
  97. }
  98. // NewBooleanColumn return an instance of SBooleanColumn
  99. func NewBooleanColumn(name string, tagmap map[string]string, isPointer bool) SBooleanColumn {
  100. bc := SBooleanColumn{SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, "TINYINT", tagmap, isPointer)}
  101. if !bc.IsPointer() && len(bc.Default()) > 0 && bc.ConvertFromString(bc.Default()) == 1 {
  102. msg := fmt.Sprintf("Non-pointer boolean column should not default true: %s(%s)", name, tagmap)
  103. panic(msg)
  104. }
  105. return bc
  106. }
  107. // STristateColumn represents a tristate type column, with value of true, false or none
  108. type STristateColumn struct {
  109. sqlchemy.SBaseWidthColumn
  110. }
  111. // DefinitionString implementation of STristateColumn for IColumnSpec
  112. func (c *STristateColumn) DefinitionString() string {
  113. buf := columnDefinitionBuffer(c)
  114. return buf.String()
  115. }
  116. // ConvertFromString implementation of STristateColumn for IColumnSpec
  117. func (c *STristateColumn) ConvertFromString(str string) interface{} {
  118. switch sqlchemy.ConvertValueToTriState(str) {
  119. case tristate.True:
  120. return 1
  121. case tristate.False:
  122. return 0
  123. default:
  124. return sql.NullInt32{}
  125. }
  126. }
  127. // ConvertFromValue implementation of STristateColumn for IColumnSpec
  128. func (c *STristateColumn) ConvertFromValue(val interface{}) interface{} {
  129. switch sqlchemy.ConvertValueToTriState(val) {
  130. case tristate.True:
  131. return 1
  132. case tristate.False:
  133. return 0
  134. default:
  135. return sql.NullInt32{}
  136. }
  137. }
  138. // IsZero implementation of STristateColumn for IColumnSpec
  139. func (c *STristateColumn) IsZero(val interface{}) bool {
  140. if c.IsPointer() {
  141. bVal := val.(*tristate.TriState)
  142. return bVal == nil
  143. }
  144. bVal := val.(tristate.TriState)
  145. return bVal == tristate.None
  146. }
  147. // NewTristateColumn return an instance of STristateColumn
  148. func NewTristateColumn(table, name string, tagmap map[string]string, isPointer bool) STristateColumn {
  149. if _, ok := tagmap[sqlchemy.TAG_NULLABLE]; ok {
  150. // simply warning, for backward compatiblity reason
  151. // tristate always nullable
  152. // delete(tagmap, sqlchemy.TAG_NULLABLE)
  153. log.Warningf("%s TristateColumn %s should have no nullable tag", table, name)
  154. }
  155. bc := STristateColumn{SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, "TINYINT", tagmap, isPointer)}
  156. return bc
  157. }
  158. // SIntegerColumn represents an integer type of column, with value of integer
  159. type SIntegerColumn struct {
  160. sqlchemy.SBaseColumn
  161. // Is this column an autoincrement colmn
  162. isAutoIncrement bool
  163. // Is this column is a version column for this records
  164. isAutoVersion bool
  165. // Is this column a unsigned integer?
  166. // isUnsigned bool
  167. // If this column is an autoincrement column, AutoIncrementOffset records the initial offset
  168. autoIncrementOffset int64
  169. }
  170. // IsNumeric implementation of SIntegerColumn for IColumnSpec
  171. func (c *SIntegerColumn) IsNumeric() bool {
  172. return true
  173. }
  174. // ExtraDefs implementation of SIntegerColumn for IColumnSpec
  175. func (c *SIntegerColumn) ExtraDefs() string {
  176. if c.isAutoIncrement {
  177. return fmt.Sprintf("IDENTITY(%d, %d)", c.autoIncrementOffset, 1)
  178. }
  179. return ""
  180. }
  181. // DefinitionString implementation of SIntegerColumn for IColumnSpec
  182. func (c *SIntegerColumn) DefinitionString() string {
  183. buf := columnDefinitionBuffer(c)
  184. return buf.String()
  185. }
  186. // IsZero implementation of SIntegerColumn for IColumnSpec
  187. func (c *SIntegerColumn) IsZero(val interface{}) bool {
  188. if val == nil || (c.IsPointer() && reflect.ValueOf(val).IsNil()) {
  189. return true
  190. }
  191. switch intVal := val.(type) {
  192. case int8, int16, int32, int64, int, uint, uint8, uint16, uint32, uint64:
  193. return intVal == 0
  194. }
  195. return true
  196. }
  197. // ConvertFromString implementation of SBooleanColumn for IColumnSpec
  198. func (c *SIntegerColumn) ConvertFromString(str string) interface{} {
  199. intval := sqlchemy.ConvertValueToInteger(str)
  200. switch c.ColType() {
  201. case "TINYINT":
  202. return int8(intval)
  203. case "SMALLINT":
  204. return int16(intval)
  205. case "INT":
  206. return int(intval)
  207. case "BIGINT":
  208. return int64(intval)
  209. }
  210. panic(fmt.Sprintf("unsupported type %s", c.ColType()))
  211. }
  212. func (c *SIntegerColumn) IsAutoVersion() bool {
  213. return c.isAutoVersion
  214. }
  215. func (c *SIntegerColumn) IsAutoIncrement() bool {
  216. return c.isAutoIncrement
  217. }
  218. func (c *SIntegerColumn) AutoIncrementOffset() int64 {
  219. return c.autoIncrementOffset
  220. }
  221. func (c *SIntegerColumn) SetAutoIncrement(on bool) {
  222. c.isAutoIncrement = on
  223. }
  224. func (c *SIntegerColumn) SetAutoIncrementOffset(offset int64) {
  225. c.autoIncrementOffset = offset
  226. }
  227. // NewIntegerColumn return an instance of SIntegerColumn
  228. func NewIntegerColumn(name string, sqltype string, tagmap map[string]string, isPointer bool) SIntegerColumn {
  229. autoinc := false
  230. autoincBase := int64(1)
  231. tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_AUTOINCREMENT)
  232. if ok {
  233. base, err := strconv.ParseInt(v, 10, 64)
  234. if err == nil && base > 0 {
  235. autoinc = true
  236. autoincBase = base
  237. } else {
  238. autoinc = utils.ToBool(v)
  239. }
  240. }
  241. autover := false
  242. tagmap, v, ok = utils.TagPop(tagmap, sqlchemy.TAG_AUTOVERSION)
  243. if ok {
  244. autover = utils.ToBool(v)
  245. }
  246. c := SIntegerColumn{
  247. SBaseColumn: sqlchemy.NewBaseColumn(name, sqltype, tagmap, isPointer),
  248. isAutoIncrement: autoinc,
  249. autoIncrementOffset: autoincBase,
  250. isAutoVersion: autover,
  251. }
  252. if autoinc {
  253. c.SetPrimary(true) // autoincrement column must be primary key
  254. c.SetNullable(false)
  255. c.isAutoVersion = false
  256. } else if autover {
  257. c.SetPrimary(false)
  258. c.SetNullable(false)
  259. if len(c.Default()) == 0 {
  260. c.SetDefault("0")
  261. }
  262. }
  263. return c
  264. }
  265. // SFloatColumn represents a float type column, e.g. float32 or float64
  266. type SFloatColumn struct {
  267. sqlchemy.SBaseColumn
  268. }
  269. // IsNumeric implementation of SFloatColumn for IColumnSpec
  270. func (c *SFloatColumn) IsNumeric() bool {
  271. return true
  272. }
  273. // DefinitionString implementation of SFloatColumn for IColumnSpec
  274. func (c *SFloatColumn) DefinitionString() string {
  275. buf := columnDefinitionBuffer(c)
  276. return buf.String()
  277. }
  278. // IsZero implementation of SFloatColumn for IColumnSpec
  279. func (c *SFloatColumn) IsZero(val interface{}) bool {
  280. if c.IsPointer() {
  281. switch val.(type) {
  282. case *float32:
  283. return val.(*float32) == nil
  284. case *float64:
  285. return val.(*float64) == nil
  286. }
  287. } else {
  288. switch val.(type) {
  289. case float32:
  290. return val.(float32) == 0.0
  291. case float64:
  292. return val.(float64) == 0.0
  293. }
  294. }
  295. return true
  296. }
  297. // ConvertFromString implementation of SBooleanColumn for IColumnSpec
  298. func (c *SFloatColumn) ConvertFromString(str string) interface{} {
  299. floatVal := sqlchemy.ConvertValueToFloat(str)
  300. switch c.ColType() {
  301. case "FLOAT", "REAL":
  302. return float32(floatVal)
  303. case "DOUBLE":
  304. return floatVal
  305. }
  306. panic(fmt.Sprintf("unsupported type %s", c.ColType()))
  307. }
  308. // NewFloatColumn returns an instance of SFloatColumn
  309. func NewFloatColumn(name string, sqlType string, tagmap map[string]string, isPointer bool) SFloatColumn {
  310. return SFloatColumn{SBaseColumn: sqlchemy.NewBaseColumn(name, sqlType, tagmap, isPointer)}
  311. }
  312. // SDecimalColumn represents a DECIMAL type of column, i.e. a float with fixed width of digits
  313. type SDecimalColumn struct {
  314. sqlchemy.SBaseWidthColumn
  315. Precision int
  316. }
  317. // ColType implementation of SDecimalColumn for IColumnSpec
  318. func (c *SDecimalColumn) ColType() string {
  319. str := c.SBaseWidthColumn.ColType()
  320. if str[len(str)-1] == ')' {
  321. str = str[:len(str)-1]
  322. }
  323. return fmt.Sprintf("%s, %d)", str, c.Precision)
  324. }
  325. // IsNumeric implementation of SDecimalColumn for IColumnSpec
  326. func (c *SDecimalColumn) IsNumeric() bool {
  327. return true
  328. }
  329. // DefinitionString implementation of SDecimalColumn for IColumnSpec
  330. func (c *SDecimalColumn) DefinitionString() string {
  331. buf := columnDefinitionBuffer(c)
  332. return buf.String()
  333. }
  334. // IsZero implementation of SDecimalColumn for IColumnSpec
  335. func (c *SDecimalColumn) IsZero(val interface{}) bool {
  336. if c.IsPointer() {
  337. switch val.(type) {
  338. case *float32:
  339. return val.(*float32) == nil
  340. case *float64:
  341. return val.(*float64) == nil
  342. }
  343. } else {
  344. switch val.(type) {
  345. case float32:
  346. return val.(float32) == 0.0
  347. case float64:
  348. return val.(float64) == 0.0
  349. }
  350. }
  351. return true
  352. }
  353. // ConvertFromString implementation of SBooleanColumn for IColumnSpec
  354. func (c *SDecimalColumn) ConvertFromString(str string) interface{} {
  355. return sqlchemy.ConvertValueToFloat(str)
  356. }
  357. // NewDecimalColumn returns an instance of SDecimalColumn
  358. func NewDecimalColumn(name string, tagmap map[string]string, isPointer bool) SDecimalColumn {
  359. tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_PRECISION)
  360. if !ok {
  361. panic(fmt.Sprintf("Field %q of float misses precision tag", name))
  362. }
  363. prec, err := strconv.Atoi(v)
  364. if err != nil {
  365. panic(fmt.Sprintf("Field precision of %q shoud be integer (%q)", name, v))
  366. }
  367. return SDecimalColumn{
  368. SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, "DECIMAL", tagmap, isPointer),
  369. Precision: prec,
  370. }
  371. }
  372. // STextColumn represents a text type of column
  373. type STextColumn struct {
  374. sqlchemy.SBaseWidthColumn
  375. }
  376. // IsText implementation of STextColumn for IColumnSpec
  377. func (c *STextColumn) IsText() bool {
  378. return true
  379. }
  380. // IsSearchable implementation of STextColumn for IColumnSpec
  381. func (c *STextColumn) IsSearchable() bool {
  382. return true
  383. }
  384. // IsAscii implementation of STextColumn for IColumnSpec
  385. func (c *STextColumn) IsAscii() bool {
  386. return false
  387. }
  388. // DefinitionString implementation of STextColumn for IColumnSpec
  389. func (c *STextColumn) DefinitionString() string {
  390. buf := columnDefinitionBuffer(c)
  391. return buf.String()
  392. }
  393. // IsZero implementation of STextColumn for IColumnSpec
  394. func (c *STextColumn) IsZero(val interface{}) bool {
  395. if c.IsPointer() {
  396. return gotypes.IsNil(val)
  397. }
  398. return reflect.ValueOf(val).Len() == 0
  399. }
  400. // ConvertFromString implementation of SBooleanColumn for IColumnSpec
  401. func (c *STextColumn) ConvertFromString(str string) interface{} {
  402. return str
  403. }
  404. func (c *STextColumn) IsString() bool {
  405. return true
  406. }
  407. // NewTextColumn return an instance of STextColumn
  408. func NewTextColumn(name string, sqlType string, tagmap map[string]string, isPointer bool) STextColumn {
  409. return STextColumn{
  410. SBaseWidthColumn: sqlchemy.NewBaseWidthColumn(name, sqlType, tagmap, isPointer),
  411. }
  412. }
  413. // STimeTypeColumn represents a Detetime type of column, e.g. DateTime
  414. type STimeTypeColumn struct {
  415. sqlchemy.SBaseColumn
  416. // timestamp precision of nanoseconds, 0-6
  417. precision int
  418. }
  419. // IsText implementation of STimeTypeColumn for IColumnSpec
  420. func (c *STimeTypeColumn) IsText() bool {
  421. return true
  422. }
  423. // DefinitionString implementation of STimeTypeColumn for IColumnSpec
  424. func (c *STimeTypeColumn) DefinitionString() string {
  425. buf := columnDefinitionBuffer(c)
  426. return buf.String()
  427. }
  428. // IsZero implementation of STimeTypeColumn for IColumnSpec
  429. func (c *STimeTypeColumn) IsZero(val interface{}) bool {
  430. if c.IsPointer() {
  431. bVal := val.(*time.Time)
  432. return bVal == nil
  433. }
  434. bVal := val.(time.Time)
  435. return bVal.IsZero()
  436. }
  437. // ColType implementation of SIntegerColumn for IColumnSpec
  438. func (c *STimeTypeColumn) ColType() string {
  439. return fmt.Sprintf("%s(%d)", (&c.SBaseColumn).ColType(), c.precision)
  440. }
  441. // ConvertFromString implementation of SBooleanColumn for IColumnSpec
  442. func (c *STimeTypeColumn) ConvertFromString(str string) interface{} {
  443. return sqlchemy.ConvertValueToTime(str)
  444. }
  445. func (c *STimeTypeColumn) ConvertFromValue(val interface{}) interface{} {
  446. return sqlchemy.ConvertValueToTime(val)
  447. }
  448. // NewTimeTypeColumn return an instance of STimeTypeColumn
  449. func NewTimeTypeColumn(name string, typeStr string, tagmap map[string]string, isPointer bool) STimeTypeColumn {
  450. tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_PRECISION)
  451. prec := 0
  452. if ok {
  453. prec, _ = strconv.Atoi(v)
  454. if prec > 6 || prec < 0 {
  455. log.Warningf("datetime field %s precision %d change to 6", name, prec)
  456. prec = 0
  457. }
  458. }
  459. dc := STimeTypeColumn{
  460. SBaseColumn: sqlchemy.NewBaseColumn(name, typeStr, tagmap, isPointer),
  461. precision: prec,
  462. }
  463. return dc
  464. }
  465. // SDateTimeColumn represents a DateTime type of column
  466. type SDateTimeColumn struct {
  467. STimeTypeColumn
  468. // Is this column a 'created_at' field, which records the time of create this record
  469. isCreatedAt bool
  470. // Is this column a 'updated_at' field, which records the time when this record was updated
  471. isUpdatedAt bool
  472. }
  473. // DefinitionString implementation of SDateTimeColumn for IColumnSpec
  474. func (c *SDateTimeColumn) DefinitionString() string {
  475. buf := columnDefinitionBuffer(c)
  476. return buf.String()
  477. }
  478. func (c *SDateTimeColumn) IsCreatedAt() bool {
  479. return c.isCreatedAt
  480. }
  481. func (c *SDateTimeColumn) IsUpdatedAt() bool {
  482. return c.isUpdatedAt
  483. }
  484. func (c *SDateTimeColumn) IsDateTime() bool {
  485. return true
  486. }
  487. // NewDateTimeColumn returns an instance of DateTime column
  488. func NewDateTimeColumn(name string, tagmap map[string]string, isPointer bool) SDateTimeColumn {
  489. createdAt := false
  490. updatedAt := false
  491. tagmap, v, ok := utils.TagPop(tagmap, sqlchemy.TAG_CREATE_TIMESTAMP)
  492. if ok {
  493. createdAt = utils.ToBool(v)
  494. }
  495. tagmap, v, ok = utils.TagPop(tagmap, sqlchemy.TAG_UPDATE_TIMESTAMP)
  496. if ok {
  497. updatedAt = utils.ToBool(v)
  498. }
  499. dtc := SDateTimeColumn{
  500. STimeTypeColumn: NewTimeTypeColumn(name, "TIMESTAMP", tagmap, isPointer),
  501. isCreatedAt: createdAt,
  502. isUpdatedAt: updatedAt,
  503. }
  504. return dtc
  505. }
  506. // CompoundColumn represents a column of compound tye, e.g. a JSON, an Array, or a struct
  507. type CompoundColumn struct {
  508. STextColumn
  509. sqlchemy.SBaseCompoundColumn
  510. }
  511. // DefinitionString implementation of CompoundColumn for IColumnSpec
  512. func (c *CompoundColumn) DefinitionString() string {
  513. buf := columnDefinitionBuffer(c)
  514. return buf.String()
  515. }
  516. // IsZero implementation of CompoundColumn for IColumnSpec
  517. func (c *CompoundColumn) IsZero(val interface{}) bool {
  518. if val == nil {
  519. return true
  520. }
  521. if c.IsPointer() && reflect.ValueOf(val).IsNil() {
  522. return true
  523. }
  524. return false
  525. }
  526. // ConvertFromString implementation of CompoundColumn for IColumnSpec
  527. func (c *CompoundColumn) ConvertFromString(str string) interface{} {
  528. return c.SBaseCompoundColumn.ConvertFromString(str)
  529. }
  530. // ConvertFromValue implementation of CompoundColumn for IColumnSpec
  531. func (c *CompoundColumn) ConvertFromValue(val interface{}) interface{} {
  532. return c.SBaseCompoundColumn.ConvertFromValue(val)
  533. }
  534. // NewCompoundColumn returns an instance of CompoundColumn
  535. func NewCompoundColumn(name string, sqlType string, tagmap map[string]string, isPointer bool) CompoundColumn {
  536. dtc := CompoundColumn{STextColumn: NewTextColumn(name, sqlType, tagmap, isPointer)}
  537. return dtc
  538. }