sql.go 13 KB

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