savepoint.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // Copyright (c) 2018 David Crawshaw <david@zentus.com>
  2. // Copyright (c) 2021 Ross Light <rosss@zombiezen.com>
  3. //
  4. // Permission to use, copy, modify, and distribute this software for any
  5. // purpose with or without fee is hereby granted, provided that the above
  6. // copyright notice and this permission notice appear in all copies.
  7. //
  8. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. //
  16. // SPDX-License-Identifier: ISC
  17. package sqlitex
  18. import (
  19. "fmt"
  20. "runtime"
  21. "strings"
  22. "zombiezen.com/go/sqlite"
  23. )
  24. // Save creates a named SQLite transaction using SAVEPOINT.
  25. //
  26. // On success Savepoint returns a releaseFn that will call either
  27. // RELEASE or ROLLBACK depending on whether the parameter *error
  28. // points to a nil or non-nil error. This is designed to be deferred.
  29. //
  30. // https://www.sqlite.org/lang_savepoint.html
  31. func Save(conn *sqlite.Conn) (releaseFn func(*error)) {
  32. name := "sqlitex.Save" // safe as names can be reused
  33. var pc [3]uintptr
  34. if n := runtime.Callers(0, pc[:]); n > 0 {
  35. frames := runtime.CallersFrames(pc[:n])
  36. if _, more := frames.Next(); more { // runtime.Callers
  37. if _, more := frames.Next(); more { // savepoint.Save
  38. frame, _ := frames.Next() // caller we care about
  39. if frame.Function != "" {
  40. name = frame.Function
  41. }
  42. }
  43. }
  44. }
  45. releaseFn, err := savepoint(conn, name)
  46. if err != nil {
  47. if sqlite.ErrCode(err) == sqlite.ResultInterrupt {
  48. return func(errp *error) {
  49. if *errp == nil {
  50. *errp = err
  51. }
  52. }
  53. }
  54. panic(err)
  55. }
  56. return releaseFn
  57. }
  58. func savepoint(conn *sqlite.Conn, name string) (releaseFn func(*error), err error) {
  59. if strings.Contains(name, `"`) {
  60. return nil, fmt.Errorf("sqlitex.Savepoint: invalid name: %q", name)
  61. }
  62. if err := Execute(conn, fmt.Sprintf("SAVEPOINT %q;", name), nil); err != nil {
  63. return nil, err
  64. }
  65. // TODO(maybe)
  66. // tracer := conn.Tracer()
  67. // if tracer != nil {
  68. // tracer.Push("TX " + name)
  69. // }
  70. releaseFn = func(errp *error) {
  71. // TODO(maybe)
  72. // if tracer != nil {
  73. // tracer.Pop()
  74. // }
  75. recoverP := recover()
  76. // If a query was interrupted or if a user exec'd COMMIT or
  77. // ROLLBACK, then everything was already rolled back
  78. // automatically, thus returning the connection to autocommit
  79. // mode.
  80. if conn.AutocommitEnabled() {
  81. // There is nothing to rollback.
  82. if recoverP != nil {
  83. panic(recoverP)
  84. }
  85. return
  86. }
  87. if *errp == nil && recoverP == nil {
  88. // Success path. Release the savepoint successfully.
  89. *errp = Execute(conn, fmt.Sprintf("RELEASE %q;", name), nil)
  90. if *errp == nil {
  91. return
  92. }
  93. // Possible interrupt. Fall through to the error path.
  94. if conn.AutocommitEnabled() {
  95. // There is nothing to rollback.
  96. if recoverP != nil {
  97. panic(recoverP)
  98. }
  99. return
  100. }
  101. }
  102. orig := ""
  103. if *errp != nil {
  104. orig = (*errp).Error() + "\n\t"
  105. }
  106. // Error path.
  107. // Always run ROLLBACK even if the connection has been interrupted.
  108. oldDoneCh := conn.SetInterrupt(nil)
  109. defer conn.SetInterrupt(oldDoneCh)
  110. err := Execute(conn, fmt.Sprintf("ROLLBACK TO %q;", name), nil)
  111. if err != nil {
  112. panic(orig + err.Error())
  113. }
  114. err = Execute(conn, fmt.Sprintf("RELEASE %q;", name), nil)
  115. if err != nil {
  116. panic(orig + err.Error())
  117. }
  118. if recoverP != nil {
  119. panic(recoverP)
  120. }
  121. }
  122. return releaseFn, nil
  123. }
  124. // Transaction creates a DEFERRED SQLite transaction.
  125. //
  126. // On success Transaction returns an endFn that will call either
  127. // COMMIT or ROLLBACK depending on whether the parameter *error
  128. // points to a nil or non-nil error. This is designed to be deferred.
  129. //
  130. // https://www.sqlite.org/lang_transaction.html
  131. func Transaction(conn *sqlite.Conn) (endFn func(*error)) {
  132. endFn, err := transaction(conn, "DEFERRED")
  133. if err != nil {
  134. if sqlite.ErrCode(err) == sqlite.ResultInterrupt {
  135. return func(errp *error) {
  136. if *errp == nil {
  137. *errp = err
  138. }
  139. }
  140. }
  141. panic(err)
  142. }
  143. return endFn
  144. }
  145. // ImmediateTransaction creates an IMMEDIATE SQLite transaction.
  146. //
  147. // On success ImmediateTransaction returns an endFn that will call either
  148. // COMMIT or ROLLBACK depending on whether the parameter *error
  149. // points to a nil or non-nil error. This is designed to be deferred.
  150. //
  151. // https://www.sqlite.org/lang_transaction.html
  152. func ImmediateTransaction(conn *sqlite.Conn) (endFn func(*error), err error) {
  153. endFn, err = transaction(conn, "IMMEDIATE")
  154. if err != nil {
  155. return func(*error) {}, err
  156. }
  157. return endFn, nil
  158. }
  159. // ExclusiveTransaction creates an EXCLUSIVE SQLite transaction.
  160. //
  161. // On success ImmediateTransaction returns an endFn that will call either
  162. // COMMIT or ROLLBACK depending on whether the parameter *error
  163. // points to a nil or non-nil error. This is designed to be deferred.
  164. //
  165. // https://www.sqlite.org/lang_transaction.html
  166. func ExclusiveTransaction(conn *sqlite.Conn) (endFn func(*error), err error) {
  167. endFn, err = transaction(conn, "EXCLUSIVE")
  168. if err != nil {
  169. return func(*error) {}, err
  170. }
  171. return endFn, nil
  172. }
  173. func transaction(conn *sqlite.Conn, mode string) (endFn func(*error), err error) {
  174. if err := Execute(conn, "BEGIN "+mode+";", nil); err != nil {
  175. return nil, err
  176. }
  177. endFn = func(errp *error) {
  178. recoverP := recover()
  179. // If a query was interrupted or if a user exec'd COMMIT or
  180. // ROLLBACK, then everything was already rolled back
  181. // automatically, thus returning the connection to autocommit
  182. // mode.
  183. if conn.AutocommitEnabled() {
  184. // There is nothing to rollback.
  185. if recoverP != nil {
  186. panic(recoverP)
  187. }
  188. return
  189. }
  190. if *errp == nil && recoverP == nil {
  191. // Success path. Commit the transaction.
  192. *errp = Execute(conn, "COMMIT;", nil)
  193. if *errp == nil {
  194. return
  195. }
  196. // Possible interrupt. Fall through to the error path.
  197. if conn.AutocommitEnabled() {
  198. // There is nothing to rollback.
  199. if recoverP != nil {
  200. panic(recoverP)
  201. }
  202. return
  203. }
  204. }
  205. orig := ""
  206. if *errp != nil {
  207. orig = (*errp).Error() + "\n\t"
  208. }
  209. // Error path.
  210. // Always run ROLLBACK even if the connection has been interrupted.
  211. oldDoneCh := conn.SetInterrupt(nil)
  212. defer conn.SetInterrupt(oldDoneCh)
  213. err := Execute(conn, "ROLLBACK;", nil)
  214. if err != nil {
  215. panic(orig + err.Error())
  216. }
  217. if recoverP != nil {
  218. panic(recoverP)
  219. }
  220. }
  221. return endFn, nil
  222. }