sql.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. package lxDb
  2. import (
  3. "errors"
  4. "fmt"
  5. "git.listensoft.net/tool/lxutils/lxUtil"
  6. "gorm.io/gorm"
  7. "strings"
  8. )
  9. // 带事务的, 和不带事务的 说明:
  10. // 如果需要支持事务请调aaaTx方法, 并传开启事务的DB
  11. // OneByIdTx 通过ID查询一条
  12. func OneById(tx *gorm.DB, m interface{}, id uint) (err error) {
  13. if id == 0 {
  14. return errors.New("主键ID不能为空")
  15. }
  16. if tx.Take(m, id).Error != nil { // or First ?
  17. return errors.New("未找到记录")
  18. }
  19. return nil
  20. }
  21. // OneTx 查询一条 使用m的有值属性作为条件, m必须是Model结构体. 条件为空时报错.
  22. func One(tx *gorm.DB, m interface{}) error {
  23. return oneTx(tx, m, "")
  24. }
  25. func oneTx(tx *gorm.DB, m interface{}, Type string) (err error) {
  26. //reflectVal := reflect.ValueOf(m)
  27. //t := reflect.Indirect(reflectVal).Type()
  28. //newObj := reflect.New(t)
  29. if lxUtil.IsZeroOfUnderlyingType(m) {
  30. return errors.New("条件不能为空")
  31. }
  32. // 这里有一个特别的情况, 如果m.id有值, 生成sql的where里id条件出现两次, 但是不影响效果
  33. if Type == "first" { // 第一个
  34. err = tx.Where(m).First(m).Error
  35. } else if Type == "last" { // 最后一个
  36. err = tx.Where(m).Last(m).Error
  37. } else { // 就是一个
  38. err = tx.Where(m).Take(m).Error
  39. }
  40. if err != nil {
  41. return err
  42. }
  43. return nil
  44. }
  45. // FirstTx 查询第一条 使用m的有值属性作为条件, m必须是Model结构体. 条件为空时报错.
  46. func First(tx *gorm.DB, m interface{}) error {
  47. return oneTx(tx, m, "first")
  48. }
  49. // LastTx 查询最后一条 使用m的有值属性作为条件, m必须是Model结构体. 条件为空时报错.
  50. func Last(tx *gorm.DB, m interface{}) error {
  51. return oneTx(tx, m, "last")
  52. }
  53. // One 查询一条. 这种方式时不行的, 实际上对应的表是 base_model
  54. //func (m *BaseModel) One() (err error) {
  55. // //if DB.Where(m).First(one).RecordNotFound() {
  56. // if DB.Where(m).First(m).Error != nil {
  57. // return errors.New("resource is not found")
  58. // }
  59. // return nil
  60. //}
  61. // CreateTx 带事务的, 新增
  62. func Create(tx *gorm.DB, m interface{}) error {
  63. return tx.Create(m).Error
  64. }
  65. // UpdateTx 带事务的, 更新一条数据的单个字段, m.ID必须有值
  66. func Update(tx *gorm.DB, m interface{}, field string, value interface{}) error {
  67. db := tx.Model(m).Update(field, value)
  68. if err := db.Error; err != nil {
  69. return err
  70. }
  71. if db.RowsAffected != 1 {
  72. return errors.New("id is invalid and resource is not found")
  73. }
  74. return nil
  75. }
  76. // UpdatesTx 带事务的, 更新一条数据的多个字段, m.ID必须有值
  77. func Updates(tx *gorm.DB, m interface{}, fields interface{}) error {
  78. db := tx.Model(m).Updates(fields)
  79. if err := db.Error; err != nil {
  80. return err
  81. }
  82. if db.RowsAffected != 1 {
  83. return errors.New("id is invalid and resource is not found")
  84. }
  85. return nil
  86. }
  87. // DeleteTx 删除, m.ID必须有值. tx不为空就是带事务的
  88. func Delete(tx *gorm.DB, m interface{}) error {
  89. //func DeleteTx(tx *gorm.DB, m interface{}, conds ...interface{}) error {
  90. db := tx.Delete(m)
  91. //db := tx.Delete(m, conds...)
  92. if err := db.Error; err != nil {
  93. return err
  94. }
  95. if db.RowsAffected != 1 {
  96. return errors.New("未找到要删除的数据")
  97. }
  98. return nil
  99. }
  100. // ListTx 查询数据列表, m是库表结构体, m的有值属性会作为查询条件, 且必须有条件, list里个体的类型可以与m的类型不同
  101. func List(tx *gorm.DB, m interface{}, list interface{}) (err error) {
  102. if lxUtil.IsZeroOfUnderlyingType(m) {
  103. return errors.New("条件不能为空")
  104. }
  105. if tx.Model(m).Where(m).Find(list).Error != nil {
  106. // if tx.Where(m).Find(list).Error != nil {
  107. return errors.New("查询出现错误")
  108. }
  109. return nil
  110. }
  111. // ListAllTx 查询所有数据, m是库表结构体, m的有值属性不会作为查询条件, list里个体的类型可以与m的类型不同
  112. func ListAll(tx *gorm.DB, m interface{}, list interface{}) (err error) {
  113. if tx.Model(m).Find(list).Error != nil {
  114. // if tx.Where(m).Find(list).Error != nil {
  115. return errors.New("查询出现错误")
  116. }
  117. return nil
  118. }
  119. // QueryTx 带事务的查询数据列表. tx为空就是不带事务, 否则认为是开启了事务. 你应该避免使用次方法, 而是使用Query
  120. func Query(tx *gorm.DB, m interface{}, list interface{}, q *PaginationQuery) (err error) {
  121. // !! 关于会话 新的Statement实例 及复用 ref: https://gorm.io/zh_CN/docs/method_chaining.html
  122. if q == nil {
  123. err = errors.New("paginationQuery不可为nil")
  124. return
  125. }
  126. tx = tx.Model(m)
  127. // 注意: count, 查询list, summary的顺序不能变.
  128. tx = q.Build(tx) // 构造查询条件
  129. // 记录条数
  130. if needDoCount(q) {
  131. var total int64
  132. tx = tx.Count(&total)
  133. q.Total = int(total)
  134. if total == 0 { // 如果查了记录条数并且是0, 就不需要查记录和汇总了
  135. return
  136. }
  137. }
  138. if q.OrderBy != "" {
  139. tx = tx.Order(lxUtil.FieldToColumn(q.OrderBy)) // TODO: q.OrderBy是字符串,可能多个字段 会有问题吗
  140. //tx = tx.Order(q.OrderBy)
  141. }
  142. if q.Offset > 0 {
  143. tx = tx.Offset((q.Offset - 1) * q.Limit)
  144. }
  145. if q.Limit > 0 {
  146. tx = tx.Limit(q.Limit)
  147. }
  148. // 获取查询值
  149. err = tx.Find(list).Error
  150. // 获取汇总信息, 如果不需要查记录条数就不再查汇总
  151. if needDoCount(q) {
  152. if q.Summary != "" && len(q.SummarySql) == 0 {
  153. q.SummarySql = fieldsToSumSql(q.Summary)
  154. }
  155. if len(q.Summary) != 0 {
  156. tx = tx.Offset(-1) // 需要去除offset, 否则结果可能为空, 注意: 设置0不起作用.
  157. var summary = make(map[string]interface{})
  158. //tx.Order("") // FIXME: 怎么去掉order by, sum是不需要order by的, 影响性能.
  159. tx.Select(q.SummarySql).Take(&summary)
  160. // []byte 转 string. 不太合理, 应该返回int或float
  161. for k, v := range summary {
  162. if bs, ok := v.([]byte); ok {
  163. summary[k] = string(bs)
  164. }
  165. }
  166. q.SummaryResult = summary
  167. }
  168. }
  169. return
  170. }
  171. //// SqlOne 原生SQL查询一个
  172. //func SqlOne(sql string, m interface{}) {
  173. // DB.Raw(sql, m)
  174. //}
  175. //
  176. //// SqlList 原生SQL查询列表
  177. //func SqlList() {
  178. //
  179. //}
  180. func SqlQuery(tx *gorm.DB, sql string, list interface{}, q *PaginationQuery, params ...interface{}) (err error) {
  181. var builder strings.Builder
  182. builder.WriteString(sql)
  183. if params == nil {
  184. params = make([]interface{}, 0)
  185. }
  186. // 条件字段
  187. if q != nil {
  188. where, args := q.BuildRawWhere()
  189. if hasWhere(sql) { // 原SQL已有WHERE子句
  190. builder.WriteString(where) // 去掉where 前头的and or ..
  191. } else { // 原SQL没有WHERE子句
  192. if strings.HasPrefix(where, " AND ") {
  193. where = strings.Replace(where, " AND ", " WHERE ", 1)
  194. builder.WriteString(where)
  195. } else if strings.HasPrefix(where, " OR ") {
  196. where = strings.Replace(where, " OR ", " WHERE ", 1)
  197. builder.WriteString(where)
  198. } else {
  199. builder.WriteString(where) // "" 或者 " GROUP BY ..."
  200. }
  201. }
  202. if len(args) > 0 {
  203. params = append(params, args...)
  204. }
  205. // 半成品 sql 用于查询其他信息
  206. var sql2 = builder.String()
  207. // 记录条数
  208. if needDoCount(q) {
  209. var total int64
  210. //tx = tx.Count(&total)
  211. tx.Raw("SELECT COUNT(*) as total FROM ("+sql2+") aaaa", params...).Take(&total)
  212. q.Total = int(total)
  213. if total == 0 { // 如果查了记录条数并且是0, 就不需要查记录和汇总了
  214. return
  215. }
  216. // 获取汇总信息 // TODO: 汇总应该放到查询列表的后面
  217. if q.Summary != "" && len(q.SummarySql) == 0 {
  218. q.SummarySql = fieldsToSumSql(q.Summary)
  219. }
  220. if len(q.Summary) != 0 {
  221. tx = tx.Offset(-1) // 需要去除offset, 否则结果可能为空, 注意: 设置0不起作用.
  222. var summary = make(map[string]interface{})
  223. //tx.Order("") // FIXME: 怎么去掉order by, sum是不需要order by的, 影响性能.
  224. //tx.Select(q.SummarySql).Take(&summary) // 不适合rawsql?
  225. tx.Raw("SELECT "+strings.Join(q.SummarySql, ", ")+" FROM ("+sql2+") ssss", params...).Take(&summary)
  226. // []byte 转 string. 不太合理, 应该返回int或float
  227. for k, v := range summary {
  228. if bs, ok := v.([]byte); ok {
  229. summary[k] = string(bs)
  230. }
  231. }
  232. q.SummaryResult = summary
  233. }
  234. }
  235. // 排序处理
  236. if q.OrderBy != "" {
  237. s := fmt.Sprintf(" ORDER BY %s", lxUtil.FieldToColumn(q.OrderBy)) // TODO: q.OrderBy是字符串,可能多个字段 会有问题吗
  238. builder.WriteString(s)
  239. }
  240. // 偏移量处理
  241. if q.Limit > 0 {
  242. if q.Offset > 0 {
  243. offset := (q.Offset - 1) * q.Limit
  244. s := fmt.Sprintf(" LIMIT %d, %d", offset, q.Limit)
  245. builder.WriteString(s)
  246. } else {
  247. s := fmt.Sprintf(" LIMIT %d", q.Limit)
  248. builder.WriteString(s)
  249. }
  250. }
  251. }
  252. //tx.Raw(builder.String(), params...).Scan(list) // FIXME: unsupported data type: &[] why?
  253. tx.Raw(builder.String(), params...).Find(list) // Find与Scan区别: list传入[]时, 查询为空的情况下, Find返回的是[], 而Scan返回的是nil.
  254. // ref: What is the difference between Find and Scan: https://github.com/go-gorm/gorm/issues/4218
  255. return
  256. }
  257. // 是否需要查询记录条数
  258. func needDoCount(q *PaginationQuery) bool {
  259. if q.NoTotal {
  260. return false
  261. }
  262. if q.Limit == 0 { // 不限制条数, 等同于查所有记录, 这时候就不需要查记录条数
  263. return false
  264. }
  265. //return q.Offset <= 1 // todo lcs 为什么要这样写 第二页都没了
  266. return true
  267. }
  268. // utils -----------------
  269. // "inAmt,inCount" -> ["SUM(int_amt) AS inAmt", "SUM(int_count) AS inCount"]
  270. func fieldsToSumSql(fields string) (sumSqls []string) {
  271. strs := strings.Split(strings.TrimSpace(fields), ",")
  272. for _, str := range strs {
  273. field := strings.TrimSpace(str)
  274. if field != "" {
  275. sumSqls = append(sumSqls, "SUM("+lxUtil.FieldToColumn(field)+") AS "+field+"")
  276. }
  277. }
  278. return
  279. }
  280. // SELECT...FROM...[WHERE] 句式的 SQL 中是否存在 WHERE 子句
  281. func hasWhere(sql string) bool {
  282. deep := 0 // 括号嵌套层数
  283. step := 0 // "where" 匹配进度
  284. // 遍历 sql 忽略 ' ( ` 判断是否存在 where
  285. for i := 0; i < len(sql); i++ {
  286. switch sql[i] {
  287. case '(':
  288. deep++
  289. case ')':
  290. deep--
  291. case 96: // "`"
  292. // 下一个 "`" 的下标
  293. // 忽略其他字符
  294. for i = i + 1; i < len(sql); i++ {
  295. if sql[i] == 96 {
  296. break
  297. }
  298. }
  299. case 39: // "'"
  300. // 下一个 "'" 的下标
  301. // 忽略其他字符
  302. for i = i + 1; i < len(sql); i++ {
  303. if sql[i] == 39 {
  304. break
  305. }
  306. }
  307. default:
  308. if deep != 0 {
  309. continue
  310. }
  311. if step == 5 {
  312. return true
  313. }
  314. if sql[i] == where[step][0] || sql[i] == where[step][1] {
  315. step++
  316. } else {
  317. step = 0
  318. }
  319. }
  320. }
  321. return false
  322. }
  323. var where = []string{
  324. "Ww",
  325. "Hh",
  326. "Ee",
  327. "Rr",
  328. "Ee",
  329. }
  330. // Transaction 同时开两个事务的方法
  331. func Transaction(txItem, txMain *gorm.DB, fun func(txItem, txMain *gorm.DB) (err error)) (err error) {
  332. tx1 := txItem.Begin()
  333. tx2 := txMain.Begin()
  334. err = fun(tx1, tx2)
  335. if err != nil {
  336. tx1.Rollback()
  337. tx2.Rollback()
  338. return err
  339. }
  340. err = tx1.Commit().Error
  341. if err != nil {
  342. return
  343. }
  344. err = tx2.Commit().Error
  345. if err != nil {
  346. return
  347. }
  348. return
  349. }