sql.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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. //todo lcs 优化速度 start
  213. // 替换 SELECT 和 FROM 之间的部分,并去掉 GROUP BY
  214. replacedSQL := replaceSelectAndRemoveGroupBy(sql2)
  215. tx.Raw(replacedSQL, params...).Take(&total)
  216. //todo end
  217. q.Total = int(total)
  218. if total == 0 { // 如果查了记录条数并且是0, 就不需要查记录和汇总了
  219. return
  220. }
  221. // 获取汇总信息 // TODO: 汇总应该放到查询列表的后面
  222. if q.Summary != "" && len(q.SummarySql) == 0 {
  223. q.SummarySql = fieldsToSumSql(q.Summary)
  224. }
  225. if len(q.Summary) != 0 {
  226. tx = tx.Offset(-1) // 需要去除offset, 否则结果可能为空, 注意: 设置0不起作用.
  227. var summary = make(map[string]interface{})
  228. //tx.Order("") // FIXME: 怎么去掉order by, sum是不需要order by的, 影响性能.
  229. //tx.Select(q.SummarySql).Take(&summary) // 不适合rawsql?
  230. tx.Raw("SELECT "+strings.Join(q.SummarySql, ", ")+" FROM ("+sql2+") ssss", params...).Take(&summary)
  231. // []byte 转 string. 不太合理, 应该返回int或float
  232. for k, v := range summary {
  233. if bs, ok := v.([]byte); ok {
  234. summary[k] = string(bs)
  235. }
  236. }
  237. q.SummaryResult = summary
  238. }
  239. }
  240. // 排序处理
  241. if q.OrderBy != "" {
  242. s := fmt.Sprintf(" ORDER BY %s", lxUtil.FieldToColumn(q.OrderBy)) // TODO: q.OrderBy是字符串,可能多个字段 会有问题吗
  243. builder.WriteString(s)
  244. }
  245. // 偏移量处理
  246. if q.Limit > 0 {
  247. if q.Offset > 0 {
  248. offset := (q.Offset - 1) * q.Limit
  249. s := fmt.Sprintf(" LIMIT %d, %d", offset, q.Limit)
  250. builder.WriteString(s)
  251. } else {
  252. s := fmt.Sprintf(" LIMIT %d", q.Limit)
  253. builder.WriteString(s)
  254. }
  255. }
  256. }
  257. //tx.Raw(builder.String(), params...).Scan(list) // FIXME: unsupported data type: &[] why?
  258. tx.Raw(builder.String(), params...).Find(list) // Find与Scan区别: list传入[]时, 查询为空的情况下, Find返回的是[], 而Scan返回的是nil.
  259. // ref: What is the difference between Find and Scan: https://github.com/go-gorm/gorm/issues/4218
  260. return
  261. }
  262. // 是否需要查询记录条数
  263. func needDoCount(q *PaginationQuery) bool {
  264. if q.NoTotal {
  265. return false
  266. }
  267. if q.Limit == 0 { // 不限制条数, 等同于查所有记录, 这时候就不需要查记录条数
  268. return false
  269. }
  270. //return q.Offset <= 1 // todo lcs 为什么要这样写 第二页都没了
  271. return true
  272. }
  273. // utils -----------------
  274. // "inAmt,inCount" -> ["SUM(int_amt) AS inAmt", "SUM(int_count) AS inCount"]
  275. func fieldsToSumSql(fields string) (sumSqls []string) {
  276. strs := strings.Split(strings.TrimSpace(fields), ",")
  277. for _, str := range strs {
  278. field := strings.TrimSpace(str)
  279. if field != "" {
  280. sumSqls = append(sumSqls, "SUM("+lxUtil.FieldToColumn(field)+") AS "+field+"")
  281. }
  282. }
  283. return
  284. }
  285. // SELECT...FROM...[WHERE] 句式的 SQL 中是否存在 WHERE 子句
  286. func hasWhere(sql string) bool {
  287. deep := 0 // 括号嵌套层数
  288. step := 0 // "where" 匹配进度
  289. // 遍历 sql 忽略 ' ( ` 判断是否存在 where
  290. for i := 0; i < len(sql); i++ {
  291. switch sql[i] {
  292. case '(':
  293. deep++
  294. case ')':
  295. deep--
  296. case 96: // "`"
  297. // 下一个 "`" 的下标
  298. // 忽略其他字符
  299. for i = i + 1; i < len(sql); i++ {
  300. if sql[i] == 96 {
  301. break
  302. }
  303. }
  304. case 39: // "'"
  305. // 下一个 "'" 的下标
  306. // 忽略其他字符
  307. for i = i + 1; i < len(sql); i++ {
  308. if sql[i] == 39 {
  309. break
  310. }
  311. }
  312. default:
  313. if deep != 0 {
  314. continue
  315. }
  316. if step == 5 {
  317. return true
  318. }
  319. if sql[i] == where[step][0] || sql[i] == where[step][1] {
  320. step++
  321. } else {
  322. step = 0
  323. }
  324. }
  325. }
  326. return false
  327. }
  328. var where = []string{
  329. "Ww",
  330. "Hh",
  331. "Ee",
  332. "Rr",
  333. "Ee",
  334. }
  335. // Transaction 同时开两个事务的方法
  336. func Transaction(txItem, txMain *gorm.DB, fun func(txItem, txMain *gorm.DB) (err error)) (err error) {
  337. tx1 := txItem.Begin()
  338. tx2 := txMain.Begin()
  339. err = fun(tx1, tx2)
  340. if err != nil {
  341. tx1.Rollback()
  342. tx2.Rollback()
  343. return err
  344. }
  345. err = tx1.Commit().Error
  346. if err != nil {
  347. return
  348. }
  349. err = tx2.Commit().Error
  350. if err != nil {
  351. return
  352. }
  353. return
  354. }
  355. // replaceSelectAndRemoveGroupBy 替换 SELECT 和 FROM 之间的部分,并去掉 GROUP BY
  356. func replaceSelectAndRemoveGroupBy(sql string) string {
  357. // 找到 SELECT 和 FROM 的位置
  358. selectIndex := strings.Index(strings.ToUpper(sql), "SELECT")
  359. fromIndex := strings.Index(strings.ToUpper(sql), "FROM")
  360. if selectIndex == -1 || fromIndex == -1 {
  361. return sql // 如果没有找到 SELECT 或 FROM,返回原始 SQL
  362. }
  363. // 替换 SELECT 和 FROM 之间的部分
  364. newSelectClause := "COUNT(DISTINCT ***) AS total"
  365. replacedSQL := sql[:selectIndex+6] + " " + newSelectClause + " " + sql[fromIndex:]
  366. // 去掉 GROUP BY 子句
  367. groupByIndex := strings.Index(strings.ToUpper(replacedSQL), "GROUP BY")
  368. if groupByIndex != -1 {
  369. c := replacedSQL[groupByIndex+8:]
  370. c = strings.TrimSpace(c)
  371. c = strings.ReplaceAll(c, ";", "")
  372. c = strings.Split(c, ",")[0]
  373. replacedSQL = replacedSQL[:groupByIndex]
  374. replacedSQL = strings.ReplaceAll(replacedSQL, "DISTINCT ***", "DISTINCT "+c)
  375. } else {
  376. //没有group by 的
  377. replacedSQL = strings.ReplaceAll(replacedSQL, "DISTINCT ***", "*")
  378. }
  379. return replacedSQL
  380. }