motion-yapper 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +63 -0
- data/README.md +16 -0
- data/Rakefile +20 -0
- data/app/app_delegate.rb +5 -0
- data/lib/yapper.rb +30 -0
- data/lib/yapper/attachment.rb +48 -0
- data/lib/yapper/bson.rb +20 -0
- data/lib/yapper/config.rb +18 -0
- data/lib/yapper/db.rb +151 -0
- data/lib/yapper/document.rb +54 -0
- data/lib/yapper/document/attachment.rb +26 -0
- data/lib/yapper/document/callbacks.rb +86 -0
- data/lib/yapper/document/persistance.rb +171 -0
- data/lib/yapper/document/relation.rb +84 -0
- data/lib/yapper/document/selection.rb +100 -0
- data/lib/yapper/extensions.rb +80 -0
- data/lib/yapper/log.rb +5 -0
- data/lib/yapper/sync.rb +134 -0
- data/lib/yapper/sync/data.rb +12 -0
- data/lib/yapper/sync/event.rb +194 -0
- data/lib/yapper/sync/queue.rb +164 -0
- data/lib/yapper/timestamps.rb +16 -0
- data/lib/yapper/version.rb +3 -0
- data/lib/yapper/yapper.rb +2 -0
- data/spec/helpers/time_helper.rb +3 -0
- data/spec/integration/all_spec.rb +40 -0
- data/spec/integration/callback_spec.rb +87 -0
- data/spec/integration/db_spec.rb +40 -0
- data/spec/integration/find_spec.rb +51 -0
- data/spec/integration/persistance_spec.rb +118 -0
- data/spec/integration/relation_spec.rb +174 -0
- data/spec/integration/sync_spec.rb +42 -0
- data/spec/integration/timestamps_spec.rb +34 -0
- data/spec/integration/types_spec.rb +23 -0
- data/spec/integration/when_spec.rb +29 -0
- data/spec/integration/where_spec.rb +132 -0
- data/vendor/Podfile.lock +24 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPClient.h +641 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPClient.m +1396 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPRequestOperation.h +133 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFHTTPRequestOperation.m +327 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFImageRequestOperation.h +113 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFImageRequestOperation.m +321 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFJSONRequestOperation.h +71 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFJSONRequestOperation.m +150 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFNetworkActivityIndicatorManager.h +75 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFNetworkActivityIndicatorManager.m +157 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFNetworking.h +43 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFPropertyListRequestOperation.h +68 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFPropertyListRequestOperation.m +143 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFURLConnectionOperation.h +370 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFURLConnectionOperation.m +848 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFXMLRequestOperation.h +89 -0
- data/vendor/Pods/AFNetworking/AFNetworking/AFXMLRequestOperation.m +167 -0
- data/vendor/Pods/AFNetworking/AFNetworking/UIImageView+AFNetworking.h +78 -0
- data/vendor/Pods/AFNetworking/AFNetworking/UIImageView+AFNetworking.m +191 -0
- data/vendor/Pods/AFNetworking/LICENSE +19 -0
- data/vendor/Pods/AFNetworking/README.md +208 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFHTTPClient.h +641 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFHTTPRequestOperation.h +133 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFImageRequestOperation.h +113 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFJSONRequestOperation.h +71 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFNetworkActivityIndicatorManager.h +75 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFNetworking.h +43 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFPropertyListRequestOperation.h +68 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFURLConnectionOperation.h +370 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/AFXMLRequestOperation.h +89 -0
- data/vendor/Pods/BuildHeaders/AFNetworking/UIImageView+AFNetworking.h +78 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/ContextFilterLogFormatter.h +65 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDASLLogger.h +41 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDAbstractDatabaseLogger.h +102 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDFileLogger.h +334 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDLog.h +601 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/DDTTYLogger.h +167 -0
- data/vendor/Pods/BuildHeaders/CocoaLumberjack/DispatchQueueLogFormatter.h +116 -0
- data/vendor/Pods/BuildHeaders/NSData+MD5Digest/NSData+MD5Digest.h +18 -0
- data/vendor/Pods/BuildHeaders/Reachability/Reachability.h +109 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapCache.h +90 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapCollectionKey.h +20 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabase.h +547 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseConnection.h +447 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseConnectionState.h +29 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseDefaults.h +37 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtension.h +15 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtensionConnection.h +11 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtensionPrivate.h +440 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseExtensionTransaction.h +11 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredView.h +108 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredViewConnection.h +12 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredViewPrivate.h +19 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFilteredViewTransaction.h +39 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearch.h +89 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchConnection.h +32 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchPrivate.h +69 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchSnippetOptions.h +79 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseFullTextSearchTransaction.h +68 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseLogging.h +158 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseManager.h +17 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabasePrivate.h +424 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseQuery.h +42 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndex.h +100 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexConnection.h +33 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexPrivate.h +73 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexSetup.h +33 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseSecondaryIndexTransaction.h +58 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseStatement.h +13 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseString.h +121 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseTransaction.h +541 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseView.h +186 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewChange.h +272 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewChangePrivate.h +94 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewConnection.h +115 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewMappings.h +825 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewMappingsPrivate.h +72 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewOptions.h +56 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewPage.h +36 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewPageMetadata.h +27 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewPrivate.h +153 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewRangeOptions.h +330 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewRangeOptionsPrivate.h +17 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapDatabaseViewTransaction.h +447 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapMemoryTable.h +74 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapNull.h +17 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapSet.h +41 -0
- data/vendor/Pods/BuildHeaders/YapDatabase/YapTouch.h +15 -0
- data/vendor/Pods/CocoaLumberjack/LICENSE.txt +18 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDASLLogger.h +41 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDASLLogger.m +99 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDAbstractDatabaseLogger.h +102 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDAbstractDatabaseLogger.m +727 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDFileLogger.h +334 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDFileLogger.m +1353 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDLog.h +601 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDLog.m +1083 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDTTYLogger.h +167 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/DDTTYLogger.m +1479 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/ContextFilterLogFormatter.h +65 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/ContextFilterLogFormatter.m +191 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/DispatchQueueLogFormatter.h +116 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/DispatchQueueLogFormatter.m +251 -0
- data/vendor/Pods/CocoaLumberjack/Lumberjack/Extensions/README.txt +7 -0
- data/vendor/Pods/CocoaLumberjack/README.markdown +37 -0
- data/vendor/Pods/Headers/AFNetworking/AFHTTPClient.h +641 -0
- data/vendor/Pods/Headers/AFNetworking/AFHTTPRequestOperation.h +133 -0
- data/vendor/Pods/Headers/AFNetworking/AFImageRequestOperation.h +113 -0
- data/vendor/Pods/Headers/AFNetworking/AFJSONRequestOperation.h +71 -0
- data/vendor/Pods/Headers/AFNetworking/AFNetworkActivityIndicatorManager.h +75 -0
- data/vendor/Pods/Headers/AFNetworking/AFNetworking.h +43 -0
- data/vendor/Pods/Headers/AFNetworking/AFPropertyListRequestOperation.h +68 -0
- data/vendor/Pods/Headers/AFNetworking/AFURLConnectionOperation.h +370 -0
- data/vendor/Pods/Headers/AFNetworking/AFXMLRequestOperation.h +89 -0
- data/vendor/Pods/Headers/AFNetworking/UIImageView+AFNetworking.h +78 -0
- data/vendor/Pods/Headers/CocoaLumberjack/ContextFilterLogFormatter.h +65 -0
- data/vendor/Pods/Headers/CocoaLumberjack/DDASLLogger.h +41 -0
- data/vendor/Pods/Headers/CocoaLumberjack/DDAbstractDatabaseLogger.h +102 -0
- data/vendor/Pods/Headers/CocoaLumberjack/DDFileLogger.h +334 -0
- data/vendor/Pods/Headers/CocoaLumberjack/DDLog.h +601 -0
- data/vendor/Pods/Headers/CocoaLumberjack/DDTTYLogger.h +167 -0
- data/vendor/Pods/Headers/CocoaLumberjack/DispatchQueueLogFormatter.h +116 -0
- data/vendor/Pods/Headers/NSData+MD5Digest/NSData+MD5Digest.h +18 -0
- data/vendor/Pods/Headers/Reachability/Reachability.h +109 -0
- data/vendor/Pods/Headers/YapDatabase/YapCache.h +90 -0
- data/vendor/Pods/Headers/YapDatabase/YapCollectionKey.h +20 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabase.h +547 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseConnection.h +447 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseConnectionState.h +29 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseDefaults.h +37 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtension.h +15 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtensionConnection.h +11 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtensionPrivate.h +440 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseExtensionTransaction.h +11 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredView.h +108 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredViewConnection.h +12 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredViewPrivate.h +19 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFilteredViewTransaction.h +39 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearch.h +89 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchConnection.h +32 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchPrivate.h +69 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchSnippetOptions.h +79 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseFullTextSearchTransaction.h +68 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseLogging.h +158 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseManager.h +17 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabasePrivate.h +424 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseQuery.h +42 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndex.h +100 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexConnection.h +33 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexPrivate.h +73 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexSetup.h +33 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseSecondaryIndexTransaction.h +58 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseStatement.h +13 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseString.h +121 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseTransaction.h +541 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseView.h +186 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewChange.h +272 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewChangePrivate.h +94 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewConnection.h +115 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewMappings.h +825 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewMappingsPrivate.h +72 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewOptions.h +56 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewPage.h +36 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewPageMetadata.h +27 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewPrivate.h +153 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewRangeOptions.h +330 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewRangeOptionsPrivate.h +17 -0
- data/vendor/Pods/Headers/YapDatabase/YapDatabaseViewTransaction.h +447 -0
- data/vendor/Pods/Headers/YapDatabase/YapMemoryTable.h +74 -0
- data/vendor/Pods/Headers/YapDatabase/YapNull.h +17 -0
- data/vendor/Pods/Headers/YapDatabase/YapSet.h +41 -0
- data/vendor/Pods/Headers/YapDatabase/YapTouch.h +15 -0
- data/vendor/Pods/Headers/____Pods-AFNetworking-prefix.h +17 -0
- data/vendor/Pods/Headers/____Pods-CocoaLumberjack-prefix.h +5 -0
- data/vendor/Pods/Headers/____Pods-NSData+MD5Digest-prefix.h +5 -0
- data/vendor/Pods/Headers/____Pods-Reachability-prefix.h +5 -0
- data/vendor/Pods/Headers/____Pods-YapDatabase-prefix.h +5 -0
- data/vendor/Pods/Headers/____Pods-environment.h +38 -0
- data/vendor/Pods/Manifest.lock +24 -0
- data/vendor/Pods/NSData+MD5Digest/NSData+MD5Digest/NSData+MD5Digest.h +18 -0
- data/vendor/Pods/NSData+MD5Digest/NSData+MD5Digest/NSData+MD5Digest.m +39 -0
- data/vendor/Pods/NSData+MD5Digest/README.md +11 -0
- data/vendor/Pods/Pods-AFNetworking-Private.xcconfig +5 -0
- data/vendor/Pods/Pods-AFNetworking-dummy.m +5 -0
- data/vendor/Pods/Pods-AFNetworking-prefix.pch +17 -0
- data/vendor/Pods/Pods-AFNetworking.xcconfig +1 -0
- data/vendor/Pods/Pods-Acknowledgements.markdown +96 -0
- data/vendor/Pods/Pods-Acknowledgements.plist +142 -0
- data/vendor/Pods/Pods-CocoaLumberjack-Private.xcconfig +5 -0
- data/vendor/Pods/Pods-CocoaLumberjack-dummy.m +5 -0
- data/vendor/Pods/Pods-CocoaLumberjack-prefix.pch +5 -0
- data/vendor/Pods/Pods-CocoaLumberjack.xcconfig +0 -0
- data/vendor/Pods/Pods-NSData+MD5Digest-Private.xcconfig +5 -0
- data/vendor/Pods/Pods-NSData+MD5Digest-dummy.m +5 -0
- data/vendor/Pods/Pods-NSData+MD5Digest-prefix.pch +5 -0
- data/vendor/Pods/Pods-NSData+MD5Digest.xcconfig +0 -0
- data/vendor/Pods/Pods-Reachability-Private.xcconfig +5 -0
- data/vendor/Pods/Pods-Reachability-dummy.m +5 -0
- data/vendor/Pods/Pods-Reachability-prefix.pch +5 -0
- data/vendor/Pods/Pods-Reachability.xcconfig +1 -0
- data/vendor/Pods/Pods-YapDatabase-Private.xcconfig +5 -0
- data/vendor/Pods/Pods-YapDatabase-dummy.m +5 -0
- data/vendor/Pods/Pods-YapDatabase-prefix.pch +5 -0
- data/vendor/Pods/Pods-YapDatabase.xcconfig +1 -0
- data/vendor/Pods/Pods-dummy.m +5 -0
- data/vendor/Pods/Pods-environment.h +38 -0
- data/vendor/Pods/Pods-resources.sh +68 -0
- data/vendor/Pods/Pods.bridgesupport +5096 -0
- data/vendor/Pods/Pods.xcconfig +5 -0
- data/vendor/Pods/Pods.xcodeproj/project.pbxproj +5536 -0
- data/vendor/Pods/Reachability/LICENCE.txt +24 -0
- data/vendor/Pods/Reachability/README.md +65 -0
- data/vendor/Pods/Reachability/Reachability.h +109 -0
- data/vendor/Pods/Reachability/Reachability.m +527 -0
- data/vendor/Pods/YapDatabase/LICENSE.txt +18 -0
- data/vendor/Pods/YapDatabase/README.md +30 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/Internal/YapDatabaseFilteredViewPrivate.h +19 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredView.h +108 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredView.m +175 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewConnection.h +12 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewConnection.m +41 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewTransaction.h +39 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FilteredViews/YapDatabaseFilteredViewTransaction.m +966 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/Internal/YapDatabaseFullTextSearchPrivate.h +69 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearch.h +89 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearch.m +146 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchConnection.h +32 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchConnection.m +298 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchSnippetOptions.h +79 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchSnippetOptions.m +95 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchTransaction.h +68 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/FullTextSearch/YapDatabaseFullTextSearchTransaction.m +1352 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/Internal/YapDatabaseExtensionPrivate.h +440 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtension.h +15 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtension.m +58 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionConnection.h +11 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionConnection.m +46 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionTransaction.h +11 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Protocol/YapDatabaseExtensionTransaction.m +180 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/Internal/YapDatabaseSecondaryIndexPrivate.h +73 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndex.h +100 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndex.m +149 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexConnection.h +33 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexConnection.m +330 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexSetup.h +33 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexSetup.m +184 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexTransaction.h +58 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/SecondaryIndex/YapDatabaseSecondaryIndexTransaction.m +1166 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewChangePrivate.h +94 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewMappingsPrivate.h +72 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPage.h +36 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPage.mm +296 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPageMetadata.h +27 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPageMetadata.m +28 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewPrivate.h +153 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Internal/YapDatabaseViewRangeOptionsPrivate.h +17 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewChange.h +272 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewChange.m +2494 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewMappings.h +825 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewMappings.m +1567 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewRangeOptions.h +330 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/Utilities/YapDatabaseViewRangeOptions.m +141 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseView.h +186 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseView.m +191 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewConnection.h +115 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewConnection.m +897 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewOptions.h +56 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewOptions.m +27 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewTransaction.h +447 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Extensions/Views/YapDatabaseViewTransaction.m +4505 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapCache.h +90 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapCache.m +453 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseConnectionState.h +29 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseConnectionState.m +48 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseDefaults.h +37 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseDefaults.m +83 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseLogging.h +158 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseLogging.m +73 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseManager.h +17 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseManager.m +56 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabasePrivate.h +424 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseStatement.h +13 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseStatement.m +26 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapDatabaseString.h +121 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapMemoryTable.h +74 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapMemoryTable.m +603 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapNull.h +17 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapNull.m +31 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapTouch.h +15 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Internal/YapTouch.m +31 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapCollectionKey.h +20 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapCollectionKey.m +194 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapDatabaseQuery.h +42 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapDatabaseQuery.m +96 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapSet.h +41 -0
- data/vendor/Pods/YapDatabase/YapDatabase/Utilities/YapSet.m +82 -0
- data/vendor/Pods/YapDatabase/YapDatabase/YapDatabase.h +547 -0
- data/vendor/Pods/YapDatabase/YapDatabase/YapDatabase.m +2022 -0
- data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseConnection.h +447 -0
- data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseConnection.m +3874 -0
- data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseTransaction.h +541 -0
- data/vendor/Pods/YapDatabase/YapDatabase/YapDatabaseTransaction.m +5282 -0
- data/vendor/YapDatabaseRubyMotion/YapDatabaseRubyMotion.bridgesupport +16 -0
- data/vendor/YapDatabaseRubyMotion/YapDatabaseRubyMotion.h +13 -0
- data/vendor/YapDatabaseRubyMotion/YapDatabaseRubyMotion.m +20 -0
- data/yapper.gemspec +24 -0
- 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
|