motion-yapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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