motion-yapper 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +63 -0
  6. data/README.md +16 -0
  7. data/Rakefile +20 -0
  8. data/app/app_delegate.rb +5 -0
  9. data/lib/yapper.rb +30 -0
  10. data/lib/yapper/attachment.rb +48 -0
  11. data/lib/yapper/bson.rb +20 -0
  12. data/lib/yapper/config.rb +18 -0
  13. data/lib/yapper/db.rb +151 -0
  14. data/lib/yapper/document.rb +54 -0
  15. data/lib/yapper/document/attachment.rb +26 -0
  16. data/lib/yapper/document/callbacks.rb +86 -0
  17. data/lib/yapper/document/persistance.rb +171 -0
  18. data/lib/yapper/document/relation.rb +84 -0
  19. data/lib/yapper/document/selection.rb +100 -0
  20. data/lib/yapper/extensions.rb +80 -0
  21. data/lib/yapper/log.rb +5 -0
  22. data/lib/yapper/sync.rb +134 -0
  23. data/lib/yapper/sync/data.rb +12 -0
  24. data/lib/yapper/sync/event.rb +194 -0
  25. data/lib/yapper/sync/queue.rb +164 -0
  26. data/lib/yapper/timestamps.rb +16 -0
  27. data/lib/yapper/version.rb +3 -0
  28. data/lib/yapper/yapper.rb +2 -0
  29. data/spec/helpers/time_helper.rb +3 -0
  30. data/spec/integration/all_spec.rb +40 -0
  31. data/spec/integration/callback_spec.rb +87 -0
  32. data/spec/integration/db_spec.rb +40 -0
  33. data/spec/integration/find_spec.rb +51 -0
  34. data/spec/integration/persistance_spec.rb +118 -0
  35. data/spec/integration/relation_spec.rb +174 -0
  36. data/spec/integration/sync_spec.rb +42 -0
  37. data/spec/integration/timestamps_spec.rb +34 -0
  38. data/spec/integration/types_spec.rb +23 -0
  39. data/spec/integration/when_spec.rb +29 -0
  40. data/spec/integration/where_spec.rb +132 -0
  41. data/vendor/Podfile.lock +24 -0
  42. data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPClient.h +641 -0
  43. data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPClient.m +1396 -0
  44. data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPRequestOperation.h +133 -0
  45. data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPRequestOperation.m +327 -0
  46. data/vendor/Pods/AFNetworking/AFNetworking/AFImageRequestOperation.h +113 -0
  47. data/vendor/Pods/AFNetworking/AFNetworking/AFImageRequestOperation.m +321 -0
  48. data/vendor/Pods/AFNetworking/AFNetworking/AFJSONRequestOperation.h +71 -0
  49. data/vendor/Pods/AFNetworking/AFNetworking/AFJSONRequestOperation.m +150 -0
  50. data/vendor/Pods/AFNetworking/AFNetworking/AFNetworkActivityIndicatorManager.h +75 -0
  51. data/vendor/Pods/AFNetworking/AFNetworking/AFNetworkActivityIndicatorManager.m +157 -0
  52. data/vendor/Pods/AFNetworking/AFNetworking/AFNetworking.h +43 -0
  53. data/vendor/Pods/AFNetworking/AFNetworking/AFPropertyListRequestOperation.h +68 -0
  54. data/vendor/Pods/AFNetworking/AFNetworking/AFPropertyListRequestOperation.m +143 -0
  55. data/vendor/Pods/AFNetworking/AFNetworking/AFURLConnectionOperation.h +370 -0
  56. data/vendor/Pods/AFNetworking/AFNetworking/AFURLConnectionOperation.m +848 -0
  57. data/vendor/Pods/AFNetworking/AFNetworking/AFXMLRequestOperation.h +89 -0
  58. data/vendor/Pods/AFNetworking/AFNetworking/AFXMLRequestOperation.m +167 -0
  59. data/vendor/Pods/AFNetworking/AFNetworking/UIImageView+AFNetworking.h +78 -0
  60. data/vendor/Pods/AFNetworking/AFNetworking/UIImageView+AFNetworking.m +191 -0
  61. data/vendor/Pods/AFNetworking/LICENSE +19 -0
  62. data/vendor/Pods/AFNetworking/README.md +208 -0
  63. data/vendor/Pods/BuildHeaders/AFNetworking/AFHTTPClient.h +641 -0
  64. data/vendor/Pods/BuildHeaders/AFNetworking/AFHTTPRequestOperation.h +133 -0
  65. data/vendor/Pods/BuildHeaders/AFNetworking/AFImageRequestOperation.h +113 -0
  66. data/vendor/Pods/BuildHeaders/AFNetworking/AFJSONRequestOperation.h +71 -0
  67. data/vendor/Pods/BuildHeaders/AFNetworking/AFNetworkActivityIndicatorManager.h +75 -0
  68. data/vendor/Pods/BuildHeaders/AFNetworking/AFNetworking.h +43 -0
  69. data/vendor/Pods/BuildHeaders/AFNetworking/AFPropertyListRequestOperation.h +68 -0
  70. data/vendor/Pods/BuildHeaders/AFNetworking/AFURLConnectionOperation.h +370 -0
  71. data/vendor/Pods/BuildHeaders/AFNetworking/AFXMLRequestOperation.h +89 -0
  72. data/vendor/Pods/BuildHeaders/AFNetworking/UIImageView+AFNetworking.h +78 -0
  73. data/vendor/Pods/BuildHeaders/CocoaLumberjack/ContextFilterLogFormatter.h +65 -0
  74. data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDASLLogger.h +41 -0
  75. data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDAbstractDatabaseLogger.h +102 -0
  76. data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDFileLogger.h +334 -0
  77. data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDLog.h +601 -0
  78. data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDTTYLogger.h +167 -0
  79. data/vendor/Pods/BuildHeaders/CocoaLumberjack/DispatchQueueLogFormatter.h +116 -0
  80. data/vendor/Pods/BuildHeaders/NSData+MD5Digest/NSData+MD5Digest.h +18 -0
  81. data/vendor/Pods/BuildHeaders/Reachability/Reachability.h +109 -0
  82. data/vendor/Pods/BuildHeaders/YapDatabase/YapCache.h +90 -0
  83. data/vendor/Pods/BuildHeaders/YapDatabase/YapCollectionKey.h +20 -0
  84. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabase.h +547 -0
  85. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseConnection.h +447 -0
  86. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseConnectionState.h +29 -0
  87. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseDefaults.h +37 -0
  88. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtension.h +15 -0
  89. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtensionConnection.h +11 -0
  90. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtensionPrivate.h +440 -0
  91. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtensionTransaction.h +11 -0
  92. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredView.h +108 -0
  93. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredViewConnection.h +12 -0
  94. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredViewPrivate.h +19 -0
  95. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredViewTransaction.h +39 -0
  96. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearch.h +89 -0
  97. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchConnection.h +32 -0
  98. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchPrivate.h +69 -0
  99. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchSnippetOptions.h +79 -0
  100. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchTransaction.h +68 -0
  101. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseLogging.h +158 -0
  102. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseManager.h +17 -0
  103. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabasePrivate.h +424 -0
  104. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseQuery.h +42 -0
  105. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndex.h +100 -0
  106. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexConnection.h +33 -0
  107. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexPrivate.h +73 -0
  108. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexSetup.h +33 -0
  109. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexTransaction.h +58 -0
  110. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseStatement.h +13 -0
  111. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseString.h +121 -0
  112. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseTransaction.h +541 -0
  113. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseView.h +186 -0
  114. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewChange.h +272 -0
  115. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewChangePrivate.h +94 -0
  116. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewConnection.h +115 -0
  117. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewMappings.h +825 -0
  118. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewMappingsPrivate.h +72 -0
  119. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewOptions.h +56 -0
  120. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewPage.h +36 -0
  121. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewPageMetadata.h +27 -0
  122. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewPrivate.h +153 -0
  123. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewRangeOptions.h +330 -0
  124. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewRangeOptionsPrivate.h +17 -0
  125. data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewTransaction.h +447 -0
  126. data/vendor/Pods/BuildHeaders/YapDatabase/YapMemoryTable.h +74 -0
  127. data/vendor/Pods/BuildHeaders/YapDatabase/YapNull.h +17 -0
  128. data/vendor/Pods/BuildHeaders/YapDatabase/YapSet.h +41 -0
  129. data/vendor/Pods/BuildHeaders/YapDatabase/YapTouch.h +15 -0
  130. data/vendor/Pods/CocoaLumberjack/LICENSE.txt +18 -0
  131. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDASLLogger.h +41 -0
  132. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDASLLogger.m +99 -0
  133. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDAbstractDatabaseLogger.h +102 -0
  134. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDAbstractDatabaseLogger.m +727 -0
  135. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDFileLogger.h +334 -0
  136. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDFileLogger.m +1353 -0
  137. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDLog.h +601 -0
  138. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDLog.m +1083 -0
  139. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDTTYLogger.h +167 -0
  140. data/vendor/Pods/CocoaLumberjack/Lumberjack/DDTTYLogger.m +1479 -0
  141. data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/ContextFilterLogFormatter.h +65 -0
  142. data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/ContextFilterLogFormatter.m +191 -0
  143. data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/DispatchQueueLogFormatter.h +116 -0
  144. data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/DispatchQueueLogFormatter.m +251 -0
  145. data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/README.txt +7 -0
  146. data/vendor/Pods/CocoaLumberjack/README.markdown +37 -0
  147. data/vendor/Pods/Headers/AFNetworking/AFHTTPClient.h +641 -0
  148. data/vendor/Pods/Headers/AFNetworking/AFHTTPRequestOperation.h +133 -0
  149. data/vendor/Pods/Headers/AFNetworking/AFImageRequestOperation.h +113 -0
  150. data/vendor/Pods/Headers/AFNetworking/AFJSONRequestOperation.h +71 -0
  151. data/vendor/Pods/Headers/AFNetworking/AFNetworkActivityIndicatorManager.h +75 -0
  152. data/vendor/Pods/Headers/AFNetworking/AFNetworking.h +43 -0
  153. data/vendor/Pods/Headers/AFNetworking/AFPropertyListRequestOperation.h +68 -0
  154. data/vendor/Pods/Headers/AFNetworking/AFURLConnectionOperation.h +370 -0
  155. data/vendor/Pods/Headers/AFNetworking/AFXMLRequestOperation.h +89 -0
  156. data/vendor/Pods/Headers/AFNetworking/UIImageView+AFNetworking.h +78 -0
  157. data/vendor/Pods/Headers/CocoaLumberjack/ContextFilterLogFormatter.h +65 -0
  158. data/vendor/Pods/Headers/CocoaLumberjack/DDASLLogger.h +41 -0
  159. data/vendor/Pods/Headers/CocoaLumberjack/DDAbstractDatabaseLogger.h +102 -0
  160. data/vendor/Pods/Headers/CocoaLumberjack/DDFileLogger.h +334 -0
  161. data/vendor/Pods/Headers/CocoaLumberjack/DDLog.h +601 -0
  162. data/vendor/Pods/Headers/CocoaLumberjack/DDTTYLogger.h +167 -0
  163. data/vendor/Pods/Headers/CocoaLumberjack/DispatchQueueLogFormatter.h +116 -0
  164. data/vendor/Pods/Headers/NSData+MD5Digest/NSData+MD5Digest.h +18 -0
  165. data/vendor/Pods/Headers/Reachability/Reachability.h +109 -0
  166. data/vendor/Pods/Headers/YapDatabase/YapCache.h +90 -0
  167. data/vendor/Pods/Headers/YapDatabase/YapCollectionKey.h +20 -0
  168. data/vendor/Pods/Headers/YapDatabase/YapDatabase.h +547 -0
  169. data/vendor/Pods/Headers/YapDatabase/YapDatabaseConnection.h +447 -0
  170. data/vendor/Pods/Headers/YapDatabase/YapDatabaseConnectionState.h +29 -0
  171. data/vendor/Pods/Headers/YapDatabase/YapDatabaseDefaults.h +37 -0
  172. data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtension.h +15 -0
  173. data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtensionConnection.h +11 -0
  174. data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtensionPrivate.h +440 -0
  175. data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtensionTransaction.h +11 -0
  176. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredView.h +108 -0
  177. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredViewConnection.h +12 -0
  178. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredViewPrivate.h +19 -0
  179. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredViewTransaction.h +39 -0
  180. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearch.h +89 -0
  181. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchConnection.h +32 -0
  182. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchPrivate.h +69 -0
  183. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchSnippetOptions.h +79 -0
  184. data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchTransaction.h +68 -0
  185. data/vendor/Pods/Headers/YapDatabase/YapDatabaseLogging.h +158 -0
  186. data/vendor/Pods/Headers/YapDatabase/YapDatabaseManager.h +17 -0
  187. data/vendor/Pods/Headers/YapDatabase/YapDatabasePrivate.h +424 -0
  188. data/vendor/Pods/Headers/YapDatabase/YapDatabaseQuery.h +42 -0
  189. data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndex.h +100 -0
  190. data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexConnection.h +33 -0
  191. data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexPrivate.h +73 -0
  192. data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexSetup.h +33 -0
  193. data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexTransaction.h +58 -0
  194. data/vendor/Pods/Headers/YapDatabase/YapDatabaseStatement.h +13 -0
  195. data/vendor/Pods/Headers/YapDatabase/YapDatabaseString.h +121 -0
  196. data/vendor/Pods/Headers/YapDatabase/YapDatabaseTransaction.h +541 -0
  197. data/vendor/Pods/Headers/YapDatabase/YapDatabaseView.h +186 -0
  198. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewChange.h +272 -0
  199. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewChangePrivate.h +94 -0
  200. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewConnection.h +115 -0
  201. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewMappings.h +825 -0
  202. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewMappingsPrivate.h +72 -0
  203. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewOptions.h +56 -0
  204. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewPage.h +36 -0
  205. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewPageMetadata.h +27 -0
  206. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewPrivate.h +153 -0
  207. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewRangeOptions.h +330 -0
  208. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewRangeOptionsPrivate.h +17 -0
  209. data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewTransaction.h +447 -0
  210. data/vendor/Pods/Headers/YapDatabase/YapMemoryTable.h +74 -0
  211. data/vendor/Pods/Headers/YapDatabase/YapNull.h +17 -0
  212. data/vendor/Pods/Headers/YapDatabase/YapSet.h +41 -0
  213. data/vendor/Pods/Headers/YapDatabase/YapTouch.h +15 -0
  214. data/vendor/Pods/Headers/____Pods-AFNetworking-prefix.h +17 -0
  215. data/vendor/Pods/Headers/____Pods-CocoaLumberjack-prefix.h +5 -0
  216. data/vendor/Pods/Headers/____Pods-NSData+MD5Digest-prefix.h +5 -0
  217. data/vendor/Pods/Headers/____Pods-Reachability-prefix.h +5 -0
  218. data/vendor/Pods/Headers/____Pods-YapDatabase-prefix.h +5 -0
  219. data/vendor/Pods/Headers/____Pods-environment.h +38 -0
  220. data/vendor/Pods/Manifest.lock +24 -0
  221. data/vendor/Pods/NSData+MD5Digest/NSData+MD5Digest/NSData+MD5Digest.h +18 -0
  222. data/vendor/Pods/NSData+MD5Digest/NSData+MD5Digest/NSData+MD5Digest.m +39 -0
  223. data/vendor/Pods/NSData+MD5Digest/README.md +11 -0
  224. data/vendor/Pods/Pods-AFNetworking-Private.xcconfig +5 -0
  225. data/vendor/Pods/Pods-AFNetworking-dummy.m +5 -0
  226. data/vendor/Pods/Pods-AFNetworking-prefix.pch +17 -0
  227. data/vendor/Pods/Pods-AFNetworking.xcconfig +1 -0
  228. data/vendor/Pods/Pods-Acknowledgements.markdown +96 -0
  229. data/vendor/Pods/Pods-Acknowledgements.plist +142 -0
  230. data/vendor/Pods/Pods-CocoaLumberjack-Private.xcconfig +5 -0
  231. data/vendor/Pods/Pods-CocoaLumberjack-dummy.m +5 -0
  232. data/vendor/Pods/Pods-CocoaLumberjack-prefix.pch +5 -0
  233. data/vendor/Pods/Pods-CocoaLumberjack.xcconfig +0 -0
  234. data/vendor/Pods/Pods-NSData+MD5Digest-Private.xcconfig +5 -0
  235. data/vendor/Pods/Pods-NSData+MD5Digest-dummy.m +5 -0
  236. data/vendor/Pods/Pods-NSData+MD5Digest-prefix.pch +5 -0
  237. data/vendor/Pods/Pods-NSData+MD5Digest.xcconfig +0 -0
  238. data/vendor/Pods/Pods-Reachability-Private.xcconfig +5 -0
  239. data/vendor/Pods/Pods-Reachability-dummy.m +5 -0
  240. data/vendor/Pods/Pods-Reachability-prefix.pch +5 -0
  241. data/vendor/Pods/Pods-Reachability.xcconfig +1 -0
  242. data/vendor/Pods/Pods-YapDatabase-Private.xcconfig +5 -0
  243. data/vendor/Pods/Pods-YapDatabase-dummy.m +5 -0
  244. data/vendor/Pods/Pods-YapDatabase-prefix.pch +5 -0
  245. data/vendor/Pods/Pods-YapDatabase.xcconfig +1 -0
  246. data/vendor/Pods/Pods-dummy.m +5 -0
  247. data/vendor/Pods/Pods-environment.h +38 -0
  248. data/vendor/Pods/Pods-resources.sh +68 -0
  249. data/vendor/Pods/Pods.bridgesupport +5096 -0
  250. data/vendor/Pods/Pods.xcconfig +5 -0
  251. data/vendor/Pods/Pods.xcodeproj/project.pbxproj +5536 -0
  252. data/vendor/Pods/Reachability/LICENCE.txt +24 -0
  253. data/vendor/Pods/Reachability/README.md +65 -0
  254. data/vendor/Pods/Reachability/Reachability.h +109 -0
  255. data/vendor/Pods/Reachability/Reachability.m +527 -0
  256. data/vendor/Pods/YapDatabase/LICENSE.txt +18 -0
  257. data/vendor/Pods/YapDatabase/README.md +30 -0
  258. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/Internal/YapDatabaseFilteredViewPrivate.h +19 -0
  259. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredView.h +108 -0
  260. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredView.m +175 -0
  261. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewConnection.h +12 -0
  262. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewConnection.m +41 -0
  263. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewTransaction.h +39 -0
  264. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewTransaction.m +966 -0
  265. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/Internal/YapDatabaseFullTextSearchPrivate.h +69 -0
  266. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearch.h +89 -0
  267. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearch.m +146 -0
  268. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchConnection.h +32 -0
  269. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchConnection.m +298 -0
  270. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchSnippetOptions.h +79 -0
  271. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchSnippetOptions.m +95 -0
  272. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchTransaction.h +68 -0
  273. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchTransaction.m +1352 -0
  274. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/Internal/YapDatabaseExtensionPrivate.h +440 -0
  275. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtension.h +15 -0
  276. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtension.m +58 -0
  277. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionConnection.h +11 -0
  278. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionConnection.m +46 -0
  279. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionTransaction.h +11 -0
  280. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionTransaction.m +180 -0
  281. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/Internal/YapDatabaseSecondaryIndexPrivate.h +73 -0
  282. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndex.h +100 -0
  283. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndex.m +149 -0
  284. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexConnection.h +33 -0
  285. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexConnection.m +330 -0
  286. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexSetup.h +33 -0
  287. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexSetup.m +184 -0
  288. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexTransaction.h +58 -0
  289. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexTransaction.m +1166 -0
  290. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewChangePrivate.h +94 -0
  291. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewMappingsPrivate.h +72 -0
  292. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPage.h +36 -0
  293. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPage.mm +296 -0
  294. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPageMetadata.h +27 -0
  295. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPageMetadata.m +28 -0
  296. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPrivate.h +153 -0
  297. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewRangeOptionsPrivate.h +17 -0
  298. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewChange.h +272 -0
  299. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewChange.m +2494 -0
  300. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewMappings.h +825 -0
  301. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewMappings.m +1567 -0
  302. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewRangeOptions.h +330 -0
  303. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewRangeOptions.m +141 -0
  304. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseView.h +186 -0
  305. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseView.m +191 -0
  306. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewConnection.h +115 -0
  307. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewConnection.m +897 -0
  308. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewOptions.h +56 -0
  309. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewOptions.m +27 -0
  310. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewTransaction.h +447 -0
  311. data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewTransaction.m +4505 -0
  312. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapCache.h +90 -0
  313. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapCache.m +453 -0
  314. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseConnectionState.h +29 -0
  315. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseConnectionState.m +48 -0
  316. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseDefaults.h +37 -0
  317. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseDefaults.m +83 -0
  318. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseLogging.h +158 -0
  319. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseLogging.m +73 -0
  320. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseManager.h +17 -0
  321. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseManager.m +56 -0
  322. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabasePrivate.h +424 -0
  323. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseStatement.h +13 -0
  324. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseStatement.m +26 -0
  325. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseString.h +121 -0
  326. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapMemoryTable.h +74 -0
  327. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapMemoryTable.m +603 -0
  328. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapNull.h +17 -0
  329. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapNull.m +31 -0
  330. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapTouch.h +15 -0
  331. data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapTouch.m +31 -0
  332. data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapCollectionKey.h +20 -0
  333. data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapCollectionKey.m +194 -0
  334. data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapDatabaseQuery.h +42 -0
  335. data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapDatabaseQuery.m +96 -0
  336. data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapSet.h +41 -0
  337. data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapSet.m +82 -0
  338. data/vendor/Pods/YapDatabase/YapDatabase/YapDatabase.h +547 -0
  339. data/vendor/Pods/YapDatabase/YapDatabase/YapDatabase.m +2022 -0
  340. data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseConnection.h +447 -0
  341. data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseConnection.m +3874 -0
  342. data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseTransaction.h +541 -0
  343. data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseTransaction.m +5282 -0
  344. data/vendor/YapDatabaseRubyMotion/YapDatabaseRubyMotion.bridgesupport +16 -0
  345. data/vendor/YapDatabaseRubyMotion/YapDatabaseRubyMotion.h +13 -0
  346. data/vendor/YapDatabaseRubyMotion/YapDatabaseRubyMotion.m +20 -0
  347. data/yapper.gemspec +24 -0
  348. metadata +458 -0
