snapshot.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright (c) 2018 David Crawshaw <david@zentus.com>
  2. //
  3. // Permission to use, copy, modify, and distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. package sqlite
  15. // #include <sqlite3.h>
  16. // #include <stdlib.h>
  17. import "C"
  18. import (
  19. "runtime"
  20. "unsafe"
  21. )
  22. // A Snapshot records the state of a WAL mode database for some specific point
  23. // in history.
  24. //
  25. // Equivalent to the sqlite3_snapshot* C object.
  26. //
  27. // https://www.sqlite.org/c3ref/snapshot.html
  28. type Snapshot struct {
  29. ptr *C.sqlite3_snapshot
  30. schema *C.char
  31. }
  32. // GetSnapshot attempts to make a new Snapshot that records the current state
  33. // of the given schema in conn. If successful, a *Snapshot and a func() is
  34. // returned, and the conn will have an open READ transaction which will
  35. // continue to reflect the state of the Snapshot until the returned func() is
  36. // called. No WRITE transaction may occur on conn until the returned func() is
  37. // called.
  38. //
  39. // The returned *Snapshot is threadsafe for creating additional read
  40. // transactions that reflect its state with Conn.StartSnapshotRead.
  41. //
  42. // In theory, so long as at least one read transaction is open on the Snapshot,
  43. // then the WAL file will not be checkpointed past that point, and the Snapshot
  44. // will continue to be available for creating additional read transactions.
  45. // However, if no read transaction is open on the Snapshot, then it is possible
  46. // for the WAL to be checkpointed past the point of the Snapshot. If this
  47. // occurs then there is no way to start a read on the Snapshot. In order to
  48. // ensure that a Snapshot remains readable, always maintain at least one open
  49. // read transaction on the Snapshot.
  50. //
  51. // In practice, this is generally reliable but sometimes the Snapshot can
  52. // sometimes become unavailable for reads unless automatic checkpointing is
  53. // entirely disabled from the start.
  54. //
  55. // The returned *Snapshot has a finalizer that calls Free if it has not been
  56. // called, so it is safe to allow a Snapshot to be garbage collected. However,
  57. // if you are sure that a Snapshot will never be used again by any thread, you
  58. // may call Free once to release the memory earlier. No reads will be possible
  59. // on the Snapshot after Free is called on it, however any open read
  60. // transactions will not be interrupted.
  61. //
  62. // See sqlitex.Pool.GetSnapshot for a helper function for automatically keeping
  63. // an open read transaction on a set aside connection until a Snapshot is GC'd.
  64. //
  65. // The following must be true for this function to succeed:
  66. //
  67. // - The schema of conn must be a WAL mode database.
  68. //
  69. // - There must not be any transaction open on schema of conn.
  70. //
  71. // - At least one transaction must have been written to the current WAL file
  72. // since it was created on disk (by any connection). You can run the following
  73. // SQL to ensure that a WAL file has been created.
  74. //
  75. // BEGIN IMMEDIATE;
  76. // COMMIT;
  77. //
  78. // https://www.sqlite.org/c3ref/snapshot_get.html
  79. func (conn *Conn) GetSnapshot(schema string) (*Snapshot, func(), error) {
  80. var s Snapshot
  81. if schema == "" || schema == "main" {
  82. s.schema = cmain
  83. } else {
  84. s.schema = C.CString(schema)
  85. }
  86. endRead, err := conn.disableAutoCommitMode()
  87. if err != nil {
  88. return nil, nil, err
  89. }
  90. res := C.sqlite3_snapshot_get(conn.conn, s.schema, &s.ptr)
  91. if res != 0 {
  92. endRead()
  93. return nil, nil, reserr("Conn.CreateSnapshot", "", "", res)
  94. }
  95. runtime.SetFinalizer(&s, func(s *Snapshot) {
  96. s.Free()
  97. })
  98. return &s, endRead, nil
  99. }
  100. // Free destroys a Snapshot. Free is not threadsafe but may be called more than
  101. // once. However, it is not necessary to call Free on a Snapshot returned by
  102. // conn.GetSnapshot or pool.GetSnapshot as these set a finalizer that calls
  103. // free which will be run automatically by the GC in a finalizer. However if it
  104. // is guaranteed that a Snapshot will never be used again, calling Free will
  105. // allow memory to be freed earlier.
  106. //
  107. // A Snapshot may become unavailable for reads before Free is called if the WAL
  108. // is checkpointed into the DB past the point of the Snapshot.
  109. //
  110. // https://www.sqlite.org/c3ref/snapshot_free.html
  111. func (s *Snapshot) Free() {
  112. if s.ptr == nil {
  113. return
  114. }
  115. C.sqlite3_snapshot_free(s.ptr)
  116. if s.schema != cmain {
  117. C.free(unsafe.Pointer(s.schema))
  118. }
  119. s.ptr = nil
  120. }
  121. // CompareAges returns whether s1 is older, newer or the same age as s2. Age
  122. // refers to writes on the database, not time since creation.
  123. //
  124. // If s is older than s2, a negative number is returned. If s and s2 are the
  125. // same age, zero is returned. If s is newer than s2, a positive number is
  126. // returned.
  127. //
  128. // The result is valid only if both of the following are true:
  129. //
  130. // - The two snapshot handles are associated with the same database file.
  131. //
  132. // - Both of the Snapshots were obtained since the last time the wal file was
  133. // deleted.
  134. //
  135. // https://www.sqlite.org/c3ref/snapshot_cmp.html
  136. func (s *Snapshot) CompareAges(s2 *Snapshot) int {
  137. return int(C.sqlite3_snapshot_cmp(s.ptr, s2.ptr))
  138. }
  139. // StartSnapshotRead starts a new read transaction on conn such that the read
  140. // transaction refers to historical Snapshot s, rather than the most recent
  141. // change to the database.
  142. //
  143. // There must be no open transaction on conn. Free must not have been called on
  144. // s prior to or during this function call.
  145. //
  146. // If err is nil, then endRead is a function that will end the read transaction
  147. // and return conn to its original state. Until endRead is called, no writes
  148. // may occur on conn, and all reads on conn will refer to the Snapshot.
  149. //
  150. // https://www.sqlite.org/c3ref/snapshot_open.html
  151. func (conn *Conn) StartSnapshotRead(s *Snapshot) (endRead func(), err error) {
  152. endRead, err = conn.disableAutoCommitMode()
  153. if err != nil {
  154. return
  155. }
  156. res := C.sqlite3_snapshot_open(conn.conn, s.schema, s.ptr)
  157. if res != 0 {
  158. endRead()
  159. return nil, reserr("Conn.StartSnapshotRead", "", "", res)
  160. }
  161. return endRead, nil
  162. }
  163. // disableAutoCommitMode starts a read transaction with `BEGIN;`, disabling
  164. // autocommit mode, and returns a function which when called will end the read
  165. // transaction with `ROLLBACK;`, re-enabling autocommit mode.
  166. //
  167. // https://sqlite.org/c3ref/get_autocommit.html
  168. func (conn *Conn) disableAutoCommitMode() (func(), error) {
  169. begin := conn.Prep("BEGIN;")
  170. defer begin.Reset()
  171. if _, err := begin.Step(); err != nil {
  172. return nil, err
  173. }
  174. rollback := conn.Prep("ROLLBACK;")
  175. return func() {
  176. defer rollback.Reset()
  177. rollback.Step()
  178. }, nil
  179. }