gdb_driver_mssql.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. //
  7. // Note:
  8. // 1. It needs manually import: _ "github.com/denisenkom/go-mssqldb"
  9. // 2. It does not support Save/Replace features.
  10. // 3. It does not support LastInsertId.
  11. package gdb
  12. import (
  13. "database/sql"
  14. "fmt"
  15. "github.com/gogf/gf/errors/gerror"
  16. "strconv"
  17. "strings"
  18. "github.com/gogf/gf/internal/intlog"
  19. "github.com/gogf/gf/text/gstr"
  20. "github.com/gogf/gf/text/gregex"
  21. )
  22. // DriverMssql is the driver for SQL server database.
  23. type DriverMssql struct {
  24. *Core
  25. }
  26. // New creates and returns a database object for SQL server.
  27. // It implements the interface of gdb.Driver for extra database driver installation.
  28. func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
  29. return &DriverMssql{
  30. Core: core,
  31. }, nil
  32. }
  33. // Open creates and returns a underlying sql.DB object for mssql.
  34. func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
  35. source := ""
  36. if config.LinkInfo != "" {
  37. source = config.LinkInfo
  38. } else {
  39. source = fmt.Sprintf(
  40. "user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
  41. config.User, config.Pass, config.Host, config.Port, config.Name,
  42. )
  43. }
  44. intlog.Printf("Open: %s", source)
  45. if db, err := sql.Open("sqlserver", source); err == nil {
  46. return db, nil
  47. } else {
  48. return nil, err
  49. }
  50. }
  51. // GetChars returns the security char for this type of database.
  52. func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
  53. return "\"", "\""
  54. }
  55. // HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
  56. func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
  57. var index int
  58. // Convert place holder char '?' to string "@px".
  59. str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
  60. index++
  61. return fmt.Sprintf("@p%d", index)
  62. })
  63. str, _ = gregex.ReplaceString("\"", "", str)
  64. return d.parseSql(str), args
  65. }
  66. // parseSql does some replacement of the sql before commits it to underlying driver,
  67. // for support of microsoft sql server.
  68. func (d *DriverMssql) parseSql(sql string) string {
  69. // SELECT * FROM USER WHERE ID=1 LIMIT 1
  70. if m, _ := gregex.MatchString(`^SELECT(.+)LIMIT 1$`, sql); len(m) > 1 {
  71. return fmt.Sprintf(`SELECT TOP 1 %s`, m[1])
  72. }
  73. // SELECT * FROM USER WHERE AGE>18 ORDER BY ID DESC LIMIT 100, 200
  74. patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
  75. if gregex.IsMatchString(patten, sql) == false {
  76. return sql
  77. }
  78. res, err := gregex.MatchAllString(patten, sql)
  79. if err != nil {
  80. return ""
  81. }
  82. index := 0
  83. keyword := strings.TrimSpace(res[index][0])
  84. keyword = strings.ToUpper(keyword)
  85. index++
  86. switch keyword {
  87. case "SELECT":
  88. // LIMIT statement checks.
  89. if len(res) < 2 ||
  90. (strings.HasPrefix(res[index][0], "LIMIT") == false &&
  91. strings.HasPrefix(res[index][0], "limit") == false) {
  92. break
  93. }
  94. if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
  95. break
  96. }
  97. // ORDER BY statement checks.
  98. selectStr := ""
  99. orderStr := ""
  100. haveOrder := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
  101. if haveOrder {
  102. queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
  103. if len(queryExpr) != 4 ||
  104. strings.EqualFold(queryExpr[1], "SELECT") == false ||
  105. strings.EqualFold(queryExpr[3], "ORDER BY") == false {
  106. break
  107. }
  108. selectStr = queryExpr[2]
  109. orderExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
  110. if len(orderExpr) != 4 ||
  111. strings.EqualFold(orderExpr[1], "ORDER BY") == false ||
  112. strings.EqualFold(orderExpr[3], "LIMIT") == false {
  113. break
  114. }
  115. orderStr = orderExpr[2]
  116. } else {
  117. queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
  118. if len(queryExpr) != 4 ||
  119. strings.EqualFold(queryExpr[1], "SELECT") == false ||
  120. strings.EqualFold(queryExpr[3], "LIMIT") == false {
  121. break
  122. }
  123. selectStr = queryExpr[2]
  124. }
  125. first, limit := 0, 0
  126. for i := 1; i < len(res[index]); i++ {
  127. if len(strings.TrimSpace(res[index][i])) == 0 {
  128. continue
  129. }
  130. if strings.HasPrefix(res[index][i], "LIMIT") ||
  131. strings.HasPrefix(res[index][i], "limit") {
  132. first, _ = strconv.Atoi(res[index][i+1])
  133. limit, _ = strconv.Atoi(res[index][i+2])
  134. break
  135. }
  136. }
  137. if haveOrder {
  138. sql = fmt.Sprintf(
  139. "SELECT * FROM "+
  140. "(SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ "+
  141. "WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d",
  142. orderStr, selectStr, first, first+limit,
  143. )
  144. } else {
  145. if first == 0 {
  146. first = limit
  147. }
  148. sql = fmt.Sprintf(
  149. "SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ",
  150. limit, first+limit, selectStr,
  151. )
  152. }
  153. default:
  154. }
  155. return sql
  156. }
  157. // Tables retrieves and returns the tables of current schema.
  158. // It's mainly used in cli tool chain for automatically generating the models.
  159. func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
  160. var result Result
  161. link, err := d.DB.GetSlave(schema...)
  162. if err != nil {
  163. return nil, err
  164. }
  165. result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
  166. if err != nil {
  167. return
  168. }
  169. for _, m := range result {
  170. for _, v := range m {
  171. tables = append(tables, v.String())
  172. }
  173. }
  174. return
  175. }
  176. // TableFields retrieves and returns the fields information of specified table of current schema.
  177. func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
  178. charL, charR := d.GetChars()
  179. table = gstr.Trim(table, charL+charR)
  180. if gstr.Contains(table, " ") {
  181. return nil, gerror.New("function TableFields supports only single table operations")
  182. }
  183. checkSchema := d.DB.GetSchema()
  184. if len(schema) > 0 && schema[0] != "" {
  185. checkSchema = schema[0]
  186. }
  187. v, _ := internalCache.GetOrSetFunc(
  188. fmt.Sprintf(`mssql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
  189. func() (interface{}, error) {
  190. var (
  191. result Result
  192. link *sql.DB
  193. )
  194. link, err = d.DB.GetSlave(checkSchema)
  195. if err != nil {
  196. return nil, err
  197. }
  198. structureSql := fmt.Sprintf(`
  199. SELECT
  200. a.name Field,
  201. CASE b.name
  202. WHEN 'datetime' THEN 'datetime'
  203. WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
  204. WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
  205. WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
  206. ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
  207. CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
  208. CASE WHEN exists (
  209. SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
  210. SELECT name FROM sysindexes WHERE indid IN (
  211. SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
  212. )
  213. )
  214. ) THEN 'PRI' ELSE '' END AS [Key],
  215. CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
  216. isnull(e.text,'') AS [Default],
  217. isnull(g.[value],'') AS [Comment]
  218. FROM syscolumns a
  219. LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
  220. INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
  221. LEFT JOIN syscomments e ON a.cdefault=e.id
  222. LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
  223. LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
  224. WHERE d.name='%s'
  225. ORDER BY a.id,a.colorder`,
  226. strings.ToUpper(table),
  227. )
  228. structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
  229. result, err = d.DB.DoGetAll(link, structureSql)
  230. if err != nil {
  231. return nil, err
  232. }
  233. fields = make(map[string]*TableField)
  234. for i, m := range result {
  235. fields[strings.ToLower(m["Field"].String())] = &TableField{
  236. Index: i,
  237. Name: strings.ToLower(m["Field"].String()),
  238. Type: strings.ToLower(m["Type"].String()),
  239. Null: m["Null"].Bool(),
  240. Key: m["Key"].String(),
  241. Default: m["Default"].Val(),
  242. Extra: m["Extra"].String(),
  243. Comment: m["Comment"].String(),
  244. }
  245. }
  246. return fields, nil
  247. }, 0)
  248. if err == nil {
  249. fields = v.(map[string]*TableField)
  250. }
  251. return
  252. }