@@ -0,0 +1,447 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ @class YapDatabase;
4
+ @class YapDatabaseReadTransaction;
5
+ @class YapDatabaseReadWriteTransaction;
6
+
7
+ /**
8
+ * Welcome to YapDatabase!
9
+ *
10
+ * The project page has a wealth of documentation if you have any questions.
11
+ * https://github.com/yaptv/YapDatabase
12
+ *
13
+ * If you're new to the project you may want to visit the wiki.
14
+ * https://github.com/yaptv/YapDatabase/wiki
15
+ *
16
+ * From a single YapDatabase instance you can create multiple connections.
17
+ * Each connection is thread-safe and may be used concurrently with other connections.
18
+ *
19
+ * Multiple connections can simultaneously read from the database.
20
+ * Multiple connections can simultaneously read from the database while another connection is modifying the database.
21
+ * For example, the main thread could be reading from the database via connection A,
22
+ * while a background thread is writing to the database via connection B.
23
+ *
24
+ * However, only a single connection may be writing to the database at any one time.
25
+ * This is an inherent limitation of the underlying sqlite database.
26
+ *
27
+ * A connection instance is thread-safe, and operates by serializing access to itself.
28
+ * Thus you can share a single connection between multiple threads.
29
+ * But for conncurrent access between multiple threads you must use multiple connections.
30
+ **/
31
+
32
+ typedef enum {
33
+ YapDatabaseConnectionFlushMemoryLevelNone = 0,
34
+ YapDatabaseConnectionFlushMemoryLevelMild = 1,
35
+ YapDatabaseConnectionFlushMemoryLevelModerate = 2,
36
+ YapDatabaseConnectionFlushMemoryLevelFull = 3,
37
+ } YapDatabaseConnectionFlushMemoryLevel;
38
+
39
+ typedef enum {
40
+ YapDatabasePolicyContainment = 0,
41
+ YapDatabasePolicyShare = 1,
42
+ YapDatabasePolicyCopy = 2,
43
+ } YapDatabasePolicy;
44
+
45
+
46
+ @interface YapDatabaseConnection : NSObject
47
+
48
+ /**
49
+ * A database connection maintains a strong reference to its parent.
50
+ *
51
+ * This is to enforce the following core architecture rule:
52
+ * A database instance cannot be deallocated if a corresponding connection is stil alive.
53
+ **/
54
+ @property (nonatomic, strong, readonly) YapDatabase *database;
55
+
56
+ /**
57
+ * The optional name property assists in debugging.
58
+ * It is only used internally for log statements.
59
+ **/
60
+ @property (atomic, copy, readwrite) NSString *name;
61
+
62
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
63
+ #pragma mark Cache
64
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
65
+
66
+ /**
67
+ * Each database connection maintains an independent cache of deserialized objects.
68
+ * This reduces disk IO and the overhead of the deserialization process.
69
+ * You can optionally configure the cache size, or disable it completely.
70
+ *
71
+ * The cache is properly kept in sync with the atomic snapshot architecture of the database system.
72
+ *
73
+ * You can configure the objectCache at any time, including within readBlocks or readWriteBlocks.
74
+ * To disable the object cache entirely, set objectCacheEnabled to NO.
75
+ * To use an inifinite cache size, set the objectCacheLimit to zero.
76
+ *
77
+ * By default the objectCache is enabled and has a limit of 250.
78
+ *
79
+ * New connections will inherit the default values set by the parent database object.
80
+ * Thus the default values for new connection instances are configurable.
81
+ *
82
+ * @see YapDatabase defaultObjectCacheEnabled
83
+ * @see YapDatabase defaultObjectCacheLimit
84
+ *
85
+ * Also see the wiki for a bit more info:
86
+ * https://github.com/yaptv/YapDatabase/wiki/Cache
87
+ **/
88
+ @property (atomic, assign, readwrite) BOOL objectCacheEnabled;
89
+ @property (atomic, assign, readwrite) NSUInteger objectCacheLimit;
90
+
91
+ /**
92
+ * Each database connection maintains an independent cache of deserialized metadata.
93
+ * This reduces disk IO and the overhead of the deserialization process.
94
+ * You can optionally configure the cache size, or disable it completely.
95
+ *
96
+ * The cache is properly kept in sync with the atomic snapshot architecture of the database system.
97
+ *
98
+ * You can configure the metadataCache at any time, including within readBlocks or readWriteBlocks.
99
+ * To disable the metadata cache entirely, set metadataCacheEnabled to NO.
100
+ * To use an inifinite cache size, set the metadataCacheLimit to zero.
101
+ *
102
+ * By default the metadataCache is enabled and has a limit of 500.
103
+ *
104
+ * New connections will inherit the default values set by the parent database object.
105
+ * Thus the default values for new connection instances are configurable.
106
+ *
107
+ * @see YapDatabase defaultMetadataCacheEnabled
108
+ * @see YapDatabase defaultMetadataCacheLimit
109
+ *
110
+ * Also see the wiki for a bit more info:
111
+ * https://github.com/yaptv/YapDatabase/wiki/Cache
112
+ **/
113
+ @property (atomic, assign, readwrite) BOOL metadataCacheEnabled;
114
+ @property (atomic, assign, readwrite) NSUInteger metadataCacheLimit;
115
+
116
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
117
+ #pragma mark Policy
118
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
119
+
120
+ /**
121
+ * YapDatabase can use various optimizations to reduce overhead and memory footprint.
122
+ * The policy properties allow you to opt in to these optimizations when ready.
123
+ *
124
+ * The default value is YapDatabasePolicyContainment.
125
+ *
126
+ * It is the slowest, but also the safest policy.
127
+ * The other policies require a little more work, and little deeper understanding.
128
+ *
129
+ * These optimizations are discussed extensively in the wiki article "Performance Pro":
130
+ * https://github.com/yaptv/YapDatabase/wiki/Performance-Pro
131
+ **/
132
+ @property (atomic, assign, readwrite) YapDatabasePolicy objectPolicy;
133
+ @property (atomic, assign, readwrite) YapDatabasePolicy metadataPolicy;
134
+
135
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
136
+ #pragma mark State
137
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
138
+
139
+ /**
140
+ * The snapshot number is the internal synchronization state primitive for the connection.
141
+ * It's generally only useful for database internals,
142
+ * but it can sometimes come in handy for general debugging of your app.
143
+ *
144
+ * The snapshot is a simple 64-bit number that gets incremented upon every readwrite transaction
145
+ * that makes modifications to the database. Due to the concurrent architecture of YapDatabase,
146
+ * there may be multiple concurrent connections that are inspecting the database at similar times,
147
+ * yet they are looking at slightly different "snapshots" of the database.
148
+ *
149
+ * The snapshot number may thus be inspected to determine (in a general fashion) what state the connection
150
+ * is in compared with other connections.
151
+ *
152
+ * You may also query YapDatabase.snapshot to determine the most up-to-date snapshot among all connections.
153
+ *
154
+ * Example:
155
+ *
156
+ * YapDatabase *database = [[YapDatabase alloc] init...];
157
+ * database.snapshot; // returns zero
158
+ *
159
+ * YapDatabaseConnection *connection1 = [database newConnection];
160
+ * YapDatabaseConnection *connection2 = [database newConnection];
161
+ *
162
+ * connection1.snapshot; // returns zero
163
+ * connection2.snapshot; // returns zero
164
+ *
165
+ * [connection1 readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){
166
+ * [transaction setObject:objectA forKey:keyA];
167
+ * }];
168
+ *
169
+ * database.snapshot; // returns 1
170
+ * connection1.snapshot; // returns 1
171
+ * connection2.snapshot; // returns 1
172
+ *
173
+ * [connection1 asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){
174
+ * [transaction setObject:objectB forKey:keyB];
175
+ * [NSThread sleepForTimeInterval:1.0]; // sleep for 1 second
176
+ *
177
+ * connection1.snapshot; // returns 1 (we know it will turn into 2 once the transaction completes)
178
+ * } completion:^{
179
+ *
180
+ * connection1.snapshot; // returns 2
181
+ * }];
182
+ *
183
+ * [connection2 asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction){
184
+ * [NSThread sleepForTimeInterval:5.0]; // sleep for 5 seconds
185
+ *
186
+ * connection2.snapshot; // returns 1. See why?
187
+ * }];
188
+ *
189
+ * It's because connection2 started its transaction when the database was in snapshot 1.
190
+ * Thus, for the duration of its transaction, the database remains in that state.
191
+ *
192
+ * However, once connection2 completes its transaction, it will automatically update itself to snapshot 2.
193
+ *
194
+ * In general, the snapshot is primarily for internal use.
195
+ * However, it may come in handy for some tricky edge-case bugs (why doesn't my connection see that other commit?)
196
+ **/
197
+ @property (atomic, assign, readonly) uint64_t snapshot;
198
+
199
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
200
+ #pragma mark Transactions
201
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
202
+
203
+ /**
204
+ * Read-only access to the database.
205
+ *
206
+ * The given block can run concurrently with sibling connections,
207
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
208
+ *
209
+ * The only time this method ever blocks is if another thread is currently using this connection instance
210
+ * to execute a readBlock or readWriteBlock. Recall that you may create multiple connections for concurrent access.
211
+ *
212
+ * This method is synchronous.
213
+ **/
214
+ - (void)readWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block;
215
+
216
+ /**
217
+ * Read-write access to the database.
218
+ *
219
+ * Only a single read-write block can execute among all sibling connections.
220
+ * Thus this method may block if another sibling connection is currently executing a read-write block.
221
+ **/
222
+ - (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block;
223
+
224
+ /**
225
+ * Read-only access to the database.
226
+ *
227
+ * The given block can run concurrently with sibling connections,
228
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
229
+ *
230
+ * This method is asynchronous.
231
+ **/
232
+ - (void)asyncReadWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block;
233
+
234
+ /**
235
+ * Read-only access to the database.
236
+ *
237
+ * The given block can run concurrently with sibling connections,
238
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
239
+ *
240
+ * This method is asynchronous.
241
+ *
242
+ * An optional completion block may be used.
243
+ * The completionBlock will be invoked on the main thread (dispatch_get_main_queue()).
244
+ **/
245
+ - (void)asyncReadWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block
246
+ completionBlock:(dispatch_block_t)completionBlock;
247
+
248
+ /**
249
+ * Read-only access to the database.
250
+ *
251
+ * The given block can run concurrently with sibling connections,
252
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
253
+ *
254
+ * This method is asynchronous.
255
+ *
256
+ * An optional completion block may be used.
257
+ * Additionally the dispatch_queue to invoke the completion block may also be specified.
258
+ * If NULL, dispatch_get_main_queue() is automatically used.
259
+ **/
260
+ - (void)asyncReadWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block
261
+ completionBlock:(dispatch_block_t)completionBlock
262
+ completionQueue:(dispatch_queue_t)completionQueue;
263
+
264
+ /**
265
+ * Read-write access to the database.
266
+ *
267
+ * Only a single read-write block can execute among all sibling connections.
268
+ * Thus this method may block if another sibling connection is currently executing a read-write block.
269
+ *
270
+ * This method is asynchronous.
271
+ **/
272
+ - (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block;
273
+
274
+ /**
275
+ * Read-write access to the database.
276
+ *
277
+ * Only a single read-write block can execute among all sibling connections.
278
+ * Thus the execution of the block may be delayed if another sibling connection
279
+ * is currently executing a read-write block.
280
+ *
281
+ * This method is asynchronous.
282
+ *
283
+ * An optional completion block may be used.
284
+ * The completionBlock will be invoked on the main thread (dispatch_get_main_queue()).
285
+ **/
286
+ - (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
287
+ completionBlock:(dispatch_block_t)completionBlock;
288
+
289
+ /**
290
+ * Read-write access to the database.
291
+ *
292
+ * Only a single read-write block can execute among all sibling connections.
293
+ * Thus the execution of the block may be delayed if another sibling connection
294
+ * is currently executing a read-write block.
295
+ *
296
+ * This method is asynchronous.
297
+ *
298
+ * An optional completion block may be used.
299
+ * Additionally the dispatch_queue to invoke the completion block may also be specified.
300
+ * If NULL, dispatch_get_main_queue() is automatically used.
301
+ **/
302
+ - (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
303
+ completionBlock:(dispatch_block_t)completionBlock
304
+ completionQueue:(dispatch_queue_t)completionQueue;
305
+
306
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
307
+ #pragma mark Long-Lived Transactions
308
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
309
+
310
+ /**
311
+ * Invoke this method to start a long-lived read-only transaction.
312
+ * This allows you to effectively create a stable state for the connection.
313
+ * This is most often used for connections that service the main thread for UI data.
314
+ *
315
+ * For a complete discussion, please see the wiki page:
316
+ * https://github.com/yaptv/YapDatabase/wiki/LongLivedReadTransactions
317
+ **/
318
+ - (NSArray *)beginLongLivedReadTransaction;
319
+ - (NSArray *)endLongLivedReadTransaction;
320
+
321
+ - (BOOL)isInLongLivedReadTransaction;
322
+
323
+ /**
324
+ * A long-lived read-only transaction is most often setup on a connection that is designed to be read-only.
325
+ * But sometimes we forget, and a read-write transaction gets added that uses the read-only connection.
326
+ * This will implicitly end the long-lived read-only transaction. Oops.
327
+ *
328
+ * This is a bug waiting to happen.
329
+ * And when it does happen, it will be one of those bugs that's nearly impossible to reproduce.
330
+ * So its better to have an early warning system to help you fix the bug before it occurs.
331
+ *
332
+ * For a complete discussion, please see the wiki page:
333
+ * https://github.com/yaptv/YapDatabase/wiki/LongLivedReadTransactions
334
+ *
335
+ * In debug mode (#if DEBUG), these exceptions are turned ON by default.
336
+ * In non-debug mode (#if !DEBUG), these exceptions are turned OFF by default.
337
+ **/
338
+ - (void)enableExceptionsForImplicitlyEndingLongLivedReadTransaction;
339
+ - (void)disableExceptionsForImplicitlyEndingLongLivedReadTransaction;
340
+
341
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
342
+ #pragma mark Changesets
343
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
344
+
345
+ /**
346
+ * A YapDatabaseModifiedNotification is posted for every readwrite transaction that makes changes to the database.
347
+ *
348
+ * Given one or more notifications, these methods allow you to easily
349
+ * query to see if a change affects a given collection, key, or combinary.
350
+ *
351
+ * This is most often used in conjunction with longLivedReadTransactions.
352
+ *
353
+ * For more information on longLivedReadTransaction, see the following wiki article:
354
+ * https://github.com/yaptv/YapDatabase/wiki/LongLivedReadTransactions
355
+ **/
356
+
357
+ // Query for any change to a collection
358
+
359
+ - (BOOL)hasChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications;
360
+ - (BOOL)hasObjectChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications;
361
+ - (BOOL)hasMetadataChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications;
362
+
363
+ // Query for a change to a particular key/collection tuple
364
+
365
+ - (BOOL)hasChangeForKey:(NSString *)key
366
+ inCollection:(NSString *)collection
367
+ inNotifications:(NSArray *)notifications;
368
+
369
+ - (BOOL)hasObjectChangeForKey:(NSString *)key
370
+ inCollection:(NSString *)collection
371
+ inNotifications:(NSArray *)notifications;
372
+
373
+ - (BOOL)hasMetadataChangeForKey:(NSString *)key
374
+ inCollection:(NSString *)collection
375
+ inNotifications:(NSArray *)notifications;
376
+
377
+ // Query for a change to a particular set of keys in a collection
378
+
379
+ - (BOOL)hasChangeForAnyKeys:(NSSet *)keys
380
+ inCollection:(NSString *)collection
381
+ inNotifications:(NSArray *)notifications;
382
+
383
+ - (BOOL)hasObjectChangeForAnyKeys:(NSSet *)keys
384
+ inCollection:(NSString *)collection
385
+ inNotifications:(NSArray *)notifications;
386
+
387
+ - (BOOL)hasMetadataChangeForAnyKeys:(NSSet *)keys
388
+ inCollection:(NSString *)collection
389
+ inNotifications:(NSArray *)notifications;
390
+
391
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
392
+ #pragma mark Extensions
393
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
394
+
395
+ /**
396
+ * Creates or fetches the extension with the given name.
397
+ * If this connection has not yet initialized the proper extension connection, it is done automatically.
398
+ *
399
+ * @return
400
+ * A subclass of YapDatabaseExtensionConnection,
401
+ * according to the type of extension registered under the given name.
402
+ *
403
+ * One must register an extension with the database before it can be accessed from within connections or transactions.
404
+ * After registration everything works automatically using just the registered extension name.
405
+ *
406
+ * @see YapDatabase registerExtension:withName:
407
+ **/
408
+ - (id)extension:(NSString *)extensionName;
409
+ - (id)ext:(NSString *)extensionName; // <-- Shorthand (same as extension: method)
410
+
411
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
412
+ #pragma mark Memory
413
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
414
+
415
+ /**
416
+ * This method may be used to flush the internal caches used by the connection,
417
+ * as well as flushing pre-compiled sqlite statements.
418
+ * Depending upon how often you use the database connection,
419
+ * you may want to be more or less aggressive on how much stuff you flush.
420
+ *
421
+ * YapDatabaseConnectionFlushMemoryLevelNone (0):
422
+ * No-op. Doesn't flush any caches or anything from internal memory.
423
+ *
424
+ * YapDatabaseConnectionFlushMemoryLevelMild (1):
425
+ * Flushes the object cache and metadata cache.
426
+ *
427
+ * YapDatabaseConnectionFlushMemoryLevelModerate (2):
428
+ * Mild plus drops less common pre-compiled sqlite statements.
429
+ *
430
+ * YapDatabaseConnectionFlushMemoryLevelFull (3):
431
+ * Full flush of all caches and removes all pre-compiled sqlite statements.
432
+ **/
433
+ - (void)flushMemoryWithLevel:(int)level;
434
+
435
+ #if TARGET_OS_IPHONE
436
+ /**
437
+ * When a UIApplicationDidReceiveMemoryWarningNotification is received,
438
+ * the code automatically invokes flushMemoryWithLevel and passes this set level.
439
+ *
440
+ * The default value is YapDatabaseConnectionFlushMemoryLevelMild.
441
+ *
442
+ * @see flushMemoryWithLevel:
443
+ **/
444
+ @property (atomic, assign, readwrite) int autoFlushMemoryLevel;
445
+ #endif
446
+
447
+ @end
@@ -0,0 +1,3874 @@
1
+ #import "YapDatabaseConnection.h"
2
+ #import "YapDatabaseConnectionState.h"
3
+ #import "YapDatabasePrivate.h"
4
+ #import "YapDatabaseExtensionPrivate.h"
5
+
6
+ #import "YapCollectionKey.h"
7
+ #import "YapCache.h"
8
+ #import "YapTouch.h"
9
+ #import "YapNull.h"
10
+ #import "YapSet.h"
11
+
12
+ #import "YapDatabaseString.h"
13
+ #import "YapDatabaseLogging.h"
14
+
15
+ #import <objc/runtime.h>
16
+ #import <libkern/OSAtomic.h>
17
+
18
+ #if ! __has_feature(objc_arc)
19
+ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
20
+ #endif
21
+
22
+ /**
23
+ * Define log level for this file: OFF, ERROR, WARN, INFO, VERBOSE
24
+ * See YapDatabaseLogging.h for more information.
25
+ **/
26
+ #if DEBUG
27
+ static const int ydbLogLevel = YDB_LOG_LEVEL_INFO;
28
+ #else
29
+ static const int ydbLogLevel = YDB_LOG_LEVEL_WARN;
30
+ #endif
31
+
32
+ #define DEFAULT_OBJECT_CACHE_LIMIT 250
33
+ #define DEFAULT_METADATA_CACHE_LIMIT 500
34
+
35
+
36
+ @implementation YapDatabaseConnection {
37
+ @private
38
+
39
+ sqlite3_stmt *beginTransactionStatement;
40
+ sqlite3_stmt *commitTransactionStatement;
41
+ sqlite3_stmt *rollbackTransactionStatement;
42
+
43
+ sqlite3_stmt *yapGetDataForKeyStatement; // Against "yap" database, for internal use
44
+ sqlite3_stmt *yapSetDataForKeyStatement; // Against "yap" database, for internal use
45
+ sqlite3_stmt *yapRemoveExtensionStatement; // Against "yap" database, for internal use
46
+
47
+ sqlite3_stmt *getCollectionCountStatement;
48
+ sqlite3_stmt *getKeyCountForCollectionStatement;
49
+ sqlite3_stmt *getKeyCountForAllStatement;
50
+ sqlite3_stmt *getCountForRowidStatement;
51
+ sqlite3_stmt *getRowidForKeyStatement;
52
+ sqlite3_stmt *getKeyForRowidStatement;
53
+ sqlite3_stmt *getKeyDataForRowidStatement;
54
+ sqlite3_stmt *getKeyMetadataForRowidStatement;
55
+ sqlite3_stmt *getDataForRowidStatement;
56
+ sqlite3_stmt *getAllForRowidStatement;
57
+ sqlite3_stmt *getDataForKeyStatement;
58
+ sqlite3_stmt *getMetadataForKeyStatement;
59
+ sqlite3_stmt *getAllForKeyStatement;
60
+ sqlite3_stmt *insertForRowidStatement;
61
+ sqlite3_stmt *updateAllForRowidStatement;
62
+ sqlite3_stmt *updateMetadataForRowidStatement;
63
+ sqlite3_stmt *removeForRowidStatement;
64
+ sqlite3_stmt *removeCollectionStatement;
65
+ sqlite3_stmt *removeAllStatement;
66
+ sqlite3_stmt *enumerateCollectionsStatement;
67
+ sqlite3_stmt *enumerateCollectionsForKeyStatement;
68
+ sqlite3_stmt *enumerateKeysInCollectionStatement;
69
+ sqlite3_stmt *enumerateKeysInAllCollectionsStatement;
70
+ sqlite3_stmt *enumerateKeysAndMetadataInCollectionStatement;
71
+ sqlite3_stmt *enumerateKeysAndMetadataInAllCollectionsStatement;
72
+ sqlite3_stmt *enumerateKeysAndObjectsInCollectionStatement;
73
+ sqlite3_stmt *enumerateKeysAndObjectsInAllCollectionsStatement;
74
+ sqlite3_stmt *enumerateRowsInCollectionStatement;
75
+ sqlite3_stmt *enumerateRowsInAllCollectionsStatement;
76
+
77
+ OSSpinLock lock;
78
+ BOOL writeQueueSuspended;
79
+ BOOL activeReadWriteTransaction;
80
+ }
81
+
82
+ + (void)load
83
+ {
84
+ static BOOL loaded = NO;
85
+ if (!loaded)
86
+ {
87
+ // Method swizzle:
88
+ // Both 'extension:' and 'ext:' are designed to be the same method (with ext: shorthand for extension:).
89
+ // So swap out the ext: method to point to extension:.
90
+
91
+ Method extMethod = class_getInstanceMethod([self class], @selector(ext:));
92
+ IMP extensionIMP = class_getMethodImplementation([self class], @selector(extension:));
93
+
94
+ method_setImplementation(extMethod, extensionIMP);
95
+ loaded = YES;
96
+ }
97
+ }
98
+
99
+ - (id)initWithDatabase:(YapDatabase *)inDatabase
100
+ {
101
+ if ((self = [super init]))
102
+ {
103
+ database = inDatabase;
104
+ connectionQueue = dispatch_queue_create("YapDatabaseConnection", NULL);
105
+
106
+ IsOnConnectionQueueKey = &IsOnConnectionQueueKey;
107
+ dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, IsOnConnectionQueueKey, NULL);
108
+
109
+ #if DEBUG
110
+ throwExceptionsForImplicitlyEndingLongLivedReadTransaction = YES;
111
+ #else
112
+ throwExceptionsForImplicitlyEndingLongLivedReadTransaction = NO;
113
+ #endif
114
+
115
+ pendingChangesets = [[NSMutableArray alloc] init];
116
+ processedChangesets = [[NSMutableArray alloc] init];
117
+
118
+ sharedKeySetForInternalChangeset = [NSDictionary sharedKeySetForKeys:[self internalChangesetKeys]];
119
+ sharedKeySetForExternalChangeset = [NSDictionary sharedKeySetForKeys:[self externalChangesetKeys]];
120
+ sharedKeySetForExtensions = [NSDictionary sharedKeySetForKeys:@[]];
121
+
122
+ extensions = [[NSMutableDictionary alloc] init];
123
+
124
+ YapDatabaseDefaults *defaults = [database defaults];
125
+
126
+ if (defaults.objectCacheEnabled)
127
+ {
128
+ objectCacheLimit = defaults.objectCacheLimit;
129
+ objectCache = [[YapCache alloc] initWithKeyClass:[YapCollectionKey class]];
130
+ objectCache.countLimit = objectCacheLimit;
131
+ }
132
+ if (defaults.metadataCacheEnabled)
133
+ {
134
+ metadataCacheLimit = defaults.metadataCacheLimit;
135
+ metadataCache = [[YapCache alloc] initWithKeyClass:[YapCollectionKey class]];
136
+ metadataCache.countLimit = metadataCacheLimit;
137
+ }
138
+
139
+ #if TARGET_OS_IPHONE
140
+ self.autoFlushMemoryLevel = defaults.autoFlushMemoryLevel;
141
+ #endif
142
+
143
+ lock = OS_SPINLOCK_INIT;
144
+
145
+ db = [database connectionPoolDequeue];
146
+ if (db == NULL)
147
+ {
148
+ // Open the database connection.
149
+ //
150
+ // We use SQLITE_OPEN_NOMUTEX to use the multi-thread threading mode,
151
+ // as we will be serializing access to the connection externally.
152
+
153
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
154
+
155
+ int status = sqlite3_open_v2([database.databasePath UTF8String], &db, flags, NULL);
156
+ if (status != SQLITE_OK)
157
+ {
158
+ // Sometimes the open function returns a db to allow us to query it for the error message
159
+ if (db) {
160
+ YDBLogWarn(@"Error opening database: %d %s", status, sqlite3_errmsg(db));
161
+ }
162
+ else {
163
+ YDBLogError(@"Error opening database: %d", status);
164
+ }
165
+ }
166
+ else
167
+ {
168
+ // Disable autocheckpointing.
169
+ //
170
+ // YapDatabase has its own optimized checkpointing algorithm built-in.
171
+ // It knows the state of every active connection for the database,
172
+ // so it can invoke the checkpoint methods at the precise time
173
+ // in which a checkpoint can be most effective.
174
+
175
+ sqlite3_wal_autocheckpoint(db, 0);
176
+
177
+ // Edge case workaround.
178
+ //
179
+ // If there's an active checkpoint operation,
180
+ // then the very first time we call sqlite3_prepare_v2 on this db,
181
+ // we sometimes get a SQLITE_BUSY error.
182
+ //
183
+ // This only seems to happen once, and only during the very first use of the db instance.
184
+ // I'm still tyring to figure out exactly why this is.
185
+ // For now I'm setting a busy timeout as a temporary workaround.
186
+ //
187
+ // Note: I've also tested setting a busy_handler which logs the number of times its called.
188
+ // And in all my testing, I've only seen the busy_handler called once per db.
189
+
190
+ sqlite3_busy_timeout(db, 50); // milliseconds
191
+ }
192
+ }
193
+
194
+ #if TARGET_OS_IPHONE
195
+ [[NSNotificationCenter defaultCenter] addObserver:self
196
+ selector:@selector(didReceiveMemoryWarning:)
197
+ name:UIApplicationDidReceiveMemoryWarningNotification
198
+ object:nil];
199
+ #endif
200
+ }
201
+ return self;
202
+ }
203
+
204
+ /**
205
+ * This method will be invoked before any other method.
206
+ * It can be used to do any setup that may be needed.
207
+ **/
208
+ - (void)prepare
209
+ {
210
+ // This method is invoked from our connectionQueue, within the snapshotQueue.
211
+ // Don't do anything expensive here that might tie up the snapshotQueue.
212
+
213
+ snapshot = [database snapshot];
214
+ registeredExtensions = [database registeredExtensions];
215
+ registeredTables = [database registeredTables];
216
+ extensionsOrder = [database extensionsOrder];
217
+
218
+ extensionsReady = ([registeredExtensions count] == 0);
219
+ }
220
+
221
+ - (void)dealloc
222
+ {
223
+ YDBLogVerbose(@"Dealloc <YapDatabaseConnection %p: databaseName=%@>",
224
+ self, [database.databasePath lastPathComponent]);
225
+
226
+ dispatch_block_t block = ^{ @autoreleasepool {
227
+
228
+ if (longLivedReadTransaction) {
229
+ [self postReadTransaction:longLivedReadTransaction];
230
+ longLivedReadTransaction = nil;
231
+ }
232
+ }};
233
+
234
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
235
+ block();
236
+ else
237
+ dispatch_sync(connectionQueue, block);
238
+
239
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
240
+
241
+ [extensions removeAllObjects];
242
+
243
+ sqlite_finalize_null(&beginTransactionStatement);
244
+ sqlite_finalize_null(&commitTransactionStatement);
245
+ sqlite_finalize_null(&rollbackTransactionStatement);
246
+
247
+ sqlite_finalize_null(&yapGetDataForKeyStatement);
248
+ sqlite_finalize_null(&yapSetDataForKeyStatement);
249
+ sqlite_finalize_null(&yapRemoveExtensionStatement);
250
+
251
+ sqlite_finalize_null(&getCollectionCountStatement);
252
+ sqlite_finalize_null(&getKeyCountForCollectionStatement);
253
+ sqlite_finalize_null(&getKeyCountForAllStatement);
254
+ sqlite_finalize_null(&getCountForRowidStatement);
255
+ sqlite_finalize_null(&getRowidForKeyStatement);
256
+ sqlite_finalize_null(&getKeyForRowidStatement);
257
+ sqlite_finalize_null(&getKeyDataForRowidStatement);
258
+ sqlite_finalize_null(&getKeyMetadataForRowidStatement);
259
+ sqlite_finalize_null(&getDataForRowidStatement);
260
+ sqlite_finalize_null(&getAllForRowidStatement);
261
+ sqlite_finalize_null(&getDataForKeyStatement);
262
+ sqlite_finalize_null(&insertForRowidStatement);
263
+ sqlite_finalize_null(&updateAllForRowidStatement);
264
+ sqlite_finalize_null(&updateMetadataForRowidStatement);
265
+ sqlite_finalize_null(&removeForRowidStatement);
266
+ sqlite_finalize_null(&removeCollectionStatement);
267
+ sqlite_finalize_null(&removeAllStatement);
268
+ sqlite_finalize_null(&enumerateCollectionsStatement);
269
+ sqlite_finalize_null(&enumerateCollectionsForKeyStatement);
270
+ sqlite_finalize_null(&enumerateKeysInCollectionStatement);
271
+ sqlite_finalize_null(&enumerateKeysInAllCollectionsStatement);
272
+ sqlite_finalize_null(&enumerateKeysAndMetadataInCollectionStatement);
273
+ sqlite_finalize_null(&enumerateKeysAndMetadataInAllCollectionsStatement);
274
+ sqlite_finalize_null(&enumerateKeysAndObjectsInCollectionStatement);
275
+ sqlite_finalize_null(&enumerateKeysAndObjectsInAllCollectionsStatement);
276
+ sqlite_finalize_null(&enumerateRowsInCollectionStatement);
277
+ sqlite_finalize_null(&enumerateRowsInAllCollectionsStatement);
278
+
279
+ if (db)
280
+ {
281
+ if (![database connectionPoolEnqueue:db])
282
+ {
283
+ int status = sqlite3_close(db);
284
+ if (status != SQLITE_OK)
285
+ {
286
+ YDBLogError(@"Error in sqlite_close: %d %s", status, sqlite3_errmsg(db));
287
+ }
288
+ }
289
+
290
+ db = NULL;
291
+ }
292
+
293
+ [database removeConnection:self];
294
+
295
+ #if !OS_OBJECT_USE_OBJC
296
+ if (connectionQueue)
297
+ dispatch_release(connectionQueue);
298
+ #endif
299
+ }
300
+
301
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
302
+ #pragma mark Memory
303
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
304
+
305
+ /**
306
+ * Optional override hook.
307
+ * Don't forget to invoke [super _flushMemoryWithLevel:level].
308
+ **/
309
+ - (void)_flushMemoryWithLevel:(int)level
310
+ {
311
+ if (level >= YapDatabaseConnectionFlushMemoryLevelMild)
312
+ {
313
+ [objectCache removeAllObjects];
314
+ [metadataCache removeAllObjects];
315
+ }
316
+
317
+ if (level >= YapDatabaseConnectionFlushMemoryLevelModerate)
318
+ {
319
+ sqlite_finalize_null(&yapRemoveExtensionStatement);
320
+ sqlite_finalize_null(&rollbackTransactionStatement);
321
+
322
+ sqlite_finalize_null(&getCollectionCountStatement);
323
+ sqlite_finalize_null(&getKeyCountForCollectionStatement);
324
+ sqlite_finalize_null(&getKeyCountForAllStatement);
325
+ sqlite_finalize_null(&getCountForRowidStatement);
326
+ sqlite_finalize_null(&getKeyForRowidStatement);
327
+ sqlite_finalize_null(&getKeyDataForRowidStatement);
328
+ sqlite_finalize_null(&getKeyMetadataForRowidStatement);
329
+ sqlite_finalize_null(&getDataForRowidStatement);
330
+ sqlite_finalize_null(&getAllForRowidStatement);
331
+ sqlite_finalize_null(&updateMetadataForRowidStatement);
332
+ sqlite_finalize_null(&removeForRowidStatement);
333
+ sqlite_finalize_null(&removeCollectionStatement);
334
+ sqlite_finalize_null(&removeAllStatement);
335
+ sqlite_finalize_null(&enumerateCollectionsStatement);
336
+ sqlite_finalize_null(&enumerateCollectionsForKeyStatement);
337
+ sqlite_finalize_null(&enumerateKeysInCollectionStatement);
338
+ sqlite_finalize_null(&enumerateKeysInAllCollectionsStatement);
339
+ sqlite_finalize_null(&enumerateKeysAndMetadataInCollectionStatement);
340
+ sqlite_finalize_null(&enumerateKeysAndMetadataInAllCollectionsStatement);
341
+ sqlite_finalize_null(&enumerateKeysAndObjectsInCollectionStatement);
342
+ sqlite_finalize_null(&enumerateKeysAndObjectsInAllCollectionsStatement);
343
+ sqlite_finalize_null(&enumerateRowsInCollectionStatement);
344
+ sqlite_finalize_null(&enumerateRowsInAllCollectionsStatement);
345
+ }
346
+
347
+ if (level >= YapDatabaseConnectionFlushMemoryLevelFull)
348
+ {
349
+ sqlite_finalize_null(&yapGetDataForKeyStatement);
350
+ sqlite_finalize_null(&yapSetDataForKeyStatement);
351
+ sqlite_finalize_null(&beginTransactionStatement);
352
+ sqlite_finalize_null(&commitTransactionStatement);
353
+
354
+ sqlite_finalize_null(&getRowidForKeyStatement);
355
+ sqlite_finalize_null(&getDataForKeyStatement);
356
+ sqlite_finalize_null(&insertForRowidStatement);
357
+ sqlite_finalize_null(&updateAllForRowidStatement);
358
+ }
359
+
360
+ [extensions enumerateKeysAndObjectsUsingBlock:^(id extNameObj, id extConnectionObj, BOOL *stop) {
361
+
362
+ [(YapDatabaseExtensionConnection *)extConnectionObj _flushMemoryWithLevel:level];
363
+ }];
364
+ }
365
+
366
+ /**
367
+ * This method may be used to flush the internal caches used by the connection,
368
+ * as well as flushing pre-compiled sqlite statements.
369
+ * Depending upon how often you use the database connection,
370
+ * you may want to be more or less aggressive on how much stuff you flush.
371
+ *
372
+ * YapDatabaseConnectionFlushMemoryLevelNone (0):
373
+ * No-op. Doesn't flush any caches or anything from internal memory.
374
+ *
375
+ * YapDatabaseConnectionFlushMemoryLevelMild (1):
376
+ * Flushes the object cache and metadata cache.
377
+ *
378
+ * YapDatabaseConnectionFlushMemoryLevelModerate (2):
379
+ * Mild plus drops less common pre-compiled sqlite statements.
380
+ *
381
+ * YapDatabaseConnectionFlushMemoryLevelFull (3):
382
+ * Full flush of all caches and removes all pre-compiled sqlite statements.
383
+ **/
384
+ - (void)flushMemoryWithLevel:(int)level
385
+ {
386
+ dispatch_block_t block = ^{
387
+
388
+ [self _flushMemoryWithLevel:level];
389
+ };
390
+
391
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
392
+ block();
393
+ else
394
+ dispatch_sync(connectionQueue, block);
395
+ }
396
+
397
+ #if TARGET_OS_IPHONE
398
+ - (void)didReceiveMemoryWarning:(NSNotification *)notification
399
+ {
400
+ [self flushMemoryWithLevel:[self autoFlushMemoryLevel]];
401
+ }
402
+ #endif
403
+
404
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
405
+ #pragma mark Properties
406
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
407
+
408
+ @synthesize database = database;
409
+ @synthesize name = _name;
410
+
411
+ //@synthesize connectionQueue = connectionQueue;
412
+
413
+ #if TARGET_OS_IPHONE
414
+ @synthesize autoFlushMemoryLevel;
415
+ #endif
416
+
417
+ - (BOOL)objectCacheEnabled
418
+ {
419
+ __block BOOL result = NO;
420
+
421
+ dispatch_block_t block = ^{
422
+ result = (objectCache != nil);
423
+ };
424
+
425
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
426
+ block();
427
+ else
428
+ dispatch_sync(connectionQueue, block);
429
+
430
+ return result;
431
+ }
432
+
433
+ - (void)setObjectCacheEnabled:(BOOL)flag
434
+ {
435
+ dispatch_block_t block = ^{
436
+
437
+ if (flag) // Enabled
438
+ {
439
+ if (objectCache == nil)
440
+ {
441
+ objectCache = [[YapCache alloc] initWithKeyClass:[YapCollectionKey class]];
442
+ objectCache.countLimit = objectCacheLimit;
443
+ }
444
+ }
445
+ else // Disabled
446
+ {
447
+ objectCache = nil;
448
+ }
449
+ };
450
+
451
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
452
+ block();
453
+ else
454
+ dispatch_async(connectionQueue, block);
455
+ }
456
+
457
+ - (NSUInteger)objectCacheLimit
458
+ {
459
+ __block NSUInteger result = 0;
460
+
461
+ dispatch_block_t block = ^{
462
+ result = objectCacheLimit;
463
+ };
464
+
465
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
466
+ block();
467
+ else
468
+ dispatch_sync(connectionQueue, block);
469
+
470
+ return result;
471
+ }
472
+
473
+ - (void)setObjectCacheLimit:(NSUInteger)newObjectCacheLimit
474
+ {
475
+ dispatch_block_t block = ^{
476
+
477
+ if (objectCacheLimit != newObjectCacheLimit)
478
+ {
479
+ objectCacheLimit = newObjectCacheLimit;
480
+
481
+ if (objectCache == nil)
482
+ {
483
+ return; // Limit changed, but objectCache is still disabled
484
+ }
485
+ else
486
+ {
487
+ objectCache.countLimit = objectCacheLimit;
488
+ }
489
+ }
490
+ };
491
+
492
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
493
+ block();
494
+ else
495
+ dispatch_async(connectionQueue, block);
496
+ }
497
+
498
+ - (BOOL)metadataCacheEnabled
499
+ {
500
+ __block BOOL result = NO;
501
+
502
+ dispatch_block_t block = ^{
503
+ result = (metadataCache != nil);
504
+ };
505
+
506
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
507
+ block();
508
+ else
509
+ dispatch_sync(connectionQueue, block);
510
+
511
+ return result;
512
+ }
513
+
514
+ - (void)setMetadataCacheEnabled:(BOOL)flag
515
+ {
516
+ dispatch_block_t block = ^{
517
+
518
+ if (flag) // Enabled
519
+ {
520
+ if (metadataCache == nil)
521
+ {
522
+ metadataCache = [[YapCache alloc] initWithKeyClass:[YapCollectionKey class]];
523
+ metadataCache.countLimit = metadataCacheLimit;
524
+ }
525
+ }
526
+ else // Disabled
527
+ {
528
+ metadataCache = nil;
529
+ }
530
+ };
531
+
532
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
533
+ block();
534
+ else
535
+ dispatch_async(connectionQueue, block);
536
+ }
537
+
538
+ - (NSUInteger)metadataCacheLimit
539
+ {
540
+ __block NSUInteger result = 0;
541
+
542
+ dispatch_block_t block = ^{
543
+ result = metadataCacheLimit;
544
+ };
545
+
546
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
547
+ block();
548
+ else
549
+ dispatch_sync(connectionQueue, block);
550
+
551
+ return result;
552
+ }
553
+
554
+ - (void)setMetadataCacheLimit:(NSUInteger)newMetadataCacheLimit
555
+ {
556
+ dispatch_block_t block = ^{
557
+
558
+ if (metadataCacheLimit != newMetadataCacheLimit)
559
+ {
560
+ metadataCacheLimit = newMetadataCacheLimit;
561
+
562
+ if (metadataCache == nil)
563
+ {
564
+ return; // Limit changed but metadataCache still disabled
565
+ }
566
+ else
567
+ {
568
+ metadataCache.countLimit = metadataCacheLimit;
569
+ }
570
+ }
571
+ };
572
+
573
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
574
+ block();
575
+ else
576
+ dispatch_async(connectionQueue, block);
577
+ }
578
+
579
+ - (YapDatabasePolicy)objectPolicy
580
+ {
581
+ __block YapDatabasePolicy policy = YapDatabasePolicyShare;
582
+
583
+ dispatch_block_t block = ^{
584
+ policy = objectPolicy;
585
+ };
586
+
587
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
588
+ block();
589
+ else
590
+ dispatch_sync(connectionQueue, block);
591
+
592
+ return policy;
593
+ }
594
+
595
+ - (void)setObjectPolicy:(YapDatabasePolicy)newObjectPolicy
596
+ {
597
+ dispatch_block_t block = ^{
598
+
599
+ // sanity check
600
+ switch (newObjectPolicy)
601
+ {
602
+ case YapDatabasePolicyContainment :
603
+ case YapDatabasePolicyShare :
604
+ case YapDatabasePolicyCopy : objectPolicy = newObjectPolicy; break;
605
+ default : objectPolicy = YapDatabasePolicyContainment;
606
+ }
607
+ };
608
+
609
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
610
+ block();
611
+ else
612
+ dispatch_async(connectionQueue, block);
613
+ }
614
+
615
+ - (YapDatabasePolicy)metadataPolicy
616
+ {
617
+ __block YapDatabasePolicy policy = YapDatabasePolicyShare;
618
+
619
+ dispatch_block_t block = ^{
620
+ policy = metadataPolicy;
621
+ };
622
+
623
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
624
+ block();
625
+ else
626
+ dispatch_sync(connectionQueue, block);
627
+
628
+ return policy;
629
+ }
630
+
631
+ - (void)setMetadataPolicy:(YapDatabasePolicy)newMetadataPolicy
632
+ {
633
+ dispatch_block_t block = ^{
634
+
635
+ // sanity check
636
+ switch (newMetadataPolicy)
637
+ {
638
+ case YapDatabasePolicyContainment :
639
+ case YapDatabasePolicyShare :
640
+ case YapDatabasePolicyCopy : metadataPolicy = newMetadataPolicy; break;
641
+ default : metadataPolicy = YapDatabasePolicyContainment;
642
+ }
643
+ };
644
+
645
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
646
+ block();
647
+ else
648
+ dispatch_async(connectionQueue, block);
649
+ }
650
+
651
+ - (uint64_t)snapshot
652
+ {
653
+ __block uint64_t result = 0;
654
+
655
+ dispatch_block_t block = ^{
656
+ result = snapshot;
657
+ };
658
+
659
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
660
+ block();
661
+ else
662
+ dispatch_sync(connectionQueue, block);
663
+
664
+ return result;
665
+ }
666
+
667
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
668
+ #pragma mark Statements
669
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
670
+
671
+ - (sqlite3_stmt *)beginTransactionStatement
672
+ {
673
+ if (beginTransactionStatement == NULL)
674
+ {
675
+ char *stmt = "BEGIN TRANSACTION;";
676
+ int stmtLen = (int)strlen(stmt);
677
+
678
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &beginTransactionStatement, NULL);
679
+ if (status != SQLITE_OK)
680
+ {
681
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
682
+ }
683
+ }
684
+
685
+ return beginTransactionStatement;
686
+ }
687
+
688
+ - (sqlite3_stmt *)commitTransactionStatement
689
+ {
690
+ if (commitTransactionStatement == NULL)
691
+ {
692
+ char *stmt = "COMMIT TRANSACTION;";
693
+ int stmtLen = (int)strlen(stmt);
694
+
695
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &commitTransactionStatement, NULL);
696
+ if (status != SQLITE_OK)
697
+ {
698
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
699
+ }
700
+ }
701
+
702
+ return commitTransactionStatement;
703
+ }
704
+
705
+ - (sqlite3_stmt *)rollbackTransactionStatement
706
+ {
707
+ if (rollbackTransactionStatement == NULL)
708
+ {
709
+ char *stmt = "ROLLBACK TRANSACTION;";
710
+ int stmtLen = (int)strlen(stmt);
711
+
712
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &rollbackTransactionStatement, NULL);
713
+ if (status != SQLITE_OK)
714
+ {
715
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
716
+ }
717
+ }
718
+
719
+ return rollbackTransactionStatement;
720
+ }
721
+
722
+ - (sqlite3_stmt *)yapGetDataForKeyStatement
723
+ {
724
+ if (yapGetDataForKeyStatement == NULL)
725
+ {
726
+ char *stmt = "SELECT \"data\" FROM \"yap2\" WHERE \"extension\" = ? AND \"key\" = ?;";
727
+ int stmtLen = (int)strlen(stmt);
728
+
729
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &yapGetDataForKeyStatement, NULL);
730
+ if (status != SQLITE_OK)
731
+ {
732
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
733
+ }
734
+ }
735
+
736
+ return yapGetDataForKeyStatement;
737
+ }
738
+
739
+ - (sqlite3_stmt *)yapSetDataForKeyStatement
740
+ {
741
+ if (yapSetDataForKeyStatement == NULL)
742
+ {
743
+ char *stmt = "INSERT OR REPLACE INTO \"yap2\" (\"extension\", \"key\", \"data\") VALUES (?, ?, ?);";
744
+ int stmtLen = (int)strlen(stmt);
745
+
746
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &yapSetDataForKeyStatement, NULL);
747
+ if (status != SQLITE_OK)
748
+ {
749
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
750
+ }
751
+ }
752
+
753
+ return yapSetDataForKeyStatement;
754
+ }
755
+
756
+ - (sqlite3_stmt *)yapRemoveExtensionStatement
757
+ {
758
+ if (yapRemoveExtensionStatement == NULL)
759
+ {
760
+ char *stmt = "DELETE FROM \"yap2\" WHERE \"extension\" = ?;";
761
+ int stmtLen = (int)strlen(stmt);
762
+
763
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &yapRemoveExtensionStatement, NULL);
764
+ if (status != SQLITE_OK)
765
+ {
766
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
767
+ }
768
+ }
769
+
770
+ return yapRemoveExtensionStatement;
771
+ }
772
+
773
+ - (sqlite3_stmt *)getCollectionCountStatement
774
+ {
775
+ if (getCollectionCountStatement == NULL)
776
+ {
777
+ char *stmt = "SELECT COUNT(DISTINCT collection) AS NumberOfRows FROM \"database2\";";
778
+ int stmtLen = (int)strlen(stmt);
779
+
780
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getCollectionCountStatement, NULL);
781
+ if (status != SQLITE_OK)
782
+ {
783
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
784
+ }
785
+ }
786
+
787
+ return getCollectionCountStatement;
788
+ }
789
+
790
+ - (sqlite3_stmt *)getKeyCountForCollectionStatement
791
+ {
792
+ if (getKeyCountForCollectionStatement == NULL)
793
+ {
794
+ char *stmt = "SELECT COUNT(*) AS NumberOfRows FROM \"database2\" WHERE \"collection\" = ?;";
795
+ int stmtLen = (int)strlen(stmt);
796
+
797
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getKeyCountForCollectionStatement, NULL);
798
+ if (status != SQLITE_OK)
799
+ {
800
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
801
+ }
802
+ }
803
+
804
+ return getKeyCountForCollectionStatement;
805
+ }
806
+
807
+ - (sqlite3_stmt *)getKeyCountForAllStatement
808
+ {
809
+ if (getKeyCountForAllStatement == NULL)
810
+ {
811
+ char *stmt = "SELECT COUNT(*) AS NumberOfRows FROM \"database2\";";
812
+ int stmtLen = (int)strlen(stmt);
813
+
814
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getKeyCountForAllStatement, NULL);
815
+ if (status != SQLITE_OK)
816
+ {
817
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
818
+ }
819
+ }
820
+
821
+ return getKeyCountForAllStatement;
822
+ }
823
+
824
+ - (sqlite3_stmt *)getCountForRowidStatement
825
+ {
826
+ if (getCountForRowidStatement == NULL)
827
+ {
828
+ char *stmt = "SELECT COUNT(*) AS NumberOfRows FROM \"database2\" WHERE \"rowid\" = ?;";
829
+ int stmtLen = (int)strlen(stmt);
830
+
831
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getCountForRowidStatement, NULL);
832
+ if (status != SQLITE_OK)
833
+ {
834
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
835
+ }
836
+ }
837
+
838
+ return getCountForRowidStatement;
839
+ }
840
+
841
+ - (sqlite3_stmt *)getRowidForKeyStatement
842
+ {
843
+ if (getRowidForKeyStatement == NULL)
844
+ {
845
+ char *stmt = "SELECT \"rowid\" FROM \"database2\" WHERE \"collection\" = ? AND \"key\" = ?;";
846
+ int stmtLen = (int)strlen(stmt);
847
+
848
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getRowidForKeyStatement, NULL);
849
+ if (status != SQLITE_OK)
850
+ {
851
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
852
+ }
853
+ }
854
+
855
+ return getRowidForKeyStatement;
856
+ }
857
+
858
+ - (sqlite3_stmt *)getKeyForRowidStatement
859
+ {
860
+ if (getKeyForRowidStatement == NULL)
861
+ {
862
+ char *stmt = "SELECT \"collection\", \"key\" FROM \"database2\" WHERE \"rowid\" = ?;";
863
+ int stmtLen = (int)strlen(stmt);
864
+
865
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getKeyForRowidStatement, NULL);
866
+ if (status != SQLITE_OK)
867
+ {
868
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
869
+ }
870
+ }
871
+
872
+ return getKeyForRowidStatement;
873
+ }
874
+
875
+ - (sqlite3_stmt *)getKeyDataForRowidStatement
876
+ {
877
+ if (getKeyDataForRowidStatement == NULL)
878
+ {
879
+ char *stmt = "SELECT \"collection\", \"key\", \"data\" FROM \"database2\" WHERE \"rowid\" = ?;";
880
+ int stmtLen = (int)strlen(stmt);
881
+
882
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getKeyDataForRowidStatement, NULL);
883
+ if (status != SQLITE_OK)
884
+ {
885
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
886
+ }
887
+ }
888
+
889
+ return getKeyDataForRowidStatement;
890
+ }
891
+
892
+ - (sqlite3_stmt *)getKeyMetadataForRowidStatement
893
+ {
894
+ if (getKeyMetadataForRowidStatement == NULL)
895
+ {
896
+ char *stmt = "SELECT \"collection\", \"key\", \"metadata\" FROM \"database2\" WHERE \"rowid\" = ?;";
897
+ int stmtLen = (int)strlen(stmt);
898
+
899
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getKeyMetadataForRowidStatement, NULL);
900
+ if (status != SQLITE_OK)
901
+ {
902
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
903
+ }
904
+ }
905
+
906
+ return getKeyMetadataForRowidStatement;
907
+ }
908
+
909
+ - (sqlite3_stmt *)getDataForRowidStatement
910
+ {
911
+ if (getDataForRowidStatement == NULL)
912
+ {
913
+ char *stmt = "SELECT \"data\" FROM \"database2\" WHERE \"rowid\" = ?;";
914
+ int stmtLen = (int)strlen(stmt);
915
+
916
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getDataForRowidStatement, NULL);
917
+ if (status != SQLITE_OK)
918
+ {
919
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
920
+ }
921
+ }
922
+
923
+ return getDataForRowidStatement;
924
+ }
925
+
926
+ - (sqlite3_stmt *)getAllForRowidStatement
927
+ {
928
+ if (getAllForRowidStatement == NULL)
929
+ {
930
+ char *stmt = "SELECT \"collection\", \"key\", \"data\", \"metadata\" FROM \"database2\" WHERE \"rowid\" = ?;";
931
+ int stmtLen = (int)strlen(stmt);
932
+
933
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getAllForRowidStatement, NULL);
934
+ if (status != SQLITE_OK)
935
+ {
936
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
937
+ }
938
+ }
939
+
940
+ return getAllForRowidStatement;
941
+ }
942
+
943
+ - (sqlite3_stmt *)getDataForKeyStatement
944
+ {
945
+ if (getDataForKeyStatement == NULL)
946
+ {
947
+ char *stmt = "SELECT \"data\" FROM \"database2\" WHERE \"collection\" = ? AND \"key\" = ?;";
948
+ int stmtLen = (int)strlen(stmt);
949
+
950
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getDataForKeyStatement, NULL);
951
+ if (status != SQLITE_OK)
952
+ {
953
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
954
+ }
955
+ }
956
+
957
+ return getDataForKeyStatement;
958
+ }
959
+
960
+ - (sqlite3_stmt *)getMetadataForKeyStatement
961
+ {
962
+ if (getMetadataForKeyStatement == NULL)
963
+ {
964
+ char *stmt = "SELECT \"metadata\" FROM \"database2\" WHERE \"collection\" = ? AND \"key\" = ?;";
965
+ int stmtLen = (int)strlen(stmt);
966
+
967
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getMetadataForKeyStatement, NULL);
968
+ if (status != SQLITE_OK)
969
+ {
970
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
971
+ }
972
+ }
973
+
974
+ return getMetadataForKeyStatement;
975
+ }
976
+
977
+ - (sqlite3_stmt *)getAllForKeyStatement
978
+ {
979
+ if (getAllForKeyStatement == NULL)
980
+ {
981
+ char *stmt = "SELECT \"data\", \"metadata\" FROM \"database2\" WHERE \"collection\" = ? AND \"key\" = ?;";
982
+ int stmtLen = (int)strlen(stmt);
983
+
984
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &getAllForKeyStatement, NULL);
985
+ if (status != SQLITE_OK)
986
+ {
987
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
988
+ }
989
+ }
990
+
991
+ return getAllForKeyStatement;
992
+ }
993
+
994
+ - (sqlite3_stmt *)insertForRowidStatement
995
+ {
996
+ if (insertForRowidStatement == NULL)
997
+ {
998
+ char *stmt = "INSERT INTO \"database2\""
999
+ " (\"collection\", \"key\", \"data\", \"metadata\") VALUES (?, ?, ?, ?);";
1000
+ int stmtLen = (int)strlen(stmt);
1001
+
1002
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &insertForRowidStatement, NULL);
1003
+ if (status != SQLITE_OK)
1004
+ {
1005
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1006
+ }
1007
+ }
1008
+
1009
+ return insertForRowidStatement;
1010
+ }
1011
+
1012
+ - (sqlite3_stmt *)updateAllForRowidStatement
1013
+ {
1014
+ if (updateAllForRowidStatement == NULL)
1015
+ {
1016
+ char *stmt = "UPDATE \"database2\" SET \"data\" = ?, \"metadata\" = ? WHERE \"rowid\" = ?;";
1017
+ int stmtLen = (int)strlen(stmt);
1018
+
1019
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &updateAllForRowidStatement, NULL);
1020
+ if (status != SQLITE_OK)
1021
+ {
1022
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1023
+ }
1024
+ }
1025
+
1026
+ return updateAllForRowidStatement;
1027
+ }
1028
+
1029
+ - (sqlite3_stmt *)updateMetadataForRowidStatement
1030
+ {
1031
+ if (updateMetadataForRowidStatement == NULL)
1032
+ {
1033
+ char *stmt = "UPDATE \"database2\" SET \"metadata\" = ? WHERE \"rowid\" = ?;";
1034
+ int stmtLen = (int)strlen(stmt);
1035
+
1036
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &updateMetadataForRowidStatement, NULL);
1037
+ if (status != SQLITE_OK)
1038
+ {
1039
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1040
+ }
1041
+ }
1042
+
1043
+ return updateMetadataForRowidStatement;
1044
+ }
1045
+
1046
+ - (sqlite3_stmt *)removeForRowidStatement
1047
+ {
1048
+ if (removeForRowidStatement == NULL)
1049
+ {
1050
+ char *stmt = "DELETE FROM \"database2\" WHERE \"rowid\" = ?;";
1051
+ int stmtLen = (int)strlen(stmt);
1052
+
1053
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &removeForRowidStatement, NULL);
1054
+ if (status != SQLITE_OK)
1055
+ {
1056
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1057
+ }
1058
+ }
1059
+
1060
+ return removeForRowidStatement;
1061
+ }
1062
+
1063
+ - (sqlite3_stmt *)removeCollectionStatement
1064
+ {
1065
+ if (removeCollectionStatement == NULL)
1066
+ {
1067
+ char *stmt = "DELETE FROM \"database2\" WHERE \"collection\" = ?;";
1068
+ int stmtLen = (int)strlen(stmt);
1069
+
1070
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &removeCollectionStatement, NULL);
1071
+ if (status != SQLITE_OK)
1072
+ {
1073
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1074
+ }
1075
+ }
1076
+
1077
+ return removeCollectionStatement;
1078
+ }
1079
+
1080
+ - (sqlite3_stmt *)removeAllStatement
1081
+ {
1082
+ if (removeAllStatement == NULL)
1083
+ {
1084
+ char *stmt = "DELETE FROM \"database2\";";
1085
+ int stmtLen = (int)strlen(stmt);
1086
+
1087
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &removeAllStatement, NULL);
1088
+ if (status != SQLITE_OK)
1089
+ {
1090
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1091
+ }
1092
+ }
1093
+
1094
+ return removeAllStatement;
1095
+ }
1096
+
1097
+ - (sqlite3_stmt *)enumerateCollectionsStatement
1098
+ {
1099
+ if (enumerateCollectionsStatement == NULL)
1100
+ {
1101
+ char *stmt = "SELECT DISTINCT \"collection\" FROM \"database2\";";
1102
+ int stmtLen = (int)strlen(stmt);
1103
+
1104
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateCollectionsStatement, NULL);
1105
+ if (status != SQLITE_OK)
1106
+ {
1107
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1108
+ }
1109
+ }
1110
+
1111
+ return enumerateCollectionsStatement;
1112
+ }
1113
+
1114
+ - (sqlite3_stmt *)enumerateCollectionsForKeyStatement
1115
+ {
1116
+ if (enumerateCollectionsForKeyStatement == NULL)
1117
+ {
1118
+ char *stmt = "SELECT \"collection\" FROM \"database2\" WHERE \"key\" = ?;";
1119
+ int stmtLen = (int)strlen(stmt);
1120
+
1121
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateCollectionsForKeyStatement, NULL);
1122
+ if (status != SQLITE_OK)
1123
+ {
1124
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1125
+ }
1126
+ }
1127
+
1128
+ return enumerateCollectionsForKeyStatement;
1129
+ }
1130
+
1131
+ - (sqlite3_stmt *)enumerateKeysInCollectionStatement
1132
+ {
1133
+ if (enumerateKeysInCollectionStatement == NULL)
1134
+ {
1135
+ char *stmt = "SELECT \"rowid\", \"key\" FROM \"database2\" WHERE collection = ?;";
1136
+ int stmtLen = (int)strlen(stmt);
1137
+
1138
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateKeysInCollectionStatement, NULL);
1139
+ if (status != SQLITE_OK)
1140
+ {
1141
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1142
+ }
1143
+ }
1144
+
1145
+ return enumerateKeysInCollectionStatement;
1146
+ }
1147
+
1148
+ - (sqlite3_stmt *)enumerateKeysInAllCollectionsStatement
1149
+ {
1150
+ if (enumerateKeysInAllCollectionsStatement == NULL)
1151
+ {
1152
+ char *stmt = "SELECT \"rowid\", \"collection\", \"key\" FROM \"database2\";";
1153
+ int stmtLen = (int)strlen(stmt);
1154
+
1155
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateKeysInAllCollectionsStatement, NULL);
1156
+ if (status != SQLITE_OK)
1157
+ {
1158
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1159
+ }
1160
+ }
1161
+
1162
+ return enumerateKeysInAllCollectionsStatement;
1163
+ }
1164
+
1165
+ - (sqlite3_stmt *)enumerateKeysAndMetadataInCollectionStatement
1166
+ {
1167
+ if (enumerateKeysAndMetadataInCollectionStatement == NULL)
1168
+ {
1169
+ char *stmt = "SELECT \"rowid\", \"key\", \"metadata\" FROM \"database2\" WHERE collection = ?;";
1170
+ int stmtLen = (int)strlen(stmt);
1171
+
1172
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateKeysAndMetadataInCollectionStatement, NULL);
1173
+ if (status != SQLITE_OK)
1174
+ {
1175
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1176
+ }
1177
+ }
1178
+
1179
+ return enumerateKeysAndMetadataInCollectionStatement;
1180
+ }
1181
+
1182
+ - (sqlite3_stmt *)enumerateKeysAndMetadataInAllCollectionsStatement
1183
+ {
1184
+ if (enumerateKeysAndMetadataInAllCollectionsStatement == NULL)
1185
+ {
1186
+ char *stmt = "SELECT \"rowid\", \"collection\", \"key\", \"metadata\""
1187
+ " FROM \"database2\" ORDER BY \"collection\" ASC;";
1188
+ int stmtLen = (int)strlen(stmt);
1189
+
1190
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateKeysAndMetadataInAllCollectionsStatement, NULL);
1191
+ if (status != SQLITE_OK)
1192
+ {
1193
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1194
+ }
1195
+ }
1196
+
1197
+ return enumerateKeysAndMetadataInAllCollectionsStatement;
1198
+ }
1199
+
1200
+ - (sqlite3_stmt *)enumerateKeysAndObjectsInCollectionStatement
1201
+ {
1202
+ if (enumerateKeysAndObjectsInCollectionStatement == NULL)
1203
+ {
1204
+ char *stmt = "SELECT \"rowid\", \"key\", \"data\" FROM \"database2\" WHERE \"collection\" = ?;";
1205
+ int stmtLen = (int)strlen(stmt);
1206
+
1207
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateKeysAndObjectsInCollectionStatement, NULL);
1208
+ if (status != SQLITE_OK)
1209
+ {
1210
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1211
+ }
1212
+ }
1213
+
1214
+ return enumerateKeysAndObjectsInCollectionStatement;
1215
+ }
1216
+
1217
+ - (sqlite3_stmt *)enumerateKeysAndObjectsInAllCollectionsStatement
1218
+ {
1219
+ if (enumerateKeysAndObjectsInAllCollectionsStatement == NULL)
1220
+ {
1221
+ char *stmt = "SELECT \"rowid\", \"collection\", \"key\", \"data\""
1222
+ " FROM \"database2\" ORDER BY \"collection\" ASC;";
1223
+ int stmtLen = (int)strlen(stmt);
1224
+
1225
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateKeysAndObjectsInAllCollectionsStatement, NULL);
1226
+ if (status != SQLITE_OK)
1227
+ {
1228
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1229
+ }
1230
+ }
1231
+
1232
+ return enumerateKeysAndObjectsInAllCollectionsStatement;
1233
+ }
1234
+
1235
+ - (sqlite3_stmt *)enumerateRowsInCollectionStatement
1236
+ {
1237
+ if (enumerateRowsInCollectionStatement == NULL)
1238
+ {
1239
+ char *stmt = "SELECT \"rowid\", \"key\", \"data\", \"metadata\" FROM \"database2\" WHERE \"collection\" = ?;";
1240
+ int stmtLen = (int)strlen(stmt);
1241
+
1242
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateRowsInCollectionStatement, NULL);
1243
+ if (status != SQLITE_OK)
1244
+ {
1245
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1246
+ }
1247
+ }
1248
+
1249
+ return enumerateRowsInCollectionStatement;
1250
+ }
1251
+
1252
+ - (sqlite3_stmt *)enumerateRowsInAllCollectionsStatement
1253
+ {
1254
+ if (enumerateRowsInAllCollectionsStatement == NULL)
1255
+ {
1256
+ char *stmt =
1257
+ "SELECT \"rowid\", \"collection\", \"key\", \"data\", \"metadata\""
1258
+ " FROM \"database2\" ORDER BY \"collection\" ASC;";
1259
+ int stmtLen = (int)strlen(stmt);
1260
+
1261
+ int status = sqlite3_prepare_v2(db, stmt, stmtLen+1, &enumerateRowsInAllCollectionsStatement, NULL);
1262
+ if (status != SQLITE_OK)
1263
+ {
1264
+ YDBLogError(@"Error creating '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
1265
+ }
1266
+ }
1267
+
1268
+ return enumerateRowsInAllCollectionsStatement;
1269
+ }
1270
+
1271
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1272
+ #pragma mark Transactions
1273
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1274
+
1275
+ /**
1276
+ * Read-only access to the database.
1277
+ *
1278
+ * The given block can run concurrently with sibling connections,
1279
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
1280
+ *
1281
+ * The only time this method ever blocks is if another thread is currently using this connection instance
1282
+ * to execute a readBlock or readWriteBlock. Recall that you may create multiple connections for concurrent access.
1283
+ *
1284
+ * This method is synchronous.
1285
+ **/
1286
+ - (void)readWithBlock:(void (^)(YapDatabaseReadTransaction *))block
1287
+ {
1288
+ dispatch_sync(connectionQueue, ^{ @autoreleasepool {
1289
+
1290
+ if (longLivedReadTransaction)
1291
+ {
1292
+ block(longLivedReadTransaction);
1293
+ }
1294
+ else
1295
+ {
1296
+ YapDatabaseReadTransaction *transaction = [self newReadTransaction];
1297
+
1298
+ [self preReadTransaction:transaction];
1299
+ block(transaction);
1300
+ [self postReadTransaction:transaction];
1301
+ }
1302
+ }});
1303
+ }
1304
+
1305
+ /**
1306
+ * Read-write access to the database.
1307
+ *
1308
+ * Only a single read-write block can execute among all sibling connections.
1309
+ * Thus this method may block if another sibling connection is currently executing a read-write block.
1310
+ *
1311
+ * This method is synchronous.
1312
+ **/
1313
+ - (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
1314
+ {
1315
+ // Order matters.
1316
+ // First go through the serial connection queue.
1317
+ // Then go through serial write queue for the database.
1318
+ //
1319
+ // Once we're inside the database writeQueue, we know that we are the only write transaction.
1320
+ // No other transaction can possibly modify the database except us, even in other connections.
1321
+
1322
+ dispatch_sync(connectionQueue, ^{
1323
+
1324
+ if (longLivedReadTransaction)
1325
+ {
1326
+ if (throwExceptionsForImplicitlyEndingLongLivedReadTransaction)
1327
+ {
1328
+ @throw [self implicitlyEndingLongLivedReadTransactionException];
1329
+ }
1330
+ else
1331
+ {
1332
+ YDBLogWarn(@"Implicitly ending long-lived read transaction on connection %@, database %@",
1333
+ self, database);
1334
+
1335
+ [self endLongLivedReadTransaction];
1336
+ }
1337
+ }
1338
+
1339
+ __preWriteQueue(self);
1340
+ dispatch_sync(database->writeQueue, ^{ @autoreleasepool {
1341
+
1342
+ YapDatabaseReadWriteTransaction *transaction = [self newReadWriteTransaction];
1343
+
1344
+ [self preReadWriteTransaction:transaction];
1345
+ block(transaction);
1346
+ [self postReadWriteTransaction:transaction];
1347
+
1348
+ }}); // End dispatch_sync(database->writeQueue)
1349
+ __postWriteQueue(self);
1350
+ }); // End dispatch_sync(connectionQueue)
1351
+ }
1352
+
1353
+ /**
1354
+ * Read-only access to the database.
1355
+ *
1356
+ * The given block can run concurrently with sibling connections,
1357
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
1358
+ *
1359
+ * This method is asynchronous.
1360
+ **/
1361
+ - (void)asyncReadWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block
1362
+ {
1363
+ [self asyncReadWithBlock:block completionBlock:NULL completionQueue:NULL];
1364
+ }
1365
+
1366
+ /**
1367
+ * Read-write access to the database.
1368
+ *
1369
+ * The given block can run concurrently with sibling connections,
1370
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
1371
+ *
1372
+ * This method is asynchronous.
1373
+ *
1374
+ * An optional completion block may be used.
1375
+ * The completionBlock will be invoked on the main thread (dispatch_get_main_queue()).
1376
+ **/
1377
+ - (void)asyncReadWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block
1378
+ completionBlock:(dispatch_block_t)completionBlock
1379
+ {
1380
+ [self asyncReadWithBlock:block completionBlock:completionBlock completionQueue:NULL];
1381
+ }
1382
+
1383
+ /**
1384
+ * Read-write access to the database.
1385
+ *
1386
+ * The given block can run concurrently with sibling connections,
1387
+ * regardless of whether the sibling connections are executing read-only or read-write transactions.
1388
+ *
1389
+ * This method is asynchronous.
1390
+ *
1391
+ * An optional completion block may be used.
1392
+ * Additionally the dispatch_queue to invoke the completion block may also be specified.
1393
+ * If NULL, dispatch_get_main_queue() is automatically used.
1394
+ **/
1395
+ - (void)asyncReadWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block
1396
+ completionBlock:(dispatch_block_t)completionBlock
1397
+ completionQueue:(dispatch_queue_t)completionQueue
1398
+ {
1399
+ if (completionQueue == NULL && completionBlock != NULL)
1400
+ completionQueue = dispatch_get_main_queue();
1401
+
1402
+ dispatch_async(connectionQueue, ^{ @autoreleasepool {
1403
+
1404
+ if (longLivedReadTransaction)
1405
+ {
1406
+ block(longLivedReadTransaction);
1407
+ }
1408
+ else
1409
+ {
1410
+ YapDatabaseReadTransaction *transaction = [self newReadTransaction];
1411
+
1412
+ [self preReadTransaction:transaction];
1413
+ block(transaction);
1414
+ [self postReadTransaction:transaction];
1415
+ }
1416
+
1417
+ if (completionBlock)
1418
+ dispatch_async(completionQueue, completionBlock);
1419
+ }});
1420
+ }
1421
+
1422
+ /**
1423
+ * Read-write access to the database.
1424
+ *
1425
+ * Only a single read-write block can execute among all sibling connections.
1426
+ * Thus this method may block if another sibling connection is currently executing a read-write block.
1427
+ *
1428
+ * This method is asynchronous.
1429
+ **/
1430
+ - (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
1431
+ {
1432
+ [self asyncReadWriteWithBlock:block completionBlock:NULL completionQueue:NULL];
1433
+ }
1434
+
1435
+ /**
1436
+ * Read-write access to the database.
1437
+ *
1438
+ * Only a single read-write block can execute among all sibling connections.
1439
+ * Thus the execution of the block may be delayted if another sibling connection
1440
+ * is currently executing a read-write block.
1441
+ *
1442
+ * This method is asynchronous.
1443
+ *
1444
+ * An optional completion block may be used.
1445
+ * The completionBlock will be invoked on the main thread (dispatch_get_main_queue()).
1446
+ **/
1447
+ - (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
1448
+ completionBlock:(dispatch_block_t)completionBlock
1449
+ {
1450
+ [self asyncReadWriteWithBlock:block completionBlock:completionBlock completionQueue:NULL];
1451
+ }
1452
+
1453
+ /**
1454
+ * Read-write access to the database.
1455
+ *
1456
+ * Only a single read-write block can execute among all sibling connections.
1457
+ * Thus the execution of the block may be delayted if another sibling connection
1458
+ * is currently executing a read-write block.
1459
+ *
1460
+ * This method is asynchronous.
1461
+ *
1462
+ * An optional completion block may be used.
1463
+ * Additionally the dispatch_queue to invoke the completion block may also be specified.
1464
+ * If NULL, dispatch_get_main_queue() is automatically used.
1465
+ **/
1466
+ - (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block
1467
+ completionBlock:(dispatch_block_t)completionBlock
1468
+ completionQueue:(dispatch_queue_t)completionQueue
1469
+ {
1470
+ if (completionQueue == NULL && completionBlock != NULL)
1471
+ completionQueue = dispatch_get_main_queue();
1472
+
1473
+ // Order matters.
1474
+ // First go through the serial connection queue.
1475
+ // Then go through serial write queue for the database.
1476
+ //
1477
+ // Once we're inside the database writeQueue, we know that we are the only write transaction.
1478
+ // No other transaction can possibly modify the database except us, even in other connections.
1479
+
1480
+ dispatch_async(connectionQueue, ^{
1481
+
1482
+ if (longLivedReadTransaction)
1483
+ {
1484
+ if (throwExceptionsForImplicitlyEndingLongLivedReadTransaction)
1485
+ {
1486
+ @throw [self implicitlyEndingLongLivedReadTransactionException];
1487
+ }
1488
+ else
1489
+ {
1490
+ YDBLogWarn(@"Implicitly ending long-lived read transaction on connection %@, database %@",
1491
+ self, database);
1492
+
1493
+ [self endLongLivedReadTransaction];
1494
+ }
1495
+ }
1496
+
1497
+ __preWriteQueue(self);
1498
+ dispatch_sync(database->writeQueue, ^{ @autoreleasepool {
1499
+
1500
+ YapDatabaseReadWriteTransaction *transaction = [self newReadWriteTransaction];
1501
+
1502
+ [self preReadWriteTransaction:transaction];
1503
+ block(transaction);
1504
+ [self postReadWriteTransaction:transaction];
1505
+
1506
+ if (completionBlock)
1507
+ dispatch_async(completionQueue, completionBlock);
1508
+
1509
+ }}); // End dispatch_sync(database->writeQueue)
1510
+ __postWriteQueue(self);
1511
+ }); // End dispatch_async(connectionQueue)
1512
+ }
1513
+
1514
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1515
+ #pragma mark Transaction States
1516
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1517
+
1518
+ /**
1519
+ * Required method.
1520
+ * Returns the proper type of transaction for this connection class.
1521
+ **/
1522
+ - (YapDatabaseReadTransaction *)newReadTransaction
1523
+ {
1524
+ return [[YapDatabaseReadTransaction alloc] initWithConnection:self isReadWriteTransaction:NO];
1525
+ }
1526
+
1527
+ /**
1528
+ * Required method.
1529
+ * Returns the proper type of transaction for this connection class.
1530
+ **/
1531
+ - (YapDatabaseReadWriteTransaction *)newReadWriteTransaction
1532
+ {
1533
+ return [[YapDatabaseReadWriteTransaction alloc] initWithConnection:self isReadWriteTransaction:YES];
1534
+ }
1535
+
1536
+ /**
1537
+ * This method executes the state transition steps required before executing a read-only transaction block.
1538
+ *
1539
+ * This method must be invoked from within the connectionQueue.
1540
+ **/
1541
+ - (void)preReadTransaction:(YapDatabaseReadTransaction *)transaction
1542
+ {
1543
+ // Pre-Read-Transaction: Step 1 of 3
1544
+ //
1545
+ // Execute "BEGIN TRANSACTION" on database connection.
1546
+ // This is actually a deferred transaction, meaning the sqlite connection won't actually
1547
+ // acquire a shared read lock until it executes a select statement.
1548
+ // There are alternatives to this, including a "begin immediate transaction".
1549
+ // However, this doesn't do what we want. Instead it blocks other read-only transactions.
1550
+ // The deferred transaction is actually what we want, as many read-only transactions only
1551
+ // hit our in-memory caches. Thus we avoid sqlite machinery when unneeded.
1552
+
1553
+ [transaction beginTransaction];
1554
+
1555
+ dispatch_sync(database->snapshotQueue, ^{ @autoreleasepool {
1556
+
1557
+ // Pre-Read-Transaction: Step 2 of 3
1558
+ //
1559
+ // Update our connection state within the state table.
1560
+ //
1561
+ // First we need to mark this connection as being within a read-only transaction.
1562
+ // We do this by marking a "yap-level" shared read lock flag.
1563
+ //
1564
+ // Now recall from step 1 that our "sql-level" transaction is deferred.
1565
+ // The sql internals won't actually acquire the shared read lock until a we perform a select.
1566
+ // If there are write transactions in progress, this is a big problem for us.
1567
+ // Here's why:
1568
+ //
1569
+ // We have an in-memory snapshot via the caches.
1570
+ // This is kept in-sync with what's on disk (in the sqlite database file).
1571
+ // But what happens if the write transaction commits its changes before we perform our select statement?
1572
+ // Our select statement would acquire a different snapshot than our in-memory snapshot.
1573
+ // Thus, we look to see if there are any write transactions.
1574
+ // If there are, then we immediately acquire the "sql-level" shared read lock.
1575
+
1576
+ BOOL hasActiveWriteTransaction = NO;
1577
+ YapDatabaseConnectionState *myState = nil;
1578
+
1579
+ for (YapDatabaseConnectionState *state in database->connectionStates)
1580
+ {
1581
+ if (state->connection == self)
1582
+ {
1583
+ myState = state;
1584
+ myState->yapLevelSharedReadLock = YES;
1585
+ }
1586
+ else if (state->yapLevelExclusiveWriteLock)
1587
+ {
1588
+ hasActiveWriteTransaction = YES;
1589
+ }
1590
+ }
1591
+
1592
+ NSAssert(myState != nil, @"Missing state in database->connectionStates");
1593
+
1594
+ // Pre-Read-Transaction: Step 3 of 3
1595
+ //
1596
+ // Update our in-memory data (caches, etc) if needed.
1597
+
1598
+ if (hasActiveWriteTransaction || longLivedReadTransaction)
1599
+ {
1600
+ // If this is for a longLivedReadTransaction,
1601
+ // then we need to immediately acquire a "sql-level" snapshot.
1602
+ //
1603
+ // Otherwise if there is a write transaction in progress,
1604
+ // then it's not safe to proceed until we acquire a "sql-level" snapshot.
1605
+ //
1606
+ // During this process we need to ensure that our "yap-level" snapshot of the in-memory data (caches, etc)
1607
+ // is in sync with our "sql-level" snapshot of the database.
1608
+ //
1609
+ // We can check this by comparing the connection's snapshot ivar with
1610
+ // the snapshot read from disk (via sqlite select).
1611
+ //
1612
+ // If the two match then our snapshots are in sync.
1613
+ // If they don't then we need to get caught up by processing changesets.
1614
+
1615
+ uint64_t yapSnapshot = snapshot;
1616
+ uint64_t sqlSnapshot = [self readSnapshotFromDatabase];
1617
+
1618
+ if (yapSnapshot < sqlSnapshot)
1619
+ {
1620
+ // The transaction can see the sqlite commit from another transaction,
1621
+ // and it hasn't processed the changeset(s) yet. We need to process them now.
1622
+
1623
+ NSArray *changesets = [database pendingAndCommittedChangesSince:yapSnapshot until:sqlSnapshot];
1624
+
1625
+ for (NSDictionary *changeset in changesets)
1626
+ {
1627
+ [self noteCommittedChanges:changeset];
1628
+ }
1629
+
1630
+ NSAssert(snapshot == sqlSnapshot,
1631
+ @"Invalid connection state in preReadTransaction: snapshot(%llu) != sqlSnapshot(%llu): %@",
1632
+ snapshot, sqlSnapshot, changesets);
1633
+ }
1634
+
1635
+ myState->lastKnownSnapshot = snapshot;
1636
+ myState->longLivedReadTransaction = (longLivedReadTransaction != nil);
1637
+ myState->sqlLevelSharedReadLock = YES;
1638
+ needsMarkSqlLevelSharedReadLock = NO;
1639
+ }
1640
+ else
1641
+ {
1642
+ // There is NOT a write transaction in progress.
1643
+ // Thus we are safe to proceed with only a "yap-level" snapshot.
1644
+ //
1645
+ // However, we MUST ensure that our "yap-level" snapshot of the in-memory data (caches, etc)
1646
+ // are in sync with the rest of the system.
1647
+ //
1648
+ // That is, our connection may have started its transaction before it was
1649
+ // able to process a changeset from a sibling connection.
1650
+ // If this is the case then we need to get caught up by processing the changeset(s).
1651
+
1652
+ uint64_t localSnapshot = snapshot;
1653
+ uint64_t globalSnapshot = [database snapshot];
1654
+
1655
+ if (localSnapshot < globalSnapshot)
1656
+ {
1657
+ // The transaction hasn't processed recent changeset(s) yet. We need to process them now.
1658
+
1659
+ NSArray *changesets = [database pendingAndCommittedChangesSince:localSnapshot until:globalSnapshot];
1660
+
1661
+ for (NSDictionary *changeset in changesets)
1662
+ {
1663
+ [self noteCommittedChanges:changeset];
1664
+ }
1665
+
1666
+ NSAssert(snapshot == globalSnapshot,
1667
+ @"Invalid connection state in preReadTransaction: snapshot(%llu) != globalSnapshot(%llu): %@",
1668
+ snapshot, globalSnapshot, changesets);
1669
+ }
1670
+
1671
+ myState->lastKnownSnapshot = snapshot;
1672
+ myState->sqlLevelSharedReadLock = NO;
1673
+ needsMarkSqlLevelSharedReadLock = YES;
1674
+ }
1675
+ }});
1676
+ }
1677
+
1678
+ /**
1679
+ * This method executes the state transition steps required after executing a read-only transaction block.
1680
+ *
1681
+ * This method must be invoked from within the connectionQueue.
1682
+ **/
1683
+ - (void)postReadTransaction:(YapDatabaseReadTransaction *)transaction
1684
+ {
1685
+ // Post-Read-Transaction: Step 1 of 4
1686
+ //
1687
+ // 1. Execute "COMMIT TRANSACTION" on database connection.
1688
+ // If we had acquired "sql-level" shared read lock, this will release associated resources.
1689
+ // It may also free the auto-checkpointing architecture within sqlite to sync the WAL to the database.
1690
+
1691
+ [transaction commitTransaction];
1692
+
1693
+ __block uint64_t minSnapshot = 0;
1694
+ __block YapDatabaseConnectionState *writeStateToSignal = nil;
1695
+
1696
+ dispatch_sync(database->snapshotQueue, ^{ @autoreleasepool {
1697
+
1698
+ // Post-Read-Transaction: Step 2 of 4
1699
+ //
1700
+ // Update our connection state within the state table.
1701
+ //
1702
+ // First we need to mark this connection as no longer being within a read-only transaction.
1703
+ // We do this by unmarking the "yap-level" and "sql-level" shared read lock flags.
1704
+ //
1705
+ // While we're doing this we also check to see if we were possibly blocking a write transaction.
1706
+ // When does a write transaction get blocked?
1707
+ //
1708
+ // Recall from the discussion above that we don't always acquire a "sql-level" shared read lock.
1709
+ // Our sql transaction is deferred until our first select statement.
1710
+ // Now if a write transaction comes along and discovers there are existing read transactions that
1711
+ // have an in-memory metadata snapshot, but haven't acquired an "sql-level" snapshot of the actual
1712
+ // database, it will block until these read transctions either complete,
1713
+ // or acquire the needed "sql-level" snapshot.
1714
+ //
1715
+ // So if we never acquired an "sql-level" snapshot of the database, and we were the last transaction
1716
+ // in such a state, and there's a blocked write transaction, then we need to signal it.
1717
+
1718
+ minSnapshot = [database snapshot];
1719
+
1720
+ BOOL wasMaybeBlockingWriteTransaction = NO;
1721
+ NSUInteger countOtherMaybeBlockingWriteTransaction = 0;
1722
+ YapDatabaseConnectionState *blockedWriteState = nil;
1723
+
1724
+ for (YapDatabaseConnectionState *state in database->connectionStates)
1725
+ {
1726
+ if (state->connection == self)
1727
+ {
1728
+ wasMaybeBlockingWriteTransaction = state->yapLevelSharedReadLock && !state->sqlLevelSharedReadLock;
1729
+ state->yapLevelSharedReadLock = NO;
1730
+ state->sqlLevelSharedReadLock = NO;
1731
+ state->longLivedReadTransaction = NO;
1732
+ }
1733
+ else if (state->yapLevelSharedReadLock)
1734
+ {
1735
+ // Active sibling connection: read-only
1736
+
1737
+ minSnapshot = MIN(state->lastKnownSnapshot, minSnapshot);
1738
+
1739
+ if (!state->sqlLevelSharedReadLock)
1740
+ countOtherMaybeBlockingWriteTransaction++;
1741
+ }
1742
+ else if (state->yapLevelExclusiveWriteLock)
1743
+ {
1744
+ // Active sibling connection: read-write
1745
+
1746
+ minSnapshot = MIN(state->lastKnownSnapshot, minSnapshot);
1747
+
1748
+ if (state->waitingForWriteLock)
1749
+ blockedWriteState = state;
1750
+ }
1751
+ }
1752
+
1753
+ if (wasMaybeBlockingWriteTransaction && countOtherMaybeBlockingWriteTransaction == 0 && blockedWriteState)
1754
+ {
1755
+ writeStateToSignal = blockedWriteState;
1756
+ }
1757
+
1758
+ YDBLogVerbose(@"YapDatabaseConnection(%p) completing read-only transaction.", self);
1759
+ }});
1760
+
1761
+ // Post-Read-Transaction: Step 3 of 4
1762
+ //
1763
+ // Check to see if this connection has been holding back the checkpoint process.
1764
+ // That is, was this connection the last active connection on an old snapshot?
1765
+
1766
+ if (snapshot < minSnapshot)
1767
+ {
1768
+ // There are commits ahead of us that need to be checkpointed.
1769
+ // And we were the oldest active connection,
1770
+ // so we were previously preventing the checkpoint from progressing.
1771
+ // Thus we can now continue the checkpoint operation.
1772
+
1773
+ [database asyncCheckpoint:minSnapshot];
1774
+
1775
+ [registeredTables enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1776
+
1777
+ [(YapMemoryTable *)obj asyncCheckpoint:minSnapshot];
1778
+ }];
1779
+ }
1780
+
1781
+ // Post-Read-Transaction: Step 4 of 4
1782
+ //
1783
+ // If we discovered a blocked write transaction,
1784
+ // and it was blocked waiting on us (because we had a "yap-level" snapshot without an "sql-level" snapshot),
1785
+ // and it's no longer blocked on any other read transaction (that have "yap-level" snapshots
1786
+ // without "sql-level snapshots"), then signal the write semaphore so the blocked thread wakes up.
1787
+
1788
+ if (writeStateToSignal)
1789
+ {
1790
+ YDBLogVerbose(@"YapDatabaseConnection(%p) signaling blocked write on connection(%p)",
1791
+ self, writeStateToSignal->connection);
1792
+
1793
+ [writeStateToSignal signalWriteLock];
1794
+ }
1795
+ }
1796
+
1797
+ /**
1798
+ * This method executes the state transition steps required before executing a read-write transaction block.
1799
+ *
1800
+ * This method must be invoked from within the connectionQueue.
1801
+ * This method must be invoked from within the database.writeQueue.
1802
+ **/
1803
+ - (void)preReadWriteTransaction:(YapDatabaseReadWriteTransaction *)transaction
1804
+ {
1805
+ // Pre-Write-Transaction: Step 1 of 5
1806
+ //
1807
+ // Execute "BEGIN TRANSACTION" on database connection.
1808
+ // This is actually a deferred transaction, meaning the sqlite connection won't actually
1809
+ // acquire any locks until it executes something.
1810
+ // There are various alternatives to this, including a "immediate" and "exclusive" transactions.
1811
+ // However, these don't do what we want. Instead they block other read-only transactions.
1812
+ // The deferred transaction allows other read-only transactions and even avoids
1813
+ // sqlite operations if no modifications are made.
1814
+ //
1815
+ // Remember, we are the only active write transaction for this database.
1816
+ // No other write transactions can occur until this transaction completes.
1817
+ // Thus no other transactions can possibly modify the database during our transaction.
1818
+ // Therefore it doesn't matter when we acquire our "sql-level" locks for writing.
1819
+
1820
+ [transaction beginTransaction];
1821
+
1822
+ dispatch_sync(database->snapshotQueue, ^{ @autoreleasepool {
1823
+
1824
+ // Pre-Write-Transaction: Step 2 of 5
1825
+ //
1826
+ // Update our connection state within the state table.
1827
+ //
1828
+ // We are the only write transaction for this database.
1829
+ // It is important for read-only transactions on other connections to know there's a writer.
1830
+
1831
+ YapDatabaseConnectionState *myState = nil;
1832
+
1833
+ for (YapDatabaseConnectionState *state in database->connectionStates)
1834
+ {
1835
+ if (state->connection == self)
1836
+ {
1837
+ myState = state;
1838
+ myState->yapLevelExclusiveWriteLock = YES;
1839
+ }
1840
+ }
1841
+
1842
+ NSAssert(myState != nil, @"Missing state in database->connectionStates");
1843
+
1844
+ // Pre-Write-Transaction: Step 3 of 5
1845
+ //
1846
+ // Validate our caches based on snapshot numbers
1847
+
1848
+ uint64_t localSnapshot = snapshot;
1849
+ uint64_t globalSnapshot = [database snapshot];
1850
+
1851
+ if (localSnapshot < globalSnapshot)
1852
+ {
1853
+ NSArray *changesets = [database pendingAndCommittedChangesSince:localSnapshot until:globalSnapshot];
1854
+
1855
+ for (NSDictionary *changeset in changesets)
1856
+ {
1857
+ [self noteCommittedChanges:changeset];
1858
+ }
1859
+
1860
+ NSAssert(snapshot == globalSnapshot,
1861
+ @"Invalid connection state in preReadWriteTransaction: snapshot(%llu) != globalSnapshot(%llu)",
1862
+ snapshot, globalSnapshot);
1863
+ }
1864
+
1865
+ myState->lastKnownSnapshot = snapshot;
1866
+ needsMarkSqlLevelSharedReadLock = NO;
1867
+
1868
+ YDBLogVerbose(@"YapDatabaseConnection(%p) starting read-write transaction.", self);
1869
+ }});
1870
+
1871
+ // Pre-Write-Transaction: Step 4 of 5
1872
+ //
1873
+ // Setup write state and changeset variables
1874
+
1875
+ hasDiskChanges = NO;
1876
+
1877
+ if (objectChanges == nil)
1878
+ objectChanges = [[NSMutableDictionary alloc] init];
1879
+ if (metadataChanges == nil)
1880
+ metadataChanges = [[NSMutableDictionary alloc] init];
1881
+ if (removedKeys == nil)
1882
+ removedKeys = [[NSMutableSet alloc] init];
1883
+ if (removedCollections == nil)
1884
+ removedCollections = [[NSMutableSet alloc] init];
1885
+
1886
+ allKeysRemoved = NO;
1887
+
1888
+ // Pre-Write-Transaction: Step 5 of 5
1889
+ //
1890
+ // Add IsOnConnectionQueueKey flag to writeQueue.
1891
+ // This allows various methods that depend on the flag to operate correctly.
1892
+
1893
+ dispatch_queue_set_specific(database->writeQueue, IsOnConnectionQueueKey, IsOnConnectionQueueKey, NULL);
1894
+ }
1895
+
1896
+ /**
1897
+ * This method executes the state transition steps required after executing a read-only transaction block.
1898
+ *
1899
+ * This method must be invoked from within the connectionQueue.
1900
+ * This method must be invoked from within the database.writeQueue.
1901
+ **/
1902
+ - (void)postReadWriteTransaction:(YapDatabaseReadWriteTransaction *)transaction
1903
+ {
1904
+ if (transaction->rollback)
1905
+ {
1906
+ // Rollback-Write-Transaction: Step 1 of 2
1907
+ //
1908
+ // Update our connection state within the state table.
1909
+ //
1910
+ // We are the only write transaction for this database.
1911
+ // It is important for read-only transactions on other connections to know we're no longer a writer.
1912
+
1913
+ dispatch_sync(database->snapshotQueue, ^{
1914
+
1915
+ for (YapDatabaseConnectionState *state in database->connectionStates)
1916
+ {
1917
+ if (state->connection == self)
1918
+ {
1919
+ state->yapLevelExclusiveWriteLock = NO;
1920
+ break;
1921
+ }
1922
+ }
1923
+ });
1924
+
1925
+ // Rollback-Write-Transaction: Step 2 of 3
1926
+ //
1927
+ // Rollback sqlite database transaction.
1928
+
1929
+ [transaction rollbackTransaction];
1930
+
1931
+ // Rollback-Write-Transaction: Step 3 of 3
1932
+ //
1933
+ // Reset any in-memory variables which may be out-of-sync with the database.
1934
+
1935
+ [self postRollbackCleanup];
1936
+
1937
+ YDBLogVerbose(@"YapDatabaseConnection(%p) completing read-write transaction (rollback).", self);
1938
+
1939
+ return;
1940
+ }
1941
+
1942
+ // Post-Write-Transaction: Step 1 of 11
1943
+ //
1944
+ // Run any pre-commit operations.
1945
+ // This allows extensions to to perform any cleanup before the changeset is requested.
1946
+
1947
+ [transaction preCommitReadWriteTransaction];
1948
+
1949
+ // Post-Write-Transaction: Step 2 of 11
1950
+ //
1951
+ // Fetch changesets.
1952
+ // Then update the snapshot in the 'yap' database (if any changes were made).
1953
+ // We use 'yap' database and snapshot value to check for a race condition.
1954
+ //
1955
+ // The "internal" changeset gets sent directly to sibling database connections.
1956
+ // The "external" changeset gets plugged into the YapDatabaseModifiedNotification as the userInfo dict.
1957
+
1958
+ NSNotification *notification = nil;
1959
+
1960
+ NSMutableDictionary *changeset = nil;
1961
+ NSMutableDictionary *userInfo = nil;
1962
+
1963
+ [self getInternalChangeset:&changeset externalChangeset:&userInfo];
1964
+ if (changeset || userInfo || hasDiskChanges)
1965
+ {
1966
+ // If hasDiskChanges is YES, then the database file was modified.
1967
+ // In this case, we're sure to write the incremented snapshot number to the database.
1968
+ //
1969
+ // If hasDiskChanges is NO, then the database file was not modified.
1970
+ // However, something was "touched" or an in-memory extension was changed.
1971
+
1972
+ if (hasDiskChanges)
1973
+ snapshot = [self incrementSnapshotInDatabase];
1974
+ else
1975
+ snapshot++;
1976
+
1977
+ if (changeset == nil)
1978
+ changeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForInternalChangeset];
1979
+
1980
+ if (userInfo == nil)
1981
+ userInfo = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForExternalChangeset];
1982
+
1983
+ [changeset setObject:@(snapshot) forKey:YapDatabaseSnapshotKey];
1984
+ [userInfo setObject:@(snapshot) forKey:YapDatabaseSnapshotKey];
1985
+
1986
+ [userInfo setObject:self forKey:YapDatabaseConnectionKey];
1987
+
1988
+ if (transaction->customObjectForNotification)
1989
+ [userInfo setObject:transaction->customObjectForNotification forKey:YapDatabaseCustomKey];
1990
+
1991
+ notification = [NSNotification notificationWithName:YapDatabaseModifiedNotification
1992
+ object:database
1993
+ userInfo:userInfo];
1994
+
1995
+ [changeset setObject:notification forKey:YapDatabaseNotificationKey];
1996
+ }
1997
+
1998
+ // Post-Write-Transaction: Step 3 of 11
1999
+ //
2000
+ // Auto-drop tables from previous extensions that aren't being used anymore.
2001
+ //
2002
+ // Note the timing of when this happens:
2003
+ // - Only once
2004
+ // - At the end of a readwrite transaction that has made modifications to the database
2005
+ // - Only if the modifications weren't dedicated to registering/unregistring an extension
2006
+
2007
+ if (database->previouslyRegisteredExtensionNames && changeset && !registeredExtensionsChanged)
2008
+ {
2009
+ for (NSString *prevExtensionName in database->previouslyRegisteredExtensionNames)
2010
+ {
2011
+ if ([registeredExtensions objectForKey:prevExtensionName] == nil)
2012
+ {
2013
+ NSString *className = [transaction stringValueForKey:@"class" extension:prevExtensionName];
2014
+ Class class = NSClassFromString(className);
2015
+
2016
+ if (className == nil)
2017
+ {
2018
+ YDBLogWarn(@"Unable to auto-unregister extension(%@). Doesn't appear to be registered.",
2019
+ prevExtensionName);
2020
+ }
2021
+ else if (class == NULL)
2022
+ {
2023
+ YDBLogError(@"Unable to auto-unregister extension(%@) with unknown class(%@)",
2024
+ prevExtensionName, className);
2025
+ }
2026
+ if (![class isSubclassOfClass:[YapDatabaseExtension class]])
2027
+ {
2028
+ YDBLogError(@"Unable to auto-unregister extension(%@) with improper class(%@)",
2029
+ prevExtensionName, className);
2030
+ }
2031
+ else
2032
+ {
2033
+ YDBLogInfo(@"Auto-unregistering extension(%@) with class(%@)",
2034
+ prevExtensionName, className);
2035
+
2036
+ // Drop tables
2037
+ [class dropTablesForRegisteredName:prevExtensionName withTransaction:transaction];
2038
+
2039
+ // Drop preferences (rows in yap2 table)
2040
+ [transaction removeAllValuesForExtension:prevExtensionName];
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ database->previouslyRegisteredExtensionNames = nil;
2046
+ }
2047
+
2048
+ // Post-Write-Transaction: Step 4 of 11
2049
+ //
2050
+ // Check to see if it's safe to commit our changes.
2051
+ //
2052
+ // There may be read-only transactions that have acquired "yap-level" snapshots
2053
+ // without "sql-level" snapshots. That is, these read-only transaction may have a snapshot
2054
+ // of the in-memory metadata dictionary at the time they started, but as for the sqlite connection
2055
+ // the only have a "BEGIN DEFERRED TRANSACTION", and haven't actually executed
2056
+ // any "select" statements. Thus they haven't actually invoked the sqlite machinery to
2057
+ // acquire the "sql-level" snapshot (last valid commit record in the WAL).
2058
+ //
2059
+ // It is our responsibility to block until all read-only transactions have either completed,
2060
+ // or have acquired the necessary "sql-level" shared read lock.
2061
+ //
2062
+ // We avoid writer starvation by enforcing new read-only transactions that start after our writer
2063
+ // started to immediately acquire "sql-level" shared read locks when they start.
2064
+ // Thus we would only ever wait for read-only transactions that started before our
2065
+ // read-write transaction started. And since most of the time the read-write transactions
2066
+ // take longer than read-only transactions, we avoid any blocking in most cases.
2067
+
2068
+ __block YapDatabaseConnectionState *myState = nil;
2069
+ __block BOOL safeToCommit = NO;
2070
+
2071
+ do
2072
+ {
2073
+ __block BOOL waitForReadOnlyTransactions = NO;
2074
+
2075
+ dispatch_sync(database->snapshotQueue, ^{ @autoreleasepool {
2076
+
2077
+ for (YapDatabaseConnectionState *state in database->connectionStates)
2078
+ {
2079
+ if (state->connection == self)
2080
+ {
2081
+ myState = state;
2082
+ }
2083
+ else if (state->yapLevelSharedReadLock && !state->sqlLevelSharedReadLock)
2084
+ {
2085
+ waitForReadOnlyTransactions = YES;
2086
+ }
2087
+ }
2088
+
2089
+ NSAssert(myState != nil, @"Missing state in database->connectionStates");
2090
+
2091
+ if (waitForReadOnlyTransactions)
2092
+ {
2093
+ myState->waitingForWriteLock = YES;
2094
+ [myState prepareWriteLock];
2095
+ }
2096
+ else
2097
+ {
2098
+ myState->waitingForWriteLock = NO;
2099
+ safeToCommit = YES;
2100
+
2101
+ // Post-Write-Transaction: Step 5 of 11
2102
+ //
2103
+ // Register pending changeset with database.
2104
+ // Our commit is actually a two step process.
2105
+ // First we execute the sqlite level commit.
2106
+ // Second we execute the final stages of the yap level commit.
2107
+ //
2108
+ // This two step process means we have an edge case,
2109
+ // where another connection could come around and begin its yap level transaction
2110
+ // before this connections yap level commit, but after this connections sqlite level commit.
2111
+ //
2112
+ // By registering the pending changeset in advance,
2113
+ // we provide a near seamless workaround for the edge case.
2114
+
2115
+ if (changeset)
2116
+ {
2117
+ [database notePendingChanges:changeset fromConnection:self];
2118
+ }
2119
+ }
2120
+
2121
+ }});
2122
+
2123
+ if (waitForReadOnlyTransactions)
2124
+ {
2125
+ // Block until a read-only transaction signals us.
2126
+ // This will occur when the last read-only transaction (that started before our read-write
2127
+ // transaction started) either completes or acquires an "sql-level" shared read lock.
2128
+ //
2129
+ // Note: Since we're using a dispatch semaphore, order doesn't matter.
2130
+ // That is, it's fine if the read-only transaction signals our write lock before we start waiting on it.
2131
+ // In this case we simply return immediately from the wait call.
2132
+
2133
+ YDBLogVerbose(@"YapDatabaseConnection(%p) blocked waiting for write lock...", self);
2134
+
2135
+ [myState waitForWriteLock];
2136
+ }
2137
+
2138
+ } while (!safeToCommit);
2139
+
2140
+ // Post-Write-Transaction: Step 6 of 11
2141
+ //
2142
+ // Execute "COMMIT TRANSACTION" on database connection.
2143
+ // This will write the changes to the WAL, and may invoke a checkpoint.
2144
+ //
2145
+ // Notice that we do this outside the context of the transactionStateQueue.
2146
+ // We do this so we don't block read-only transactions from starting or finishing.
2147
+ // However, this does leave us open for the possibility that a read-only transaction will
2148
+ // get a "yap-level" snapshot of the metadata dictionary before this commit,
2149
+ // but a "sql-level" snapshot of the sql database after this commit.
2150
+ // This is rare but must be guarded against.
2151
+ // The solution is pretty simple and straight-forward.
2152
+ // When a read-only transaction starts, if there's an active write transaction,
2153
+ // it immediately acquires an "sql-level" snapshot. It does this by invoking a select statement,
2154
+ // which invokes the internal sqlite snapshot machinery for the transaction.
2155
+ // So rather than using a dummy select statement that we ignore, we instead select a lastCommit number
2156
+ // from the database. If it doesn't match what we expect, then we know we've run into the race condition,
2157
+ // and we make the read-only transaction back out and try again.
2158
+
2159
+ [transaction commitTransaction];
2160
+
2161
+ __block uint64_t minSnapshot = UINT64_MAX;
2162
+
2163
+ dispatch_sync(database->snapshotQueue, ^{ @autoreleasepool {
2164
+
2165
+ // Post-Write-Transaction: Step 7 of 11
2166
+ //
2167
+ // Notify database of changes, and drop reference to set of changed keys.
2168
+
2169
+ if (changeset)
2170
+ {
2171
+ [database noteCommittedChanges:changeset fromConnection:self];
2172
+ }
2173
+
2174
+ // Post-Write-Transaction: Step 8 of 11
2175
+ //
2176
+ // Update our connection state within the state table.
2177
+ //
2178
+ // We are the only write transaction for this database.
2179
+ // It is important for read-only transactions on other connections to know we're no longer a writer.
2180
+
2181
+ for (YapDatabaseConnectionState *state in database->connectionStates)
2182
+ {
2183
+ if (state->yapLevelSharedReadLock)
2184
+ {
2185
+ minSnapshot = MIN(state->lastKnownSnapshot, minSnapshot);
2186
+ }
2187
+ }
2188
+
2189
+ myState->yapLevelExclusiveWriteLock = NO;
2190
+ myState->waitingForWriteLock = NO;
2191
+
2192
+ YDBLogVerbose(@"YapDatabaseConnection(%p) completing read-write transaction.", self);
2193
+ }});
2194
+
2195
+ // Post-Write-Transaction: Step 9 of 11
2196
+
2197
+ if (changeset)
2198
+ {
2199
+ // We added frames to the WAL.
2200
+ // We can invoke a checkpoint if there are no other active connections.
2201
+
2202
+ if (minSnapshot == UINT64_MAX)
2203
+ {
2204
+ [database asyncCheckpoint:snapshot];
2205
+
2206
+ [registeredTables enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
2207
+
2208
+ [(YapMemoryTable *)obj asyncCheckpoint:snapshot];
2209
+ }];
2210
+ }
2211
+ }
2212
+
2213
+ // Post-Write-Transaction: Step 10 of 11
2214
+ //
2215
+ // Post YapDatabaseModifiedNotification (if needed),
2216
+ // and clear changeset variables (which are now a part of the notification).
2217
+
2218
+ if (notification)
2219
+ {
2220
+ dispatch_async(dispatch_get_main_queue(), ^{
2221
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
2222
+ });
2223
+ }
2224
+
2225
+ if ([objectChanges count] > 0)
2226
+ objectChanges = nil;
2227
+ if ([metadataChanges count] > 0)
2228
+ metadataChanges = nil;
2229
+ if ([removedKeys count] > 0)
2230
+ removedKeys = nil;
2231
+ if ([removedCollections count] > 0)
2232
+ removedCollections = nil;
2233
+
2234
+ // Post-Write-Transaction: Step 11 of 11
2235
+ //
2236
+ // Drop IsOnConnectionQueueKey flag from writeQueue since we're exiting writeQueue.
2237
+
2238
+ dispatch_queue_set_specific(database->writeQueue, IsOnConnectionQueueKey, NULL, NULL);
2239
+ }
2240
+
2241
+ /**
2242
+ * This method "kills two birds with one stone".
2243
+ *
2244
+ * First, it invokes a SELECT statement on the database.
2245
+ * This executes the sqlite machinery to acquire a "sql-level" snapshot of the database.
2246
+ * That is, the encompassing transaction will now reference a specific commit record in the WAL,
2247
+ * and will ignore any commits made after this record.
2248
+ *
2249
+ * Second, it reads a specific value from the database, and tells us which commit record in the WAL its using.
2250
+ * This allows us to validate the transaction, and check for a particular race condition.
2251
+ **/
2252
+ - (uint64_t)readSnapshotFromDatabase
2253
+ {
2254
+ sqlite3_stmt *statement = [self yapGetDataForKeyStatement];
2255
+ if (statement == NULL) return 0;
2256
+
2257
+ uint64_t result = 0;
2258
+
2259
+ // SELECT data FROM 'yap2' WHERE extension = ? AND key = ? ;
2260
+
2261
+ char *extension = "";
2262
+ sqlite3_bind_text(statement, 1, extension, (int)strlen(extension), SQLITE_STATIC);
2263
+
2264
+ char *key = "snapshot";
2265
+ sqlite3_bind_text(statement, 2, key, (int)strlen(key), SQLITE_STATIC);
2266
+
2267
+ int status = sqlite3_step(statement);
2268
+ if (status == SQLITE_ROW)
2269
+ {
2270
+ result = (uint64_t)sqlite3_column_int64(statement, 0);
2271
+ }
2272
+ else if (status == SQLITE_ERROR)
2273
+ {
2274
+ YDBLogError(@"Error executing 'yapGetDataForKeyStatement': %d %s",
2275
+ status, sqlite3_errmsg(db));
2276
+ }
2277
+
2278
+ sqlite3_clear_bindings(statement);
2279
+ sqlite3_reset(statement);
2280
+
2281
+ return result;
2282
+ }
2283
+
2284
+ /**
2285
+ * This method updates the 'snapshot' row in the database.
2286
+ **/
2287
+ - (uint64_t)incrementSnapshotInDatabase
2288
+ {
2289
+ uint64_t newSnapshot = snapshot + 1;
2290
+
2291
+ sqlite3_stmt *statement = [self yapSetDataForKeyStatement];
2292
+ if (statement == NULL) return newSnapshot;
2293
+
2294
+ // INSERT OR REPLACE INTO "yap2" ("extension", "key", "data") VALUES (?, ?, ?);
2295
+
2296
+ char *extension = "";
2297
+ sqlite3_bind_text(statement, 1, extension, (int)strlen(extension), SQLITE_STATIC);
2298
+
2299
+ char *key = "snapshot";
2300
+ sqlite3_bind_text(statement, 2, key, (int)strlen(key), SQLITE_STATIC);
2301
+
2302
+ sqlite3_bind_int64(statement, 3, (sqlite3_int64)newSnapshot);
2303
+
2304
+ int status = sqlite3_step(statement);
2305
+ if (status != SQLITE_DONE)
2306
+ {
2307
+ YDBLogError(@"Error executing 'yapSetDataForKeyStatement': %d %s",
2308
+ status, sqlite3_errmsg(db));
2309
+ }
2310
+
2311
+ sqlite3_clear_bindings(statement);
2312
+ sqlite3_reset(statement);
2313
+
2314
+ return newSnapshot;
2315
+ }
2316
+
2317
+ - (void)markSqlLevelSharedReadLockAcquired
2318
+ {
2319
+ NSAssert(needsMarkSqlLevelSharedReadLock, @"Method called but unneeded. Unnecessary overhead.");
2320
+ if (!needsMarkSqlLevelSharedReadLock) return;
2321
+
2322
+ __block YapDatabaseConnectionState *writeStateToSignal = nil;
2323
+
2324
+ dispatch_sync(database->snapshotQueue, ^{ @autoreleasepool {
2325
+
2326
+ // Update our connection state within the state table.
2327
+ //
2328
+ // We need to mark this connection as having acquired an "sql-level" shared read lock.
2329
+ // That is, our sqlite connection has invoked a select statement, and has thus invoked the sqlite
2330
+ // machinery that causes it to acquire the "sql-level" snapshot (last valid commit record in the WAL).
2331
+ //
2332
+ // While we're doing this we also check to see if we were possibly blocking a write transaction.
2333
+ // When does a write transaction get blocked?
2334
+ //
2335
+ // If a write transaction goes to commit its changes and sees a read-only transaction with
2336
+ // a "yap-level" snapshot of the in-memory metadata snapshot, but without an "sql-level" snapshot
2337
+ // of the actual database, it will block until these read transctions either complete,
2338
+ // or acquire the needed "sql-level" snapshot.
2339
+ //
2340
+ // So if we never acquired an "sql-level" snapshot of the database, and we were the last transaction
2341
+ // in such a state, and there's a blocked write transaction, then we need to signal it.
2342
+
2343
+ __block NSUInteger countOtherMaybeBlockingWriteTransaction = 0;
2344
+ __block YapDatabaseConnectionState *blockedWriteState = nil;
2345
+
2346
+ for (YapDatabaseConnectionState *state in database->connectionStates)
2347
+ {
2348
+ if (state->connection == self)
2349
+ {
2350
+ state->sqlLevelSharedReadLock = YES;
2351
+ }
2352
+ else if (state->yapLevelSharedReadLock && !state->sqlLevelSharedReadLock)
2353
+ {
2354
+ countOtherMaybeBlockingWriteTransaction++;
2355
+ }
2356
+ else if (state->waitingForWriteLock)
2357
+ {
2358
+ blockedWriteState = state;
2359
+ }
2360
+ }
2361
+
2362
+ if (countOtherMaybeBlockingWriteTransaction == 0 && blockedWriteState)
2363
+ {
2364
+ writeStateToSignal = blockedWriteState;
2365
+ }
2366
+ }});
2367
+
2368
+ needsMarkSqlLevelSharedReadLock = NO;
2369
+
2370
+ if (writeStateToSignal)
2371
+ {
2372
+ YDBLogVerbose(@"YapDatabaseConnection(%p) signaling blocked write on connection(%p)",
2373
+ self, writeStateToSignal->connection);
2374
+ [writeStateToSignal signalWriteLock];
2375
+ }
2376
+ }
2377
+
2378
+ /**
2379
+ * This method is invoked after a read-write transaction completes, which was rolled-back.
2380
+ * You should flush anything from memory that may be out-of-sync with the database.
2381
+ **/
2382
+ - (void)postRollbackCleanup
2383
+ {
2384
+ [objectCache removeAllObjects];
2385
+ [metadataCache removeAllObjects];
2386
+
2387
+ if ([objectChanges count] > 0)
2388
+ objectChanges = nil;
2389
+ if ([metadataChanges count] > 0)
2390
+ metadataChanges = nil;
2391
+ if ([removedKeys count] > 0)
2392
+ removedKeys = nil;
2393
+ if ([removedCollections count] > 0)
2394
+ removedCollections = nil;
2395
+ }
2396
+
2397
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2398
+ #pragma mark Long-Lived Transactions
2399
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2400
+
2401
+ - (NSArray *)beginLongLivedReadTransaction
2402
+ {
2403
+ __block NSMutableArray *notifications = nil;
2404
+
2405
+ dispatch_block_t block = ^{ @autoreleasepool {
2406
+
2407
+ if (longLivedReadTransaction)
2408
+ {
2409
+ // Caller using implicit atomic reBeginLongLivedReadTransaction
2410
+ notifications = (NSMutableArray *)[self endLongLivedReadTransaction];
2411
+ }
2412
+
2413
+ longLivedReadTransaction = [self newReadTransaction];
2414
+ [self preReadTransaction:longLivedReadTransaction];
2415
+
2416
+ // The preReadTransaction method acquires the "sqlite-level" snapshot.
2417
+ // In doing so, if it needs to fetch and process any changesets,
2418
+ // then it adds them to the processedChangesets ivar for us.
2419
+
2420
+ if (notifications == nil)
2421
+ notifications = [NSMutableArray arrayWithCapacity:[processedChangesets count]];
2422
+
2423
+ for (NSDictionary *changeset in processedChangesets)
2424
+ {
2425
+ // The changeset has already been processed.
2426
+
2427
+ NSNotification *notification = [changeset objectForKey:YapDatabaseNotificationKey];
2428
+ if (notification) {
2429
+ [notifications addObject:notification];
2430
+ }
2431
+ }
2432
+
2433
+ [processedChangesets removeAllObjects];
2434
+ }};
2435
+
2436
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
2437
+ block();
2438
+ else
2439
+ dispatch_sync(connectionQueue, block);
2440
+
2441
+ return notifications;
2442
+ }
2443
+
2444
+ - (NSArray *)endLongLivedReadTransaction
2445
+ {
2446
+ __block NSMutableArray *notifications = nil;
2447
+
2448
+ dispatch_block_t block = ^{ @autoreleasepool {
2449
+
2450
+ if (longLivedReadTransaction)
2451
+ {
2452
+ // End the transaction (sqlite commit)
2453
+
2454
+ [self postReadTransaction:longLivedReadTransaction];
2455
+ longLivedReadTransaction = nil;
2456
+
2457
+ // Now process any changesets that were pending.
2458
+ // And extract the corresponding external notifications to return the the caller.
2459
+
2460
+ notifications = [NSMutableArray arrayWithCapacity:[pendingChangesets count]];
2461
+
2462
+ for (NSDictionary *changeset in pendingChangesets)
2463
+ {
2464
+ [self noteCommittedChanges:changeset];
2465
+
2466
+ NSNotification *notification = [changeset objectForKey:YapDatabaseNotificationKey];
2467
+ if (notification) {
2468
+ [notifications addObject:notification];
2469
+ }
2470
+ }
2471
+
2472
+ [pendingChangesets removeAllObjects];
2473
+ }
2474
+ }};
2475
+
2476
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
2477
+ block();
2478
+ else
2479
+ dispatch_sync(connectionQueue, block);
2480
+
2481
+ return notifications;
2482
+ }
2483
+
2484
+ - (BOOL)isInLongLivedReadTransaction
2485
+ {
2486
+ __block BOOL result = NO;
2487
+
2488
+ dispatch_block_t block = ^{
2489
+
2490
+ result = (longLivedReadTransaction != nil);
2491
+ };
2492
+
2493
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
2494
+ block();
2495
+ else
2496
+ dispatch_sync(connectionQueue, block);
2497
+
2498
+ return result;
2499
+ }
2500
+
2501
+ - (void)enableExceptionsForImplicitlyEndingLongLivedReadTransaction
2502
+ {
2503
+ dispatch_block_t block = ^{
2504
+
2505
+ throwExceptionsForImplicitlyEndingLongLivedReadTransaction = YES;
2506
+ };
2507
+
2508
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
2509
+ block();
2510
+ else
2511
+ dispatch_async(connectionQueue, block);
2512
+ }
2513
+
2514
+ - (void)disableExceptionsForImplicitlyEndingLongLivedReadTransaction
2515
+ {
2516
+ dispatch_block_t block = ^{
2517
+
2518
+ throwExceptionsForImplicitlyEndingLongLivedReadTransaction = NO;
2519
+ };
2520
+
2521
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
2522
+ block();
2523
+ else
2524
+ dispatch_async(connectionQueue, block);
2525
+ }
2526
+
2527
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2528
+ #pragma mark Changeset Architecture
2529
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2530
+
2531
+ /**
2532
+ * The creation of changeset dictionaries happens constantly.
2533
+ * So, to optimize a bit, we use sharedKeySet's (part of NSDictionary).
2534
+ *
2535
+ * See ivar 'sharedKeySetForInternalChangeset'
2536
+ **/
2537
+ - (NSArray *)internalChangesetKeys
2538
+ {
2539
+ return @[ YapDatabaseSnapshotKey,
2540
+ YapDatabaseExtensionsKey,
2541
+ YapDatabaseRegisteredExtensionsKey,
2542
+ YapDatabaseRegisteredTablesKey,
2543
+ YapDatabaseExtensionsOrderKey,
2544
+ YapDatabaseNotificationKey,
2545
+ YapDatabaseObjectChangesKey,
2546
+ YapDatabaseMetadataChangesKey,
2547
+ YapDatabaseRemovedKeysKey,
2548
+ YapDatabaseRemovedCollectionsKey,
2549
+ YapDatabaseAllKeysRemovedKey ];
2550
+ }
2551
+
2552
+ /**
2553
+ * The creation of changeset dictionaries happens constantly.
2554
+ * So, to optimize a bit, we use sharedKeySet's (part of NSDictionary).
2555
+ *
2556
+ * See ivar 'sharedKeySetForExternalChangeset'
2557
+ **/
2558
+ - (NSArray *)externalChangesetKeys
2559
+ {
2560
+ return @[ YapDatabaseSnapshotKey,
2561
+ YapDatabaseConnectionKey,
2562
+ YapDatabaseExtensionsKey,
2563
+ YapDatabaseCustomKey,
2564
+ YapDatabaseObjectChangesKey,
2565
+ YapDatabaseMetadataChangesKey,
2566
+ YapDatabaseRemovedKeysKey,
2567
+ YapDatabaseRemovedCollectionsKey,
2568
+ YapDatabaseAllKeysRemovedKey ];
2569
+ }
2570
+
2571
+ /**
2572
+ * This method is invoked from within the postReadWriteTransaction operation.
2573
+ * This method is invoked before anything has been committed.
2574
+ *
2575
+ * If changes have been made, it should return a changeset dictionary.
2576
+ * If no changes have been made, it should return nil.
2577
+ *
2578
+ * @see processChangeset:
2579
+ **/
2580
+ - (void)getInternalChangeset:(NSMutableDictionary **)internalChangesetPtr
2581
+ externalChangeset:(NSMutableDictionary **)externalChangesetPtr
2582
+ {
2583
+ // Step 1 of 2 - Process extensions
2584
+ //
2585
+ // Note: Use existing extensions (extensions ivar, not [self extensions]).
2586
+ // There's no need to create any new extConnections at this point.
2587
+
2588
+ __block NSMutableDictionary *internalChangeset_extensions = nil;
2589
+ __block NSMutableDictionary *externalChangeset_extensions = nil;
2590
+
2591
+ [extensions enumerateKeysAndObjectsUsingBlock:^(id extName, id extConnectionObj, BOOL *stop) {
2592
+
2593
+ __unsafe_unretained YapDatabaseExtensionConnection *extConnection = extConnectionObj;
2594
+
2595
+ NSMutableDictionary *internal = nil;
2596
+ NSMutableDictionary *external = nil;
2597
+ BOOL extHasDiskChanges = NO;
2598
+
2599
+ [extConnection getInternalChangeset:&internal
2600
+ externalChangeset:&external
2601
+ hasDiskChanges:&extHasDiskChanges];
2602
+
2603
+ if (internal)
2604
+ {
2605
+ if (internalChangeset_extensions == nil)
2606
+ internalChangeset_extensions =
2607
+ [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForExtensions];
2608
+
2609
+ [internalChangeset_extensions setObject:internal forKey:extName];
2610
+ }
2611
+ if (external)
2612
+ {
2613
+ if (externalChangeset_extensions == nil)
2614
+ externalChangeset_extensions =
2615
+ [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForExtensions];
2616
+
2617
+ [externalChangeset_extensions setObject:external forKey:extName];
2618
+ }
2619
+ if (extHasDiskChanges && !hasDiskChanges)
2620
+ {
2621
+ hasDiskChanges = YES;
2622
+ }
2623
+ }];
2624
+
2625
+ NSMutableDictionary *internalChangeset = nil;
2626
+ NSMutableDictionary *externalChangeset = nil;
2627
+
2628
+ if (internalChangeset_extensions)
2629
+ {
2630
+ internalChangeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForInternalChangeset];
2631
+ [internalChangeset setObject:internalChangeset_extensions forKey:YapDatabaseExtensionsKey];
2632
+ }
2633
+
2634
+ if (externalChangeset_extensions)
2635
+ {
2636
+ externalChangeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForExternalChangeset];
2637
+ [externalChangeset setObject:externalChangeset_extensions forKey:YapDatabaseExtensionsKey];
2638
+ }
2639
+
2640
+ if (registeredExtensionsChanged)
2641
+ {
2642
+ if (internalChangeset == nil)
2643
+ internalChangeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForInternalChangeset];
2644
+
2645
+ [internalChangeset setObject:registeredExtensions forKey:YapDatabaseRegisteredExtensionsKey];
2646
+ [internalChangeset setObject:extensionsOrder forKey:YapDatabaseExtensionsOrderKey];
2647
+ }
2648
+
2649
+ if (registeredTablesChanged)
2650
+ {
2651
+ if (internalChangeset == nil)
2652
+ internalChangeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForInternalChangeset];
2653
+
2654
+ [internalChangeset setObject:registeredTables forKey:YapDatabaseRegisteredTablesKey];
2655
+ }
2656
+
2657
+ // Step 2 of 2 - Process database changes
2658
+ //
2659
+ // Throughout the readwrite transaction we've been keeping a list of what changed.
2660
+ // Copy this change information into the changeset for processing by other connections.
2661
+
2662
+ if ([objectChanges count] > 0 ||
2663
+ [metadataChanges count] > 0 ||
2664
+ [removedKeys count] > 0 ||
2665
+ [removedCollections count] > 0 || allKeysRemoved)
2666
+ {
2667
+ if (internalChangeset == nil)
2668
+ internalChangeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForInternalChangeset];
2669
+
2670
+ if (externalChangeset == nil)
2671
+ externalChangeset = [NSMutableDictionary dictionaryWithSharedKeySet:sharedKeySetForExternalChangeset];
2672
+
2673
+ if ([objectChanges count] > 0)
2674
+ {
2675
+ [internalChangeset setObject:objectChanges forKey:YapDatabaseObjectChangesKey];
2676
+
2677
+ YapSet *immutableObjectChanges = [[YapSet alloc] initWithDictionary:objectChanges];
2678
+ [externalChangeset setObject:immutableObjectChanges forKey:YapDatabaseObjectChangesKey];
2679
+ }
2680
+
2681
+ if ([metadataChanges count] > 0)
2682
+ {
2683
+ [internalChangeset setObject:metadataChanges forKey:YapDatabaseMetadataChangesKey];
2684
+
2685
+ YapSet *immutableMetadataChanges = [[YapSet alloc] initWithDictionary:metadataChanges];
2686
+ [externalChangeset setObject:immutableMetadataChanges forKey:YapDatabaseMetadataChangesKey];
2687
+ }
2688
+
2689
+ if ([removedKeys count] > 0)
2690
+ {
2691
+ [internalChangeset setObject:removedKeys forKey:YapDatabaseRemovedKeysKey];
2692
+
2693
+ YapSet *immutableRemovedKeys = [[YapSet alloc] initWithSet:removedKeys];
2694
+ [externalChangeset setObject:immutableRemovedKeys forKey:YapDatabaseRemovedKeysKey];
2695
+ }
2696
+
2697
+ if ([removedCollections count] > 0)
2698
+ {
2699
+ [internalChangeset setObject:removedCollections forKey:YapDatabaseRemovedCollectionsKey];
2700
+
2701
+ YapSet *immutableRemovedCollections = [[YapSet alloc] initWithSet:removedCollections];
2702
+ [externalChangeset setObject:immutableRemovedCollections
2703
+ forKey:YapDatabaseRemovedCollectionsKey];
2704
+ }
2705
+
2706
+ if (allKeysRemoved)
2707
+ {
2708
+ [internalChangeset setObject:@(YES) forKey:YapDatabaseAllKeysRemovedKey];
2709
+ [externalChangeset setObject:@(YES) forKey:YapDatabaseAllKeysRemovedKey];
2710
+ }
2711
+ }
2712
+
2713
+ *internalChangesetPtr = internalChangeset;
2714
+ *externalChangesetPtr = externalChangeset;
2715
+ }
2716
+
2717
+ /**
2718
+ * This method is invoked with the changeset from a sibling connection.
2719
+ * The connection should update any in-memory components (such as the cache) to properly reflect the changeset.
2720
+ *
2721
+ * @see getInternalChangeset:externalChangeset:
2722
+ **/
2723
+ - (void)processChangeset:(NSDictionary *)changeset
2724
+ {
2725
+ // Did registered extensions change ?
2726
+
2727
+ NSDictionary *changeset_registeredExtensions = [changeset objectForKey:YapDatabaseRegisteredExtensionsKey];
2728
+ if (changeset_registeredExtensions)
2729
+ {
2730
+ // Retain new lists
2731
+
2732
+ registeredExtensions = changeset_registeredExtensions;
2733
+ extensionsOrder = [changeset objectForKey:YapDatabaseExtensionsOrderKey];
2734
+
2735
+ // Remove any extensions that have been dropped
2736
+
2737
+ for (NSString *extName in [extensions allKeys])
2738
+ {
2739
+ if ([registeredExtensions objectForKey:extName] == nil)
2740
+ {
2741
+ YDBLogVerbose(@"Dropping extension: %@", extName);
2742
+
2743
+ [extensions removeObjectForKey:extName];
2744
+ }
2745
+ }
2746
+
2747
+ // Make a note if there are extensions for which we haven't instantiated an extConnection instance.
2748
+ // We lazily load these later, if needed.
2749
+
2750
+ extensionsReady = ([registeredExtensions count] == [extensions count]);
2751
+ }
2752
+
2753
+ // Did registered memory tables change ?
2754
+
2755
+ NSDictionary *changeset_registeredTables = [changeset objectForKey:YapDatabaseRegisteredTablesKey];
2756
+ if (changeset_registeredTables)
2757
+ {
2758
+ // Retain new list
2759
+ registeredTables = changeset_registeredTables;
2760
+ }
2761
+
2762
+ // Allow extensions to process their individual changesets
2763
+
2764
+ NSDictionary *changeset_extensions = [changeset objectForKey:YapDatabaseExtensionsKey];
2765
+ if (changeset_extensions)
2766
+ {
2767
+ // Use existing extensions (extensions ivar, not [self extensions]).
2768
+ // There's no need to create any new extConnections at this point.
2769
+
2770
+ [extensions enumerateKeysAndObjectsUsingBlock:^(id extName, id extConnectionObj, BOOL *stop) {
2771
+
2772
+ __unsafe_unretained YapDatabaseExtensionConnection *extConnection = extConnectionObj;
2773
+
2774
+ NSDictionary *changeset_extensions_extName = [changeset_extensions objectForKey:extName];
2775
+ if (changeset_extensions_extName)
2776
+ {
2777
+ [extConnection processChangeset:changeset_extensions_extName];
2778
+ }
2779
+ }];
2780
+ }
2781
+
2782
+ // Process normal database changset information
2783
+
2784
+ NSDictionary *changeset_objectChanges = [changeset objectForKey:YapDatabaseObjectChangesKey];
2785
+ NSDictionary *changeset_metadataChanges = [changeset objectForKey:YapDatabaseMetadataChangesKey];
2786
+
2787
+ NSSet *changeset_removedKeys = [changeset objectForKey:YapDatabaseRemovedKeysKey];
2788
+ NSSet *changeset_removedCollections = [changeset objectForKey:YapDatabaseRemovedCollectionsKey];
2789
+
2790
+ BOOL changeset_allKeysRemoved = [[changeset objectForKey:YapDatabaseAllKeysRemovedKey] boolValue];
2791
+
2792
+ BOOL hasObjectChanges = [changeset_objectChanges count] > 0;
2793
+ BOOL hasMetadataChanges = [changeset_metadataChanges count] > 0;
2794
+ BOOL hasRemovedKeys = [changeset_removedKeys count] > 0;
2795
+ BOOL hasRemovedCollections = [changeset_removedCollections count] > 0;
2796
+
2797
+ // Update objectCache
2798
+
2799
+ if (changeset_allKeysRemoved && !hasObjectChanges)
2800
+ {
2801
+ // Shortcut: Everything was removed from the database
2802
+
2803
+ [objectCache removeAllObjects];
2804
+ }
2805
+ else if (hasObjectChanges && !hasRemovedKeys && !hasRemovedCollections && !changeset_allKeysRemoved)
2806
+ {
2807
+ // Shortcut: Nothing was removed from the database.
2808
+ // So we can simply enumerate over the changes and update the cache inline as needed.
2809
+
2810
+ id yapNull = [YapNull null]; // value == yapNull : setPrimitive or containment policy
2811
+ id yapTouch = [YapTouch touch]; // value == yapTouch : touchObjectForKey: was used
2812
+
2813
+ BOOL isPolicyContainment = (objectPolicy == YapDatabasePolicyContainment);
2814
+ BOOL isPolicyShare = (objectPolicy == YapDatabasePolicyShare);
2815
+
2816
+ [changeset_objectChanges enumerateKeysAndObjectsUsingBlock:^(id key, id newObject, BOOL *stop) {
2817
+
2818
+ __unsafe_unretained YapCollectionKey *cacheKey = (YapCollectionKey *)key;
2819
+
2820
+ if ([objectCache containsKey:cacheKey])
2821
+ {
2822
+ if (newObject == yapNull)
2823
+ {
2824
+ [objectCache removeObjectForKey:cacheKey];
2825
+ }
2826
+ else if (newObject != yapTouch)
2827
+ {
2828
+ if (isPolicyContainment) {
2829
+ [objectCache removeObjectForKey:cacheKey];
2830
+ }
2831
+ else if (isPolicyShare) {
2832
+ [objectCache setObject:newObject forKey:cacheKey];
2833
+ }
2834
+ else // if (isPolicyCopy)
2835
+ {
2836
+ if ([newObject conformsToProtocol:@protocol(NSCopying)])
2837
+ [objectCache setObject:[newObject copy] forKey:cacheKey];
2838
+ else
2839
+ [objectCache removeObjectForKey:cacheKey];
2840
+ }
2841
+ }
2842
+ }
2843
+ }];
2844
+ }
2845
+ else if (hasObjectChanges || hasRemovedKeys || hasRemovedCollections)
2846
+ {
2847
+ NSUInteger updateCapacity = MIN([objectCache count], [changeset_objectChanges count]);
2848
+ NSUInteger removeCapacity = MIN([objectCache count], [changeset_removedKeys count]);
2849
+
2850
+ NSMutableArray *keysToUpdate = [NSMutableArray arrayWithCapacity:updateCapacity];
2851
+ NSMutableArray *keysToRemove = [NSMutableArray arrayWithCapacity:removeCapacity];
2852
+
2853
+ [objectCache enumerateKeysWithBlock:^(id key, BOOL *stop) {
2854
+
2855
+ // Order matters.
2856
+ // Consider the following database change:
2857
+ //
2858
+ // [transaction removeAllObjectsInAllCollections];
2859
+ // [transaction setObject:obj forKey:key inCollection:collection];
2860
+
2861
+ __unsafe_unretained YapCollectionKey *cacheKey = (YapCollectionKey *)key;
2862
+
2863
+ if ([changeset_objectChanges objectForKey:cacheKey])
2864
+ {
2865
+ [keysToUpdate addObject:key];
2866
+ }
2867
+ else if ([changeset_removedKeys containsObject:cacheKey] ||
2868
+ [changeset_removedCollections containsObject:cacheKey.collection] || changeset_allKeysRemoved)
2869
+ {
2870
+ [keysToRemove addObject:key];
2871
+ }
2872
+ }];
2873
+
2874
+ [objectCache removeObjectsForKeys:keysToRemove];
2875
+
2876
+ id yapNull = [YapNull null]; // value == yapNull : setPrimitive or containment policy
2877
+ id yapTouch = [YapTouch touch]; // value == yapTouch : touchObjectForKey: was used
2878
+
2879
+ BOOL isPolicyContainment = (objectPolicy == YapDatabasePolicyContainment);
2880
+ BOOL isPolicyShare = (objectPolicy == YapDatabasePolicyShare);
2881
+
2882
+ for (YapCollectionKey *cacheKey in keysToUpdate)
2883
+ {
2884
+ id newObject = [changeset_objectChanges objectForKey:cacheKey];
2885
+
2886
+ if (newObject == yapNull)
2887
+ {
2888
+ [objectCache removeObjectForKey:cacheKey];
2889
+ }
2890
+ else if (newObject != yapTouch)
2891
+ {
2892
+ if (isPolicyContainment) {
2893
+ [objectCache removeObjectForKey:cacheKey];
2894
+ }
2895
+ else if (isPolicyShare) {
2896
+ [objectCache setObject:newObject forKey:cacheKey];
2897
+ }
2898
+ else // if (isPolicyCopy)
2899
+ {
2900
+ if ([newObject conformsToProtocol:@protocol(NSCopying)])
2901
+ [objectCache setObject:[newObject copy] forKey:cacheKey];
2902
+ else
2903
+ [objectCache removeObjectForKey:cacheKey];
2904
+ }
2905
+ }
2906
+ }
2907
+ }
2908
+
2909
+ // Update metadataCache
2910
+
2911
+ if (changeset_allKeysRemoved && !hasMetadataChanges)
2912
+ {
2913
+ // Shortcut: Everything was removed from the database
2914
+
2915
+ [metadataCache removeAllObjects];
2916
+ }
2917
+ else if (hasMetadataChanges && !hasRemovedKeys && !hasRemovedCollections && !changeset_allKeysRemoved)
2918
+ {
2919
+ // Shortcut: Nothing was removed from the database.
2920
+ // So we can simply enumerate over the changes and update the cache inline as needed.
2921
+
2922
+ id yapNull = [YapNull null]; // value == yapNull : setPrimitive or containment policy
2923
+ id yapTouch = [YapTouch touch]; // value == yapTouch : touchObjectForKey: was used
2924
+
2925
+ BOOL isPolicyContainment = (metadataPolicy == YapDatabasePolicyContainment);
2926
+ BOOL isPolicyShare = (metadataPolicy == YapDatabasePolicyShare);
2927
+
2928
+ [changeset_metadataChanges enumerateKeysAndObjectsUsingBlock:^(id key, id newMetadata, BOOL *stop) {
2929
+
2930
+ __unsafe_unretained YapCollectionKey *cacheKey = (YapCollectionKey *)key;
2931
+
2932
+ if ([metadataCache containsKey:cacheKey])
2933
+ {
2934
+ if (newMetadata == yapNull)
2935
+ {
2936
+ [metadataCache removeObjectForKey:cacheKey];
2937
+ }
2938
+ else if (newMetadata != yapTouch)
2939
+ {
2940
+ if (isPolicyContainment) {
2941
+ [metadataCache removeObjectForKey:cacheKey];
2942
+ }
2943
+ else if (isPolicyShare) {
2944
+ [metadataCache setObject:newMetadata forKey:cacheKey];
2945
+ }
2946
+ else // if (isPolicyCopy)
2947
+ {
2948
+ if ([newMetadata conformsToProtocol:@protocol(NSCopying)])
2949
+ [metadataCache setObject:[newMetadata copy] forKey:cacheKey];
2950
+ else
2951
+ [metadataCache removeObjectForKey:cacheKey];
2952
+ }
2953
+ }
2954
+ }
2955
+ }];
2956
+ }
2957
+ else if (hasMetadataChanges || hasRemovedKeys || hasRemovedCollections)
2958
+ {
2959
+ NSUInteger updateCapacity = MIN([metadataCache count], [changeset_metadataChanges count]);
2960
+ NSUInteger removeCapacity = MIN([metadataCache count], [changeset_removedKeys count]);
2961
+
2962
+ NSMutableArray *keysToUpdate = [NSMutableArray arrayWithCapacity:updateCapacity];
2963
+ NSMutableArray *keysToRemove = [NSMutableArray arrayWithCapacity:removeCapacity];
2964
+
2965
+ [metadataCache enumerateKeysWithBlock:^(id key, BOOL *stop) {
2966
+
2967
+ // Order matters.
2968
+ // Consider the following database change:
2969
+ //
2970
+ // [transaction removeAllObjectsInAllCollections];
2971
+ // [transaction setObject:obj forKey:key inCollection:collection];
2972
+
2973
+ __unsafe_unretained YapCollectionKey *cacheKey = (YapCollectionKey *)key;
2974
+
2975
+ if ([changeset_metadataChanges objectForKey:cacheKey])
2976
+ {
2977
+ [keysToUpdate addObject:key];
2978
+ }
2979
+ else if ([changeset_removedKeys containsObject:cacheKey] ||
2980
+ [changeset_removedCollections containsObject:cacheKey.collection] || changeset_allKeysRemoved)
2981
+ {
2982
+ [keysToRemove addObject:key];
2983
+ }
2984
+ }];
2985
+
2986
+ [metadataCache removeObjectsForKeys:keysToRemove];
2987
+
2988
+ id yapNull = [YapNull null]; // value == yapNull : setPrimitive or containment policy
2989
+ id yapTouch = [YapTouch touch]; // value == yapTouch : touchObjectForKey: was used
2990
+
2991
+ BOOL isPolicyContainment = (metadataPolicy == YapDatabasePolicyContainment);
2992
+ BOOL isPolicyShare = (metadataPolicy == YapDatabasePolicyShare);
2993
+
2994
+ for (YapCollectionKey *cacheKey in keysToUpdate)
2995
+ {
2996
+ id newMetadata = [changeset_metadataChanges objectForKey:cacheKey];
2997
+
2998
+ if (newMetadata == yapNull)
2999
+ {
3000
+ [metadataCache removeObjectForKey:cacheKey];
3001
+ }
3002
+ else if (newMetadata != yapTouch)
3003
+ {
3004
+ if (isPolicyContainment) {
3005
+ [metadataCache removeObjectForKey:cacheKey];
3006
+ }
3007
+ else if (isPolicyShare) {
3008
+ [metadataCache setObject:newMetadata forKey:cacheKey];
3009
+ }
3010
+ else // if (isPolicyCopy)
3011
+ {
3012
+ if ([newMetadata conformsToProtocol:@protocol(NSCopying)])
3013
+ [metadataCache setObject:[newMetadata copy] forKey:cacheKey];
3014
+ else
3015
+ [metadataCache removeObjectForKey:cacheKey];
3016
+ }
3017
+ }
3018
+ }
3019
+ }
3020
+ }
3021
+
3022
+ /**
3023
+ * Internal method.
3024
+ *
3025
+ * This method is invoked with the changeset from a sibling connection.
3026
+ **/
3027
+ - (void)noteCommittedChanges:(NSDictionary *)changeset
3028
+ {
3029
+ // This method must be invoked from within connectionQueue.
3030
+ // It may be invoked from:
3031
+ //
3032
+ // 1. [database noteCommittedChanges:fromConnection:]
3033
+ // via dispatch_async(connectionQueue, ...)
3034
+ //
3035
+ // 2. [self preReadTransaction:]
3036
+ // via dispatch_X(connectionQueue) -> dispatch_sync(database->snapshotQueue)
3037
+ //
3038
+ // 3. [self preReadWriteTransaction:]
3039
+ // via dispatch_X(connectionQueue) -> dispatch_sync(database->snapshotQueue)
3040
+ //
3041
+ // In case 1 (the common case) we can see IsOnConnectionQueueKey.
3042
+ // In case 2 & 3 (the edge cases) we can see IsOnSnapshotQueueKey.
3043
+
3044
+ NSAssert(dispatch_get_specific(IsOnConnectionQueueKey) ||
3045
+ dispatch_get_specific(database->IsOnSnapshotQueueKey), @"Must be invoked within connectionQueue");
3046
+
3047
+ // Grab the new snapshot.
3048
+ // This tells us the minimum snapshot we could get if we started a transaction right now.
3049
+
3050
+ uint64_t changesetSnapshot = [[changeset objectForKey:YapDatabaseSnapshotKey] unsignedLongLongValue];
3051
+
3052
+ if (changesetSnapshot <= snapshot)
3053
+ {
3054
+ // We already noted this changeset.
3055
+ //
3056
+ // There is a "race condition" that occasionally happens when a readonly transaction is started
3057
+ // around the same instant a readwrite transaction finishes committing its changes to disk.
3058
+ // The readonly transaction enters our transaction state queue (to start) before
3059
+ // the readwrite transaction enters our transaction state queue (to finish).
3060
+ // However the readonly transaction gets a database snapshot post readwrite commit.
3061
+ // That is, the readonly transaction can read the changes from the readwrite transaction at the sqlite layer,
3062
+ // even though the readwrite transaction hasn't completed within the yap database layer.
3063
+ //
3064
+ // This race condition is handled automatically within the preReadTransaction method.
3065
+ // In fact, it invokes this method to handle the race condition.
3066
+ // Thus this method could be invoked twice to handle the same changeset.
3067
+ // So catching it here and ignoring it is simply a minor optimization to avoid duplicate work.
3068
+
3069
+ YDBLogVerbose(@"Ignoring previously processed changeset %lu for connection %@, database %@",
3070
+ (unsigned long)changesetSnapshot, self, database);
3071
+
3072
+ return;
3073
+ }
3074
+
3075
+ if (longLivedReadTransaction)
3076
+ {
3077
+ if (dispatch_get_specific(database->IsOnSnapshotQueueKey))
3078
+ {
3079
+ // This method is being invoked from preReadTransaction:.
3080
+ // We are to process the changeset for it.
3081
+
3082
+ [processedChangesets addObject:changeset];
3083
+ }
3084
+ else
3085
+ {
3086
+ // This method is being invoked from [database noteCommittedChanges:].
3087
+ // We cannot process the changeset yet.
3088
+ // We must wait for the longLivedReadTransaction to be reset.
3089
+
3090
+ YDBLogVerbose(@"Storing pending changeset %lu for connection %@, database %@",
3091
+ (unsigned long)changesetSnapshot, self, database);
3092
+
3093
+ [pendingChangesets addObject:changeset];
3094
+ return;
3095
+ }
3096
+ }
3097
+
3098
+ // Changeset processing
3099
+
3100
+ YDBLogVerbose(@"Processing changeset %lu for connection %@, database %@",
3101
+ (unsigned long)changesetSnapshot, self, database);
3102
+
3103
+ snapshot = changesetSnapshot;
3104
+ [self processChangeset:changeset];
3105
+ }
3106
+
3107
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3108
+ #pragma mark Changeset Inspection
3109
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3110
+
3111
+ - (BOOL)hasChangeForCollection:(NSString *)collection
3112
+ inNotifications:(NSArray *)notifications
3113
+ includingObjectChanges:(BOOL)includeObjectChanges
3114
+ metadataChanges:(BOOL)includeMetadataChanges
3115
+ {
3116
+ if (collection == nil)
3117
+ collection = @"";
3118
+
3119
+ for (NSNotification *notification in notifications)
3120
+ {
3121
+ if (![notification isKindOfClass:[NSNotification class]])
3122
+ {
3123
+ YDBLogWarn(@"%@ - notifications parameter contains non-NSNotification object", THIS_METHOD);
3124
+ continue;
3125
+ }
3126
+
3127
+ NSDictionary *changeset = notification.userInfo;
3128
+
3129
+ if (includeObjectChanges)
3130
+ {
3131
+ YapSet *changeset_objectChanges = [changeset objectForKey:YapDatabaseObjectChangesKey];
3132
+ for (YapCollectionKey *collectionKey in changeset_objectChanges)
3133
+ {
3134
+ if ([collectionKey.collection isEqualToString:collection])
3135
+ {
3136
+ return YES;
3137
+ }
3138
+ }
3139
+ }
3140
+
3141
+ if (includeMetadataChanges)
3142
+ {
3143
+ YapSet *changeset_metadataChanges = [changeset objectForKey:YapDatabaseMetadataChangesKey];
3144
+ for (YapCollectionKey *collectionKey in changeset_metadataChanges)
3145
+ {
3146
+ if ([collectionKey.collection isEqualToString:collection])
3147
+ {
3148
+ return YES;
3149
+ }
3150
+ }
3151
+ }
3152
+
3153
+ YapSet *changeset_removedKeys = [changeset objectForKey:YapDatabaseRemovedKeysKey];
3154
+ for (YapCollectionKey *collectionKey in changeset_removedKeys)
3155
+ {
3156
+ if ([collectionKey.collection isEqualToString:collection])
3157
+ {
3158
+ return YES;
3159
+ }
3160
+ }
3161
+
3162
+ YapSet *changeset_removedCollections = [changeset objectForKey:YapDatabaseRemovedCollectionsKey];
3163
+ if ([changeset_removedCollections containsObject:collection])
3164
+ return YES;
3165
+
3166
+ BOOL changeset_allKeysRemoved = [[changeset objectForKey:YapDatabaseAllKeysRemovedKey] boolValue];
3167
+ if (changeset_allKeysRemoved)
3168
+ return YES;
3169
+ }
3170
+
3171
+ return NO;
3172
+ }
3173
+
3174
+ - (BOOL)hasChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications
3175
+ {
3176
+ return [self hasChangeForCollection:collection
3177
+ inNotifications:notifications
3178
+ includingObjectChanges:YES
3179
+ metadataChanges:YES];
3180
+ }
3181
+
3182
+ - (BOOL)hasObjectChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications
3183
+ {
3184
+ return [self hasChangeForCollection:collection
3185
+ inNotifications:notifications
3186
+ includingObjectChanges:YES
3187
+ metadataChanges:NO];
3188
+ }
3189
+
3190
+ - (BOOL)hasMetadataChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications
3191
+ {
3192
+ return [self hasChangeForCollection:collection
3193
+ inNotifications:notifications
3194
+ includingObjectChanges:NO
3195
+ metadataChanges:YES];
3196
+ }
3197
+
3198
+ // Query for a change to a particular key/collection tuple
3199
+
3200
+ - (BOOL)hasChangeForKey:(NSString *)key
3201
+ inCollection:(NSString *)collection
3202
+ inNotifications:(NSArray *)notifications
3203
+ includingObjectChanges:(BOOL)includeObjectChanges
3204
+ metadataChanges:(BOOL)includeMetadataChanges
3205
+ {
3206
+ if (collection == nil)
3207
+ collection = @"";
3208
+
3209
+ YapCollectionKey *collectionKey = [[YapCollectionKey alloc] initWithCollection:collection key:key];
3210
+
3211
+ for (NSNotification *notification in notifications)
3212
+ {
3213
+ if (![notification isKindOfClass:[NSNotification class]])
3214
+ {
3215
+ YDBLogWarn(@"%@ - notifications parameter contains non-NSNotification object", THIS_METHOD);
3216
+ continue;
3217
+ }
3218
+
3219
+ NSDictionary *changeset = notification.userInfo;
3220
+
3221
+ if (includeObjectChanges)
3222
+ {
3223
+ YapSet *changeset_objectChanges = [changeset objectForKey:YapDatabaseObjectChangesKey];
3224
+ if ([changeset_objectChanges containsObject:collectionKey])
3225
+ return YES;
3226
+ }
3227
+
3228
+ if (includeMetadataChanges)
3229
+ {
3230
+ YapSet *changeset_metadataChanges = [changeset objectForKey:YapDatabaseMetadataChangesKey];
3231
+ if ([changeset_metadataChanges containsObject:collectionKey])
3232
+ return YES;
3233
+ }
3234
+
3235
+ YapSet *changeset_removedKeys = [changeset objectForKey:YapDatabaseRemovedKeysKey];
3236
+ if ([changeset_removedKeys containsObject:collectionKey])
3237
+ return YES;
3238
+
3239
+ YapSet *changeset_removedCollections = [changeset objectForKey:YapDatabaseRemovedCollectionsKey];
3240
+ if ([changeset_removedCollections containsObject:collection])
3241
+ return YES;
3242
+
3243
+ BOOL changeset_allKeysRemoved = [[changeset objectForKey:YapDatabaseAllKeysRemovedKey] boolValue];
3244
+ if (changeset_allKeysRemoved)
3245
+ return YES;
3246
+ }
3247
+
3248
+ return NO;
3249
+ }
3250
+
3251
+ - (BOOL)hasChangeForKey:(NSString *)key
3252
+ inCollection:(NSString *)collection
3253
+ inNotifications:(NSArray *)notifications
3254
+ {
3255
+ return [self hasChangeForKey:key
3256
+ inCollection:collection
3257
+ inNotifications:notifications
3258
+ includingObjectChanges:YES
3259
+ metadataChanges:YES];
3260
+ }
3261
+
3262
+ - (BOOL)hasObjectChangeForKey:(NSString *)key
3263
+ inCollection:(NSString *)collection
3264
+ inNotifications:(NSArray *)notifications
3265
+ {
3266
+ return [self hasChangeForKey:key
3267
+ inCollection:collection
3268
+ inNotifications:notifications
3269
+ includingObjectChanges:YES
3270
+ metadataChanges:NO];
3271
+ }
3272
+
3273
+ - (BOOL)hasMetadataChangeForKey:(NSString *)key
3274
+ inCollection:(NSString *)collection
3275
+ inNotifications:(NSArray *)notifications
3276
+ {
3277
+ return [self hasChangeForKey:key
3278
+ inCollection:collection
3279
+ inNotifications:notifications
3280
+ includingObjectChanges:NO
3281
+ metadataChanges:YES];
3282
+ }
3283
+
3284
+ // Query for a change to a particular set of keys in a collection
3285
+
3286
+ - (BOOL)hasChangeForAnyKeys:(NSSet *)keys
3287
+ inCollection:(NSString *)collection
3288
+ inNotifications:(NSArray *)notifications
3289
+ includingObjectChanges:(BOOL)includeObjectChanges
3290
+ metadataChanges:(BOOL)includeMetadataChanges
3291
+ {
3292
+ if ([keys count] == 0) return NO;
3293
+ if (collection == nil)
3294
+ collection = @"";
3295
+
3296
+ for (NSNotification *notification in notifications)
3297
+ {
3298
+ if (![notification isKindOfClass:[NSNotification class]])
3299
+ {
3300
+ YDBLogWarn(@"%@ - notifications parameter contains non-NSNotification object", THIS_METHOD);
3301
+ continue;
3302
+ }
3303
+
3304
+ NSDictionary *changeset = notification.userInfo;
3305
+
3306
+ if (includeObjectChanges)
3307
+ {
3308
+ YapSet *changeset_objectChanges = [changeset objectForKey:YapDatabaseObjectChangesKey];
3309
+ for (YapCollectionKey *collectionKey in changeset_objectChanges)
3310
+ {
3311
+ if ([collectionKey.collection isEqualToString:collection])
3312
+ {
3313
+ if ([keys containsObject:collectionKey.key])
3314
+ {
3315
+ return YES;
3316
+ }
3317
+ }
3318
+ }
3319
+ }
3320
+
3321
+ if (includeMetadataChanges)
3322
+ {
3323
+ YapSet *changeset_metadataChanges = [changeset objectForKey:YapDatabaseMetadataChangesKey];
3324
+ for (YapCollectionKey *collectionKey in changeset_metadataChanges)
3325
+ {
3326
+ if ([collectionKey.collection isEqualToString:collection])
3327
+ {
3328
+ if ([keys containsObject:collectionKey.key])
3329
+ {
3330
+ return YES;
3331
+ }
3332
+ }
3333
+ }
3334
+ }
3335
+
3336
+ YapSet *changeset_removedKeys = [changeset objectForKey:YapDatabaseRemovedKeysKey];
3337
+ for (YapCollectionKey *collectionKey in changeset_removedKeys)
3338
+ {
3339
+ if ([collectionKey.collection isEqualToString:collection])
3340
+ {
3341
+ if ([keys containsObject:collectionKey.key])
3342
+ {
3343
+ return YES;
3344
+ }
3345
+ }
3346
+ }
3347
+
3348
+ YapSet *changeset_removedCollections = [changeset objectForKey:YapDatabaseRemovedCollectionsKey];
3349
+ if ([changeset_removedCollections containsObject:collection])
3350
+ return YES;
3351
+
3352
+ BOOL changeset_allKeysRemoved = [[changeset objectForKey:YapDatabaseAllKeysRemovedKey] boolValue];
3353
+ if (changeset_allKeysRemoved)
3354
+ return YES;
3355
+ }
3356
+
3357
+ return NO;
3358
+ }
3359
+
3360
+ - (BOOL)hasChangeForAnyKeys:(NSSet *)keys
3361
+ inCollection:(NSString *)collection
3362
+ inNotifications:(NSArray *)notifications
3363
+ {
3364
+ return [self hasChangeForAnyKeys:keys
3365
+ inCollection:collection
3366
+ inNotifications:notifications
3367
+ includingObjectChanges:YES
3368
+ metadataChanges:YES];
3369
+ }
3370
+
3371
+ - (BOOL)hasObjectChangeForAnyKeys:(NSSet *)keys
3372
+ inCollection:(NSString *)collection
3373
+ inNotifications:(NSArray *)notifications
3374
+ {
3375
+ return [self hasChangeForAnyKeys:keys
3376
+ inCollection:collection
3377
+ inNotifications:notifications
3378
+ includingObjectChanges:YES
3379
+ metadataChanges:NO];
3380
+ }
3381
+
3382
+ - (BOOL)hasMetadataChangeForAnyKeys:(NSSet *)keys
3383
+ inCollection:(NSString *)collection
3384
+ inNotifications:(NSArray *)notifications
3385
+ {
3386
+ return [self hasChangeForAnyKeys:keys
3387
+ inCollection:collection
3388
+ inNotifications:notifications
3389
+ includingObjectChanges:NO
3390
+ metadataChanges:YES];
3391
+ }
3392
+
3393
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3394
+ #pragma mark Extensions
3395
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3396
+
3397
+ /**
3398
+ * Creates or fetches the extension with the given name.
3399
+ * If this connection has not yet initialized the proper extensions connection, it is done automatically.
3400
+ *
3401
+ * @return
3402
+ * A subclass of YapDatabaseExtensionConnection,
3403
+ * according to the type of extension registered under the given name.
3404
+ *
3405
+ * One must register an extension with the database before it can be accessed from within connections or transactions.
3406
+ * After registration everything works automatically using just the registered extension name.
3407
+ *
3408
+ * @see [YapDatabase registerExtension:withName:]
3409
+ **/
3410
+ - (id)extension:(NSString *)extName
3411
+ {
3412
+ // This method is PUBLIC.
3413
+ //
3414
+ // This method returns a subclass of YapDatabaseExtensionConnection.
3415
+ // To get:
3416
+ // - YapDatabaseExtension => [database registeredExtension:@"registeredNameOfExtension"]
3417
+ // - YapDatabaseExtensionConnection => [databaseConnection extension:@"registeredNameOfExtension"]
3418
+ // - YapDatabaseExtensionTransaction => [databaseTransaction extension:@"registeredNameOfExtension"]
3419
+
3420
+ __block id extConnection = nil;
3421
+
3422
+ dispatch_block_t block = ^{
3423
+
3424
+ extConnection = [extensions objectForKey:extName];
3425
+
3426
+ if (!extConnection && !extensionsReady)
3427
+ {
3428
+ // We don't have an existing connection for the extension.
3429
+ // Create one (if we can).
3430
+
3431
+ YapDatabaseExtension *ext = [registeredExtensions objectForKey:extName];
3432
+ if (ext)
3433
+ {
3434
+ extConnection = [ext newConnection:self];
3435
+ [extensions setObject:extConnection forKey:extName];
3436
+ }
3437
+ }
3438
+ };
3439
+
3440
+ if (dispatch_get_specific(IsOnConnectionQueueKey))
3441
+ block();
3442
+ else
3443
+ dispatch_sync(connectionQueue, block);
3444
+
3445
+ return extConnection;
3446
+ }
3447
+
3448
+ - (id)ext:(NSString *)extensionName
3449
+ {
3450
+ // The "+ (void)load" method swizzles the implementation of this class
3451
+ // to point to the implementation of the extension: method.
3452
+ //
3453
+ // So the two methods are literally the same thing.
3454
+
3455
+ return [self extension:extensionName]; // This method is swizzled !
3456
+ }
3457
+
3458
+ - (NSDictionary *)extensions
3459
+ {
3460
+ // This method is INTERNAL
3461
+
3462
+ if (!extensionsReady)
3463
+ {
3464
+ [registeredExtensions enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
3465
+
3466
+ __unsafe_unretained NSString *extName = key;
3467
+ __unsafe_unretained YapDatabaseExtension *ext = obj;
3468
+
3469
+ if ([extensions objectForKey:extName] == nil)
3470
+ {
3471
+ id extConnection = [ext newConnection:self];
3472
+ [extensions setObject:extConnection forKey:extName];
3473
+ }
3474
+ }];
3475
+
3476
+ extensionsReady = YES;
3477
+ }
3478
+
3479
+ return extensions;
3480
+ }
3481
+
3482
+ - (BOOL)registerExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName
3483
+ {
3484
+ NSAssert(dispatch_get_specific(database->IsOnWriteQueueKey), @"Must go through writeQueue.");
3485
+
3486
+ __block BOOL result = NO;
3487
+
3488
+ dispatch_sync(connectionQueue, ^{ @autoreleasepool {
3489
+
3490
+ YapDatabaseReadWriteTransaction *transaction = [self newReadWriteTransaction];
3491
+ [self preReadWriteTransaction:transaction];
3492
+
3493
+ YapDatabaseExtensionConnection *extensionConnection;
3494
+ YapDatabaseExtensionTransaction *extensionTransaction;
3495
+
3496
+ extensionConnection = [extension newConnection:self];
3497
+ extensionTransaction = [extensionConnection newReadWriteTransaction:transaction];
3498
+
3499
+ BOOL needsClassValue = NO;
3500
+ [self willRegisterExtension:extension
3501
+ withTransaction:transaction
3502
+ needsClassValue:&needsClassValue];
3503
+
3504
+ result = [extensionTransaction createIfNeeded];
3505
+
3506
+ if (result)
3507
+ {
3508
+ [self didRegisterExtension:extension
3509
+ withTransaction:transaction
3510
+ needsClassValue:needsClassValue];
3511
+
3512
+ [self addRegisteredExtensionConnection:extensionConnection];
3513
+ [transaction addRegisteredExtensionTransaction:extensionTransaction];
3514
+ }
3515
+ else
3516
+ {
3517
+ [transaction rollback];
3518
+ }
3519
+
3520
+ [self postReadWriteTransaction:transaction];
3521
+ registeredExtensionsChanged = NO;
3522
+ }});
3523
+
3524
+ return result;
3525
+ }
3526
+
3527
+ - (void)unregisterExtension:(NSString *)extensionName
3528
+ {
3529
+ NSAssert(dispatch_get_specific(database->IsOnWriteQueueKey), @"Must go through writeQueue.");
3530
+
3531
+ dispatch_sync(connectionQueue, ^{ @autoreleasepool {
3532
+
3533
+ YapDatabaseReadWriteTransaction *transaction = [self newReadWriteTransaction];
3534
+ [self preReadWriteTransaction:transaction];
3535
+
3536
+ NSString *className = [transaction stringValueForKey:@"class" extension:extensionName];
3537
+ Class class = NSClassFromString(className);
3538
+
3539
+ if (className == nil)
3540
+ {
3541
+ YDBLogWarn(@"Unable to unregister extension(%@). Doesn't appear to be registered.", extensionName);
3542
+ }
3543
+ else if (class == NULL)
3544
+ {
3545
+ YDBLogError(@"Unable to unregister extension(%@) with unknown class(%@)", extensionName, className);
3546
+ }
3547
+ if (![class isSubclassOfClass:[YapDatabaseExtension class]])
3548
+ {
3549
+ YDBLogError(@"Unable to unregister extension(%@) with improper class(%@)", extensionName, className);
3550
+ }
3551
+ else
3552
+ {
3553
+ // Drop tables
3554
+ [class dropTablesForRegisteredName:extensionName withTransaction:transaction];
3555
+
3556
+ // Drop preferences (rows in yap2 table)
3557
+ [transaction removeAllValuesForExtension:extensionName];
3558
+
3559
+ [self didUnregisterExtension:extensionName];
3560
+
3561
+ [self removeRegisteredExtensionConnection:extensionName];
3562
+ [transaction removeRegisteredExtensionTransaction:extensionName];
3563
+ }
3564
+
3565
+ [self postReadWriteTransaction:transaction];
3566
+ registeredExtensionsChanged = NO;
3567
+ }});
3568
+ }
3569
+
3570
+ - (void)willRegisterExtension:(YapDatabaseExtension *)extension
3571
+ withTransaction:(YapDatabaseReadWriteTransaction *)transaction
3572
+ needsClassValue:(BOOL *)needsClassValuePtr
3573
+ {
3574
+ // This method is INTERNAL
3575
+ //
3576
+ // The class name of every registered extension is recorded in the yap2 table.
3577
+ // We ensure that re-registrations under the same name use the same extension class.
3578
+ // If we detect a change, we auto-unregister the previous extension.
3579
+ //
3580
+ // Note: @"class" is a reserved key for all extensions.
3581
+
3582
+ NSString *extensionName = extension.registeredName;
3583
+
3584
+ NSString *prevExtensionClassName = [transaction stringValueForKey:@"class" extension:extensionName];
3585
+ if (prevExtensionClassName == nil)
3586
+ {
3587
+ // First time registration
3588
+ *needsClassValuePtr = YES;
3589
+ return;
3590
+ }
3591
+
3592
+ NSString *extensionClassName = NSStringFromClass([extension class]);
3593
+
3594
+ if ([extensionClassName isEqualToString:prevExtensionClassName])
3595
+ {
3596
+ // Re-registration
3597
+ *needsClassValuePtr = NO;
3598
+ return;
3599
+ }
3600
+
3601
+ NSArray *otherValidClassNames = [[extension class] previousClassNames];
3602
+
3603
+ if ([otherValidClassNames containsObject:prevExtensionClassName])
3604
+ {
3605
+ // The extension class was renamed.
3606
+ // We should update the class value in the database.
3607
+ *needsClassValuePtr = YES;
3608
+ return;
3609
+ }
3610
+
3611
+ YDBLogWarn(@"Dropping tables for previously registered extension with name(%@), class(%@) for new class(%@)",
3612
+ extensionName, prevExtensionClassName, extensionClassName);
3613
+
3614
+ Class prevExtensionClass = NSClassFromString(prevExtensionClassName);
3615
+
3616
+ if (prevExtensionClass == NULL)
3617
+ {
3618
+ YDBLogError(@"Unable to drop tables for previously registered extension with name(%@), unknown class(%@)",
3619
+ extensionName, prevExtensionClassName);
3620
+ }
3621
+ else if (![prevExtensionClass isSubclassOfClass:[YapDatabaseExtension class]])
3622
+ {
3623
+ YDBLogError(@"Unable to drop tables for previously registered extension with name(%@), invalid class(%@)",
3624
+ extensionName, prevExtensionClassName);
3625
+ }
3626
+ else
3627
+ {
3628
+ // Drop tables
3629
+ [prevExtensionClass dropTablesForRegisteredName:extensionName withTransaction:transaction];
3630
+
3631
+ // Drop preferences (rows in yap2 table)
3632
+ [transaction removeAllValuesForExtension:extensionName];
3633
+ }
3634
+
3635
+ *needsClassValuePtr = YES;
3636
+ }
3637
+
3638
+ - (void)didRegisterExtension:(YapDatabaseExtension *)extension
3639
+ withTransaction:(YapDatabaseReadWriteTransaction *)transaction
3640
+ needsClassValue:(BOOL)needsClassValue
3641
+ {
3642
+ // This method is INTERNAL
3643
+
3644
+ NSString *extensionName = extension.registeredName;
3645
+
3646
+ // Record the class name of the extension in the yap2 table.
3647
+
3648
+ if (needsClassValue)
3649
+ {
3650
+ NSString *extensionClassName = NSStringFromClass([extension class]);
3651
+
3652
+ [transaction setStringValue:extensionClassName forKey:@"class" extension:extensionName];
3653
+ }
3654
+
3655
+ // Update the list of registered extensions.
3656
+
3657
+ NSMutableDictionary *newRegisteredExtensions = [registeredExtensions mutableCopy];
3658
+ [newRegisteredExtensions setObject:extension forKey:extensionName];
3659
+
3660
+ NSMutableArray *newExtensionsOrder = [extensionsOrder mutableCopy];
3661
+ [newExtensionsOrder addObject:extensionName];
3662
+
3663
+ registeredExtensions = [newRegisteredExtensions copy];
3664
+ extensionsOrder = [newExtensionsOrder copy];
3665
+ extensionsReady = NO;
3666
+
3667
+ sharedKeySetForExtensions = [NSDictionary sharedKeySetForKeys:[registeredExtensions allKeys]];
3668
+
3669
+ // Set the registeredExtensionsChanged flag.
3670
+ // This will be consulted during the creation of the changeset,
3671
+ // and will cause us to add the updated registeredExtensions to the list of changes.
3672
+ // It will then get propogated to the database, and all other connections.
3673
+
3674
+ registeredExtensionsChanged = YES;
3675
+ }
3676
+
3677
+ - (void)didUnregisterExtension:(NSString *)extensionName
3678
+ {
3679
+ // This method is INTERNAL
3680
+
3681
+ if ([registeredExtensions objectForKey:extensionName])
3682
+ {
3683
+ NSMutableDictionary *newRegisteredExtensions = [registeredExtensions mutableCopy];
3684
+ [newRegisteredExtensions removeObjectForKey:extensionName];
3685
+
3686
+ registeredExtensions = [newRegisteredExtensions copy];
3687
+ extensionsReady = NO;
3688
+
3689
+ sharedKeySetForExtensions = [NSDictionary sharedKeySetForKeys:[registeredExtensions allKeys]];
3690
+
3691
+ // Set the registeredExtensionsChanged flag.
3692
+ // This will be consulted during the creation of the changeset,
3693
+ // and will cause us to add the updated registeredExtensions to the list of changes.
3694
+ // It will then get propogated to the database, and all other connections.
3695
+
3696
+ registeredExtensionsChanged = YES;
3697
+ }
3698
+ }
3699
+
3700
+ - (void)addRegisteredExtensionConnection:(YapDatabaseExtensionConnection *)extConnection
3701
+ {
3702
+ // This method is INTERNAL
3703
+
3704
+ if (extensions == nil)
3705
+ extensions = [[NSMutableDictionary alloc] init];
3706
+
3707
+ NSString *extName = [[extConnection extension] registeredName];
3708
+
3709
+ [extensions setObject:extConnection forKey:extName];
3710
+ }
3711
+
3712
+ - (void)removeRegisteredExtensionConnection:(NSString *)extName
3713
+ {
3714
+ // This method is INTERNAL
3715
+
3716
+ [extensions removeObjectForKey:extName];
3717
+ }
3718
+
3719
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3720
+ #pragma mark Memory Tables
3721
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3722
+
3723
+ - (NSDictionary *)registeredTables
3724
+ {
3725
+ // This method is INTERNAL
3726
+
3727
+ return registeredTables;
3728
+ }
3729
+
3730
+ - (BOOL)registerTable:(YapMemoryTable *)table withName:(NSString *)name
3731
+ {
3732
+ // This method is INTERNAL
3733
+
3734
+ if ([registeredTables objectForKey:name])
3735
+ return NO;
3736
+
3737
+ NSMutableDictionary *newRegisteredTables = [registeredTables mutableCopy];
3738
+ [newRegisteredTables setObject:table forKey:name];
3739
+
3740
+ registeredTables = [newRegisteredTables copy];
3741
+ registeredTablesChanged = YES;
3742
+
3743
+ return YES;
3744
+ }
3745
+
3746
+ - (void)unregisterTableWithName:(NSString *)name
3747
+ {
3748
+ // This method is INTERNAL
3749
+
3750
+ if ([registeredTables objectForKey:name])
3751
+ {
3752
+ NSMutableDictionary *newRegisteredTables = [registeredTables mutableCopy];
3753
+ [newRegisteredTables removeObjectForKey:name];
3754
+
3755
+ registeredTables = [newRegisteredTables copy];
3756
+ registeredTablesChanged = YES;
3757
+ }
3758
+ }
3759
+
3760
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3761
+ #pragma mark Utilities
3762
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3763
+
3764
+ /**
3765
+ * Long-lived read transactions are a great way to achive stability, especially in places like the main-thread.
3766
+ * However, they pose a unique problem. These long-lived transactions often start out by
3767
+ * locking the WAL (write ahead log). This prevents the WAL from ever getting reset,
3768
+ * and thus causes the WAL to potentially grow infinitely large. In order to allow the WAL to get properly reset,
3769
+ * we need the long-lived read transactions to "reset". That is, without changing their stable state (their snapshot),
3770
+ * we need them to restart the transaction, but this time without locking this WAL.
3771
+ *
3772
+ * We use the maybeResetLongLivedReadTransaction method to achieve this.
3773
+ **/
3774
+ - (void)maybeResetLongLivedReadTransaction
3775
+ {
3776
+ // Async dispatch onto the writeQueue so we know there aren't any other active readWrite transactions
3777
+
3778
+ dispatch_async(database->writeQueue, ^{
3779
+
3780
+ // Pause the writeQueue so readWrite operations can't interfere with us.
3781
+ // We abort if our connection has a readWrite transaction pending.
3782
+
3783
+ BOOL abort = NO;
3784
+
3785
+ OSSpinLockLock(&lock);
3786
+ {
3787
+ if (activeReadWriteTransaction) {
3788
+ abort = YES;
3789
+ }
3790
+ else if (!writeQueueSuspended) {
3791
+ dispatch_suspend(database->writeQueue);
3792
+ writeQueueSuspended = YES;
3793
+ }
3794
+ }
3795
+ OSSpinLockUnlock(&lock);
3796
+
3797
+ if (abort) return;
3798
+
3799
+ // Async dispatch onto our connectionQueue.
3800
+
3801
+ dispatch_async(connectionQueue, ^{
3802
+
3803
+ // If possible, silently reset the longLivedReadTransaction (same snapshot, no longer locking the WAL)
3804
+
3805
+ if (longLivedReadTransaction && (snapshot == [database snapshot]))
3806
+ {
3807
+ NSArray *empty = [self beginLongLivedReadTransaction];
3808
+
3809
+ if ([empty count] != 0)
3810
+ {
3811
+ YDBLogError(@"Core logic failure! "
3812
+ @"Silent longLivedReadTransaction reset resulted in non-empty notification array!");
3813
+ }
3814
+ }
3815
+
3816
+ // Resume the writeQueue
3817
+
3818
+ OSSpinLockLock(&lock);
3819
+ {
3820
+ if (writeQueueSuspended) {
3821
+ dispatch_resume(database->writeQueue);
3822
+ writeQueueSuspended = NO;
3823
+ }
3824
+ }
3825
+ OSSpinLockUnlock(&lock);
3826
+ });
3827
+ });
3828
+ }
3829
+
3830
+ NS_INLINE void __preWriteQueue(YapDatabaseConnection *connection)
3831
+ {
3832
+ OSSpinLockLock(&connection->lock);
3833
+ {
3834
+ if (connection->writeQueueSuspended) {
3835
+ dispatch_resume(connection->database->writeQueue);
3836
+ connection->writeQueueSuspended = NO;
3837
+ }
3838
+ connection->activeReadWriteTransaction = YES;
3839
+ }
3840
+ OSSpinLockUnlock(&connection->lock);
3841
+ }
3842
+
3843
+ NS_INLINE void __postWriteQueue(YapDatabaseConnection *connection)
3844
+ {
3845
+ OSSpinLockLock(&connection->lock);
3846
+ {
3847
+ connection->activeReadWriteTransaction = NO;
3848
+ }
3849
+ OSSpinLockUnlock(&connection->lock);
3850
+ }
3851
+
3852
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3853
+ #pragma mark Exceptions
3854
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3855
+
3856
+ - (NSException *)implicitlyEndingLongLivedReadTransactionException
3857
+ {
3858
+ NSString *reason = [NSString stringWithFormat:
3859
+ @"Database <%@: %p> had long-lived read transaction implicitly ended by executing a read-write transaction.",
3860
+ NSStringFromClass([self class]), self];
3861
+
3862
+ NSDictionary *userInfo = @{ NSLocalizedRecoverySuggestionErrorKey:
3863
+ @"Connections with long-lived read transactions are generally designed to be read-only connections."
3864
+ @" As such, you'll want to use a separate connection for the read-write transaction."
3865
+ @" If this is not the case (very, very, very rare) you can disable this exception using"
3866
+ @" disableExceptionsForImplicitlyEndingLongLivedReadTransaction."
3867
+ @" Keep in mind that if you disable these exceptions without understanding why they're enabled by default"
3868
+ @" then you're inevitably creating a hard-to-reproduce bug and likely a few crashes too."
3869
+ @" Don't be lazy. You've been warned."};
3870
+
3871
+ return [NSException exceptionWithName:@"YapDatabaseException" reason:reason userInfo:userInfo];
3872
+ }
3873
+
3874
+ @end