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,2022 @@
|
|
|
1
|
+
#import "YapDatabase.h"
|
|
2
|
+
#import "YapDatabasePrivate.h"
|
|
3
|
+
#import "YapDatabaseExtensionPrivate.h"
|
|
4
|
+
#import "YapCollectionKey.h"
|
|
5
|
+
#import "YapDatabaseManager.h"
|
|
6
|
+
#import "YapDatabaseConnectionState.h"
|
|
7
|
+
#import "YapDatabaseLogging.h"
|
|
8
|
+
|
|
9
|
+
#import "sqlite3.h"
|
|
10
|
+
|
|
11
|
+
#import <libkern/OSAtomic.h>
|
|
12
|
+
|
|
13
|
+
#if ! __has_feature(objc_arc)
|
|
14
|
+
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Define log level for this file: OFF, ERROR, WARN, INFO, VERBOSE
|
|
19
|
+
* See YapDatabaseLogging.h for more information.
|
|
20
|
+
**/
|
|
21
|
+
#if DEBUG
|
|
22
|
+
static const int ydbLogLevel = YDB_LOG_LEVEL_INFO;
|
|
23
|
+
#else
|
|
24
|
+
static const int ydbLogLevel = YDB_LOG_LEVEL_WARN;
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
NSString *const YapDatabaseModifiedNotification = @"YapDatabaseModifiedNotification";
|
|
28
|
+
|
|
29
|
+
NSString *const YapDatabaseSnapshotKey = @"snapshot";
|
|
30
|
+
NSString *const YapDatabaseConnectionKey = @"connection";
|
|
31
|
+
NSString *const YapDatabaseExtensionsKey = @"extensions";
|
|
32
|
+
NSString *const YapDatabaseCustomKey = @"custom";
|
|
33
|
+
|
|
34
|
+
NSString *const YapDatabaseObjectChangesKey = @"objectChanges";
|
|
35
|
+
NSString *const YapDatabaseMetadataChangesKey = @"metadataChanges";
|
|
36
|
+
NSString *const YapDatabaseRemovedKeysKey = @"removedKeys";
|
|
37
|
+
NSString *const YapDatabaseRemovedCollectionsKey = @"removedCollections";
|
|
38
|
+
NSString *const YapDatabaseAllKeysRemovedKey = @"allKeysRemoved";
|
|
39
|
+
|
|
40
|
+
NSString *const YapDatabaseRegisteredExtensionsKey = @"registeredExtensions";
|
|
41
|
+
NSString *const YapDatabaseRegisteredTablesKey = @"registeredTables";
|
|
42
|
+
NSString *const YapDatabaseExtensionsOrderKey = @"extensionsOrder";
|
|
43
|
+
NSString *const YapDatabaseNotificationKey = @"notification";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The database version is stored (via pragma user_version) to sqlite.
|
|
47
|
+
* It is used to represent the version of the userlying architecture of YapDatabase.
|
|
48
|
+
* In the event of future changes to the sqlite underpinnings of YapDatabase,
|
|
49
|
+
* the version can be consulted to allow for proper on-the-fly upgrades.
|
|
50
|
+
* For more information, see the upgradeTable method.
|
|
51
|
+
**/
|
|
52
|
+
#define YAP_DATABASE_CURRENT_VERION 3
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Default values
|
|
56
|
+
**/
|
|
57
|
+
#define DEFAULT_MAX_CONNECTION_POOL_COUNT 5 // connections
|
|
58
|
+
#define DEFAULT_CONNECTION_POOL_LIFETIME 90.0 // seconds
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@implementation YapDatabase
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The default serializer & deserializer use NSCoding (NSKeyedArchiver & NSKeyedUnarchiver).
|
|
65
|
+
* Thus the objects need only support the NSCoding protocol.
|
|
66
|
+
**/
|
|
67
|
+
+ (YapDatabaseSerializer)defaultSerializer
|
|
68
|
+
{
|
|
69
|
+
return ^ NSData* (NSString *collection, NSString *key, id object){
|
|
70
|
+
return [NSKeyedArchiver archivedDataWithRootObject:object];
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The default serializer & deserializer use NSCoding (NSKeyedArchiver & NSKeyedUnarchiver).
|
|
76
|
+
* Thus the objects need only support the NSCoding protocol.
|
|
77
|
+
**/
|
|
78
|
+
+ (YapDatabaseDeserializer)defaultDeserializer
|
|
79
|
+
{
|
|
80
|
+
return ^ id (NSString *collection, NSString *key, NSData *data){
|
|
81
|
+
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Property lists ONLY support the following: NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber.
|
|
87
|
+
* Property lists are highly optimized and are used extensively by Apple.
|
|
88
|
+
*
|
|
89
|
+
* Property lists make a good fit when your existing code already uses them,
|
|
90
|
+
* such as replacing NSUserDefaults with a database.
|
|
91
|
+
**/
|
|
92
|
+
+ (YapDatabaseSerializer)propertyListSerializer
|
|
93
|
+
{
|
|
94
|
+
return ^ NSData* (NSString *collection, NSString *key, id object){
|
|
95
|
+
return [NSPropertyListSerialization dataWithPropertyList:object
|
|
96
|
+
format:NSPropertyListBinaryFormat_v1_0
|
|
97
|
+
options:NSPropertyListImmutable
|
|
98
|
+
error:NULL];
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Property lists ONLY support the following: NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber.
|
|
104
|
+
* Property lists are highly optimized and are used extensively by Apple.
|
|
105
|
+
*
|
|
106
|
+
* Property lists make a good fit when your existing code already uses them,
|
|
107
|
+
* such as replacing NSUserDefaults with a database.
|
|
108
|
+
**/
|
|
109
|
+
+ (YapDatabaseDeserializer)propertyListDeserializer
|
|
110
|
+
{
|
|
111
|
+
return ^ id (NSString *collection, NSString *key, NSData *data){
|
|
112
|
+
return [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A FASTER serializer than the default, if serializing ONLY a NSDate object.
|
|
118
|
+
* You may want to use timestampSerializer & timestampDeserializer if your metadata is simply an NSDate.
|
|
119
|
+
**/
|
|
120
|
+
+ (YapDatabaseSerializer)timestampSerializer
|
|
121
|
+
{
|
|
122
|
+
return ^ NSData* (NSString *collection, NSString *key, id object) {
|
|
123
|
+
|
|
124
|
+
if ([object isKindOfClass:[NSDate class]])
|
|
125
|
+
{
|
|
126
|
+
NSTimeInterval timestamp = [(NSDate *)object timeIntervalSinceReferenceDate];
|
|
127
|
+
|
|
128
|
+
return [[NSData alloc] initWithBytes:(void *)×tamp length:sizeof(NSTimeInterval)];
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
{
|
|
132
|
+
return [NSKeyedArchiver archivedDataWithRootObject:object];
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A FASTER deserializer than the default, if deserializing data from timestampSerializer.
|
|
139
|
+
* You may want to use timestampSerializer & timestampDeserializer if your metadata is simply an NSDate.
|
|
140
|
+
**/
|
|
141
|
+
+ (YapDatabaseDeserializer)timestampDeserializer
|
|
142
|
+
{
|
|
143
|
+
return ^ id (NSString *collection, NSString *key, NSData *data) {
|
|
144
|
+
|
|
145
|
+
if ([data length] == sizeof(NSTimeInterval))
|
|
146
|
+
{
|
|
147
|
+
NSTimeInterval timestamp;
|
|
148
|
+
memcpy((void *)×tamp, [data bytes], sizeof(NSTimeInterval));
|
|
149
|
+
|
|
150
|
+
return [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:timestamp];
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
{
|
|
154
|
+
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
160
|
+
#pragma mark Properties
|
|
161
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
162
|
+
|
|
163
|
+
@synthesize databasePath;
|
|
164
|
+
|
|
165
|
+
@synthesize objectSerializer = objectSerializer;
|
|
166
|
+
@synthesize objectDeserializer = objectDeserializer;
|
|
167
|
+
@synthesize metadataSerializer = metadataSerializer;
|
|
168
|
+
@synthesize metadataDeserializer = metadataDeserializer;
|
|
169
|
+
@synthesize objectSanitizer = objectSanitizer;
|
|
170
|
+
@synthesize metadataSanitizer = metadataSanitizer;
|
|
171
|
+
|
|
172
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
173
|
+
#pragma mark Init
|
|
174
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
175
|
+
|
|
176
|
+
- (id)initWithPath:(NSString *)inPath
|
|
177
|
+
{
|
|
178
|
+
return [self initWithPath:inPath
|
|
179
|
+
objectSerializer:NULL
|
|
180
|
+
objectDeserializer:NULL
|
|
181
|
+
metadataSerializer:NULL
|
|
182
|
+
metadataDeserializer:NULL
|
|
183
|
+
objectSanitizer:NULL
|
|
184
|
+
metadataSanitizer:NULL];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
- (id)initWithPath:(NSString *)inPath
|
|
188
|
+
serializer:(YapDatabaseSerializer)inSerializer
|
|
189
|
+
deserializer:(YapDatabaseDeserializer)inDeserializer
|
|
190
|
+
{
|
|
191
|
+
return [self initWithPath:inPath
|
|
192
|
+
objectSerializer:inSerializer
|
|
193
|
+
objectDeserializer:inDeserializer
|
|
194
|
+
metadataSerializer:inSerializer
|
|
195
|
+
metadataDeserializer:inDeserializer
|
|
196
|
+
objectSanitizer:NULL
|
|
197
|
+
metadataSanitizer:NULL];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- (id)initWithPath:(NSString *)inPath
|
|
201
|
+
serializer:(YapDatabaseSerializer)inSerializer
|
|
202
|
+
deserializer:(YapDatabaseDeserializer)inDeserializer
|
|
203
|
+
sanitizer:(YapDatabaseSanitizer)inSanitizer
|
|
204
|
+
{
|
|
205
|
+
return [self initWithPath:inPath
|
|
206
|
+
objectSerializer:inSerializer
|
|
207
|
+
objectDeserializer:inDeserializer
|
|
208
|
+
metadataSerializer:inSerializer
|
|
209
|
+
metadataDeserializer:inDeserializer
|
|
210
|
+
objectSanitizer:inSanitizer
|
|
211
|
+
metadataSanitizer:inSanitizer];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
- (id)initWithPath:(NSString *)inPath objectSerializer:(YapDatabaseSerializer)inObjectSerializer
|
|
215
|
+
objectDeserializer:(YapDatabaseDeserializer)inObjectDeserializer
|
|
216
|
+
metadataSerializer:(YapDatabaseSerializer)inMetadataSerializer
|
|
217
|
+
metadataDeserializer:(YapDatabaseDeserializer)inMetadataDeserializer
|
|
218
|
+
{
|
|
219
|
+
return [self initWithPath:inPath
|
|
220
|
+
objectSerializer:inObjectSerializer
|
|
221
|
+
objectDeserializer:inObjectDeserializer
|
|
222
|
+
metadataSerializer:inMetadataSerializer
|
|
223
|
+
metadataDeserializer:inMetadataDeserializer
|
|
224
|
+
objectSanitizer:NULL
|
|
225
|
+
metadataSanitizer:NULL];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
- (id)initWithPath:(NSString *)inPath objectSerializer:(YapDatabaseSerializer)inObjectSerializer
|
|
229
|
+
objectDeserializer:(YapDatabaseDeserializer)inObjectDeserializer
|
|
230
|
+
metadataSerializer:(YapDatabaseSerializer)inMetadataSerializer
|
|
231
|
+
metadataDeserializer:(YapDatabaseDeserializer)inMetadataDeserializer
|
|
232
|
+
objectSanitizer:(YapDatabaseSanitizer)inObjectSanitizer
|
|
233
|
+
metadataSanitizer:(YapDatabaseSanitizer)inMetadataSanitizer;
|
|
234
|
+
{
|
|
235
|
+
// First, standardize path.
|
|
236
|
+
// This allows clients to be lazy when passing paths.
|
|
237
|
+
NSString *path = [inPath stringByStandardizingPath];
|
|
238
|
+
|
|
239
|
+
// Ensure there is only a single database instance per file.
|
|
240
|
+
// However, clients may create as many connections as desired.
|
|
241
|
+
if (![YapDatabaseManager registerDatabaseForPath:path])
|
|
242
|
+
{
|
|
243
|
+
YDBLogError(@"Only a single database instance is allowed per file. "
|
|
244
|
+
@"For concurrency you create multiple connections from a single database instance.");
|
|
245
|
+
return nil;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if ((self = [super init]))
|
|
249
|
+
{
|
|
250
|
+
databasePath = path;
|
|
251
|
+
|
|
252
|
+
BOOL(^openConfigCreate)(void) = ^BOOL (void) { @autoreleasepool {
|
|
253
|
+
|
|
254
|
+
BOOL result = YES;
|
|
255
|
+
|
|
256
|
+
if (result) result = [self openDatabase];
|
|
257
|
+
if (result) result = [self configureDatabase];
|
|
258
|
+
if (result) result = [self createTables];
|
|
259
|
+
|
|
260
|
+
if (!result && db)
|
|
261
|
+
{
|
|
262
|
+
sqlite3_close(db);
|
|
263
|
+
db = NULL;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return result;
|
|
267
|
+
}};
|
|
268
|
+
|
|
269
|
+
BOOL result = openConfigCreate();
|
|
270
|
+
if (!result)
|
|
271
|
+
{
|
|
272
|
+
// There are a few reasons why the database might not open.
|
|
273
|
+
// One possibility is if the database file gets corrupt.
|
|
274
|
+
// In the event of a problem, we simply delete the database file.
|
|
275
|
+
// This isn't a big deal since we can just redownload the data.
|
|
276
|
+
|
|
277
|
+
// Delete the (possibly corrupt) database file.
|
|
278
|
+
[[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
|
|
279
|
+
|
|
280
|
+
// Then try opening a database again.
|
|
281
|
+
|
|
282
|
+
result = openConfigCreate();
|
|
283
|
+
|
|
284
|
+
if (result) {
|
|
285
|
+
YDBLogInfo(@"Database corruption resolved (name=%@)", [path lastPathComponent]);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
YDBLogError(@"Database corruption unresolved (name=%@)", [path lastPathComponent]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (!result)
|
|
292
|
+
{
|
|
293
|
+
return nil;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
internalQueue = dispatch_queue_create("YapDatabase-Internal", NULL);
|
|
297
|
+
checkpointQueue = dispatch_queue_create("YapDatabase-Checkpoint", NULL);
|
|
298
|
+
snapshotQueue = dispatch_queue_create("YapDatabase-Snapshot", NULL);
|
|
299
|
+
writeQueue = dispatch_queue_create("YapDatabase-Write", NULL);
|
|
300
|
+
|
|
301
|
+
changesets = [[NSMutableArray alloc] init];
|
|
302
|
+
connectionStates = [[NSMutableArray alloc] init];
|
|
303
|
+
|
|
304
|
+
defaults = [[YapDatabaseDefaults alloc] init];
|
|
305
|
+
|
|
306
|
+
registeredExtensions = [[NSDictionary alloc] init];
|
|
307
|
+
registeredTables = [[NSDictionary alloc] init];
|
|
308
|
+
|
|
309
|
+
extensionDependencies = [[NSDictionary alloc] init];
|
|
310
|
+
extensionsOrder = [[NSArray alloc] init];
|
|
311
|
+
|
|
312
|
+
maxConnectionPoolCount = DEFAULT_MAX_CONNECTION_POOL_COUNT;
|
|
313
|
+
connectionPoolLifetime = DEFAULT_CONNECTION_POOL_LIFETIME;
|
|
314
|
+
|
|
315
|
+
YapDatabaseSerializer defaultSerializer = nil;
|
|
316
|
+
YapDatabaseDeserializer defaultDeserializer = nil;
|
|
317
|
+
|
|
318
|
+
if (!inObjectSerializer || !inMetadataSerializer)
|
|
319
|
+
defaultSerializer = [[self class] defaultSerializer];
|
|
320
|
+
|
|
321
|
+
if (!inObjectDeserializer || !inMetadataDeserializer)
|
|
322
|
+
defaultDeserializer = [[self class] defaultDeserializer];
|
|
323
|
+
|
|
324
|
+
objectSerializer = inObjectSerializer ? inObjectSerializer : defaultSerializer;
|
|
325
|
+
objectDeserializer = inObjectDeserializer ? inObjectDeserializer : defaultDeserializer;
|
|
326
|
+
|
|
327
|
+
metadataSerializer = inMetadataSerializer ? inMetadataSerializer : defaultSerializer;
|
|
328
|
+
metadataDeserializer = inMetadataDeserializer ? inMetadataDeserializer : defaultDeserializer;
|
|
329
|
+
|
|
330
|
+
objectSanitizer = inObjectSanitizer;
|
|
331
|
+
metadataSanitizer = inMetadataSanitizer;
|
|
332
|
+
|
|
333
|
+
// Mark the queues so we can identify them.
|
|
334
|
+
// There are several methods whose use is restricted to within a certain queue.
|
|
335
|
+
|
|
336
|
+
IsOnSnapshotQueueKey = &IsOnSnapshotQueueKey;
|
|
337
|
+
dispatch_queue_set_specific(snapshotQueue, IsOnSnapshotQueueKey, IsOnSnapshotQueueKey, NULL);
|
|
338
|
+
|
|
339
|
+
IsOnWriteQueueKey = &IsOnWriteQueueKey;
|
|
340
|
+
dispatch_queue_set_specific(writeQueue, IsOnWriteQueueKey, IsOnWriteQueueKey, NULL);
|
|
341
|
+
|
|
342
|
+
// Complete database setup in the background
|
|
343
|
+
dispatch_async(snapshotQueue, ^{ @autoreleasepool {
|
|
344
|
+
|
|
345
|
+
[self upgradeTable];
|
|
346
|
+
[self prepare];
|
|
347
|
+
}});
|
|
348
|
+
}
|
|
349
|
+
return self;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
- (void)dealloc
|
|
353
|
+
{
|
|
354
|
+
YDBLogVerbose(@"Dealloc <%@ %p: databaseName=%@>", [self class], self, [databasePath lastPathComponent]);
|
|
355
|
+
|
|
356
|
+
while ([connectionPoolValues count] > 0)
|
|
357
|
+
{
|
|
358
|
+
sqlite3 *aDb = (sqlite3 *)[[connectionPoolValues objectAtIndex:0] pointerValue];
|
|
359
|
+
|
|
360
|
+
int status = sqlite3_close(aDb);
|
|
361
|
+
if (status != SQLITE_OK)
|
|
362
|
+
{
|
|
363
|
+
YDBLogError(@"Error in sqlite_close: %d %s", status, sqlite3_errmsg(aDb));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
[connectionPoolValues removeObjectAtIndex:0];
|
|
367
|
+
[connectionPoolDates removeObjectAtIndex:0];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (connectionPoolTimer)
|
|
371
|
+
dispatch_source_cancel(connectionPoolTimer);
|
|
372
|
+
|
|
373
|
+
if (db) {
|
|
374
|
+
sqlite3_close(db);
|
|
375
|
+
db = NULL;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
[YapDatabaseManager deregisterDatabaseForPath:databasePath];
|
|
379
|
+
|
|
380
|
+
#if !OS_OBJECT_USE_OBJC
|
|
381
|
+
if (internalQueue)
|
|
382
|
+
dispatch_release(internalQueue);
|
|
383
|
+
if (snapshotQueue)
|
|
384
|
+
dispatch_release(snapshotQueue);
|
|
385
|
+
if (writeQueue)
|
|
386
|
+
dispatch_release(writeQueue);
|
|
387
|
+
if (checkpointQueue)
|
|
388
|
+
dispatch_release(checkpointQueue);
|
|
389
|
+
#endif
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
393
|
+
#pragma mark Setup
|
|
394
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Attempts to open (or create & open) the database connection.
|
|
398
|
+
**/
|
|
399
|
+
- (BOOL)openDatabase
|
|
400
|
+
{
|
|
401
|
+
// Open the database connection.
|
|
402
|
+
//
|
|
403
|
+
// We use SQLITE_OPEN_NOMUTEX to use the multi-thread threading mode,
|
|
404
|
+
// as we will be serializing access to the connection externally.
|
|
405
|
+
|
|
406
|
+
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
|
407
|
+
|
|
408
|
+
int status = sqlite3_open_v2([databasePath UTF8String], &db, flags, NULL);
|
|
409
|
+
if (status != SQLITE_OK)
|
|
410
|
+
{
|
|
411
|
+
// There are a few reasons why the database might not open.
|
|
412
|
+
// One possibility is if the database file gets corrupt.
|
|
413
|
+
// In the event of a problem, we simply delete the database file.
|
|
414
|
+
// This isn't a big deal since we can just redownload the data.
|
|
415
|
+
|
|
416
|
+
// Sometimes the open function returns a db to allow us to query it for the error message
|
|
417
|
+
if (db) {
|
|
418
|
+
YDBLogWarn(@"Error opening database: %d %s", status, sqlite3_errmsg(db));
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
YDBLogError(@"Error opening database: %d", status);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return NO;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return YES;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Configures the database connection.
|
|
432
|
+
* This mainly means enabling WAL mode, and configuring the auto-checkpoint.
|
|
433
|
+
**/
|
|
434
|
+
- (BOOL)configureDatabase
|
|
435
|
+
{
|
|
436
|
+
int status;
|
|
437
|
+
|
|
438
|
+
status = sqlite3_exec(db, "PRAGMA legacy_file_format = 0;", NULL, NULL, NULL);
|
|
439
|
+
if (status != SQLITE_OK)
|
|
440
|
+
{
|
|
441
|
+
YDBLogError(@"Error setting legacy_file_format: %d %s", status, sqlite3_errmsg(db));
|
|
442
|
+
return NO;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
status = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
446
|
+
if (status != SQLITE_OK)
|
|
447
|
+
{
|
|
448
|
+
YDBLogError(@"Error setting journal_mode: %d %s", status, sqlite3_errmsg(db));
|
|
449
|
+
return NO;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Disable autocheckpointing.
|
|
453
|
+
//
|
|
454
|
+
// YapDatabase has its own optimized checkpointing algorithm built-in.
|
|
455
|
+
// It knows the state of every active connection for the database,
|
|
456
|
+
// so it can invoke the checkpoint methods at the precise time in which a checkpoint can be most effective.
|
|
457
|
+
|
|
458
|
+
sqlite3_wal_autocheckpoint(db, 0);
|
|
459
|
+
|
|
460
|
+
return YES;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Creates the database tables we need:
|
|
465
|
+
*
|
|
466
|
+
* - yap2 : stores snapshot and metadata for extensions
|
|
467
|
+
* - database2 : stores collection/key/value/metadata rows
|
|
468
|
+
**/
|
|
469
|
+
- (BOOL)createTables
|
|
470
|
+
{
|
|
471
|
+
int status;
|
|
472
|
+
|
|
473
|
+
char *createYapTableStatement =
|
|
474
|
+
"CREATE TABLE IF NOT EXISTS \"yap2\""
|
|
475
|
+
" (\"extension\" CHAR NOT NULL, "
|
|
476
|
+
" \"key\" CHAR NOT NULL, "
|
|
477
|
+
" \"data\" BLOB, "
|
|
478
|
+
" PRIMARY KEY (\"extension\", \"key\")"
|
|
479
|
+
" );";
|
|
480
|
+
|
|
481
|
+
status = sqlite3_exec(db, createYapTableStatement, NULL, NULL, NULL);
|
|
482
|
+
if (status != SQLITE_OK)
|
|
483
|
+
{
|
|
484
|
+
YDBLogError(@"Failed creating 'yap2' table: %d %s", status, sqlite3_errmsg(db));
|
|
485
|
+
return NO;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
char *createDatabaseTableStatement =
|
|
489
|
+
"CREATE TABLE IF NOT EXISTS \"database2\""
|
|
490
|
+
" (\"rowid\" INTEGER PRIMARY KEY,"
|
|
491
|
+
" \"collection\" CHAR NOT NULL,"
|
|
492
|
+
" \"key\" CHAR NOT NULL,"
|
|
493
|
+
" \"data\" BLOB,"
|
|
494
|
+
" \"metadata\" BLOB"
|
|
495
|
+
" );";
|
|
496
|
+
|
|
497
|
+
status = sqlite3_exec(db, createDatabaseTableStatement, NULL, NULL, NULL);
|
|
498
|
+
if (status != SQLITE_OK)
|
|
499
|
+
{
|
|
500
|
+
YDBLogError(@"Failed creating 'database2' table: %d %s", status, sqlite3_errmsg(db));
|
|
501
|
+
return NO;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
char *createIndexStatement =
|
|
505
|
+
"CREATE UNIQUE INDEX IF NOT EXISTS \"true_primary_key\" ON \"database2\" ( \"collection\", \"key\" );";
|
|
506
|
+
|
|
507
|
+
status = sqlite3_exec(db, createIndexStatement, NULL, NULL, NULL);
|
|
508
|
+
if (status != SQLITE_OK)
|
|
509
|
+
{
|
|
510
|
+
YDBLogError(@"Failed creating index on 'database' table: %d %s", status, sqlite3_errmsg(db));
|
|
511
|
+
return NO;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return YES;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
518
|
+
#pragma mark Utilities
|
|
519
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Returns whether or not the given table exists.
|
|
523
|
+
**/
|
|
524
|
+
- (BOOL)tableExists:(NSString *)tableName using:(sqlite3 *)aDb
|
|
525
|
+
{
|
|
526
|
+
if (tableName == nil) return NO;
|
|
527
|
+
|
|
528
|
+
sqlite3_stmt *statement;
|
|
529
|
+
char *stmt = "SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = ?";
|
|
530
|
+
|
|
531
|
+
int status = sqlite3_prepare_v2(aDb, stmt, (int)strlen(stmt)+1, &statement, NULL);
|
|
532
|
+
if (status != SQLITE_OK)
|
|
533
|
+
{
|
|
534
|
+
YDBLogError(@"%@: Error creating statement! %d %s", THIS_METHOD, status, sqlite3_errmsg(aDb));
|
|
535
|
+
return NO;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
BOOL result = NO;
|
|
539
|
+
|
|
540
|
+
sqlite3_bind_text(statement, 1, [tableName UTF8String], -1, SQLITE_TRANSIENT);
|
|
541
|
+
|
|
542
|
+
status = sqlite3_step(statement);
|
|
543
|
+
if (status == SQLITE_ROW)
|
|
544
|
+
{
|
|
545
|
+
int count = sqlite3_column_int(statement, 1);
|
|
546
|
+
|
|
547
|
+
result = (count > 0);
|
|
548
|
+
}
|
|
549
|
+
else if (status == SQLITE_ERROR)
|
|
550
|
+
{
|
|
551
|
+
YDBLogError(@"%@: Error executing statement! %d %s", THIS_METHOD, status, sqlite3_errmsg(aDb));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
sqlite3_finalize(statement);
|
|
555
|
+
statement = NULL;
|
|
556
|
+
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Extracts and returns column names of our database.
|
|
562
|
+
**/
|
|
563
|
+
- (NSArray *)columnNamesForTable:(NSString *)tableName using:(sqlite3 *)aDb
|
|
564
|
+
{
|
|
565
|
+
if (tableName == nil) return nil;
|
|
566
|
+
|
|
567
|
+
sqlite3_stmt *statement;
|
|
568
|
+
char *stmt = "PRAGMA table_info(?);";
|
|
569
|
+
|
|
570
|
+
int status = sqlite3_prepare_v2(aDb, stmt, (int)strlen(stmt)+1, &statement, NULL);
|
|
571
|
+
if (status != SQLITE_OK)
|
|
572
|
+
{
|
|
573
|
+
YDBLogError(@"%@: Error creating statement! %d %s", THIS_METHOD, status, sqlite3_errmsg(aDb));
|
|
574
|
+
return nil;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
NSMutableArray *tableColumnNames = [NSMutableArray array];
|
|
578
|
+
|
|
579
|
+
sqlite3_bind_text(statement, 1, [tableName UTF8String], -1, SQLITE_TRANSIENT);
|
|
580
|
+
|
|
581
|
+
while ((status = sqlite3_step(statement)) == SQLITE_ROW)
|
|
582
|
+
{
|
|
583
|
+
const unsigned char *text = sqlite3_column_text(statement, 1);
|
|
584
|
+
int textSize = sqlite3_column_bytes(statement, 1);
|
|
585
|
+
|
|
586
|
+
NSString *columnName = [[NSString alloc] initWithBytes:text length:textSize encoding:NSUTF8StringEncoding];
|
|
587
|
+
if (columnName)
|
|
588
|
+
{
|
|
589
|
+
[tableColumnNames addObject:columnName];
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (status != SQLITE_DONE)
|
|
594
|
+
{
|
|
595
|
+
YDBLogError(@"%@: Error executing statement! %d %s", THIS_METHOD, status, sqlite3_errmsg(aDb));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
sqlite3_finalize(statement);
|
|
599
|
+
statement = NULL;
|
|
600
|
+
|
|
601
|
+
return tableColumnNames;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
605
|
+
#pragma mark Upgrade
|
|
606
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Gets the version of the table.
|
|
610
|
+
* This is used to perform the various upgrade paths.
|
|
611
|
+
**/
|
|
612
|
+
- (BOOL)get_user_version:(int *)user_version_ptr
|
|
613
|
+
{
|
|
614
|
+
sqlite3_stmt *pragmaStatement;
|
|
615
|
+
int status;
|
|
616
|
+
int user_version;
|
|
617
|
+
|
|
618
|
+
char *stmt = "PRAGMA user_version;";
|
|
619
|
+
|
|
620
|
+
status = sqlite3_prepare_v2(db, stmt, (int)strlen(stmt)+1, &pragmaStatement, NULL);
|
|
621
|
+
if (status != SQLITE_OK)
|
|
622
|
+
{
|
|
623
|
+
YDBLogError(@"Error creating pragma user_version statement! %d %s", status, sqlite3_errmsg(db));
|
|
624
|
+
return NO;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
status = sqlite3_step(pragmaStatement);
|
|
628
|
+
if (status == SQLITE_ROW)
|
|
629
|
+
{
|
|
630
|
+
user_version = sqlite3_column_int(pragmaStatement, 0);
|
|
631
|
+
}
|
|
632
|
+
else
|
|
633
|
+
{
|
|
634
|
+
YDBLogError(@"Error fetching user_version: %d %s", status, sqlite3_errmsg(db));
|
|
635
|
+
return NO;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
sqlite3_finalize(pragmaStatement);
|
|
639
|
+
pragmaStatement = NULL;
|
|
640
|
+
|
|
641
|
+
// If user_version is zero, then this is a new database
|
|
642
|
+
|
|
643
|
+
if (user_version == 0)
|
|
644
|
+
{
|
|
645
|
+
user_version = YAP_DATABASE_CURRENT_VERION;
|
|
646
|
+
[self set_user_version:user_version];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (user_version_ptr)
|
|
650
|
+
*user_version_ptr = user_version;
|
|
651
|
+
return YES;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Sets the version of the table.
|
|
656
|
+
* The version is used to check and perform upgrade logic if needed.
|
|
657
|
+
**/
|
|
658
|
+
- (BOOL)set_user_version:(int)user_version
|
|
659
|
+
{
|
|
660
|
+
NSString *query = [NSString stringWithFormat:@"PRAGMA user_version = %d;", user_version];
|
|
661
|
+
|
|
662
|
+
int status = sqlite3_exec(db, [query UTF8String], NULL, NULL, NULL);
|
|
663
|
+
if (status != SQLITE_OK)
|
|
664
|
+
{
|
|
665
|
+
YDBLogError(@"Error setting user_version: %d %s", status, sqlite3_errmsg(db));
|
|
666
|
+
return NO;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return YES;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
- (BOOL)upgradeTable_1_2
|
|
673
|
+
{
|
|
674
|
+
// In version 1, we used a table named "yap" which had {key, data}.
|
|
675
|
+
// In version 2, we use a table named "yap2" which has {extension, key, data}
|
|
676
|
+
|
|
677
|
+
int status = sqlite3_exec(db, "DROP TABLE IF EXISTS \"yap\"", NULL, NULL, NULL);
|
|
678
|
+
if (status != SQLITE_OK)
|
|
679
|
+
{
|
|
680
|
+
YDBLogError(@"Failed dropping 'yap' table: %d %s", status, sqlite3_errmsg(db));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return YES;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* In version 3 (more commonly known as version 2.1),
|
|
688
|
+
* we altered the tables to use INTEGER PRIMARY KEY's so we could pass rowid's to extensions.
|
|
689
|
+
*
|
|
690
|
+
* This method migrates 'database' to 'database2'.
|
|
691
|
+
**/
|
|
692
|
+
- (BOOL)upgradeTable_2_3
|
|
693
|
+
{
|
|
694
|
+
int status;
|
|
695
|
+
|
|
696
|
+
char *stmt = "INSERT INTO \"database2\" (\"collection\", \"key\", \"data\", \"metadata\")"
|
|
697
|
+
" SELECT \"collection\", \"key\", \"data\", \"metadata\" FROM \"database\";";
|
|
698
|
+
|
|
699
|
+
status = sqlite3_exec(db, stmt, NULL, NULL, NULL);
|
|
700
|
+
if (status != SQLITE_OK)
|
|
701
|
+
{
|
|
702
|
+
YDBLogError(@"Error migrating 'database' to 'database2': %d %s", status, sqlite3_errmsg(db));
|
|
703
|
+
return NO;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
status = sqlite3_exec(db, "DROP TABLE IF EXISTS \"database\"", NULL, NULL, NULL);
|
|
707
|
+
if (status != SQLITE_OK)
|
|
708
|
+
{
|
|
709
|
+
YDBLogError(@"Failed dropping 'database' table: %d %s", status, sqlite3_errmsg(db));
|
|
710
|
+
return NO;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return YES;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Performs upgrade checks, and implements the upgrade "plumbing" by invoking the appropriate upgrade methods.
|
|
718
|
+
*
|
|
719
|
+
* To add custom upgrade logic, implement a method named "upgradeTable_X_Y",
|
|
720
|
+
* where X is the previous version, and Y is the new version.
|
|
721
|
+
* For example:
|
|
722
|
+
*
|
|
723
|
+
* - (BOOL)upgradeTable_1_2 {
|
|
724
|
+
* // Upgrades from version 1 to version 2 of YapDatabase.
|
|
725
|
+
* // Return YES if successful.
|
|
726
|
+
* }
|
|
727
|
+
*
|
|
728
|
+
* IMPORTANT:
|
|
729
|
+
* This is for upgrades of the database schema, and low-level operations of YapDatabase.
|
|
730
|
+
* This is NOT for upgrading data within the database (i.e. objects, metadata, or keys).
|
|
731
|
+
* Such data upgrades should be performed client side.
|
|
732
|
+
*
|
|
733
|
+
* This method is run asynchronously on the queue.
|
|
734
|
+
**/
|
|
735
|
+
- (void)upgradeTable
|
|
736
|
+
{
|
|
737
|
+
int user_version = 0;
|
|
738
|
+
if (![self get_user_version:&user_version]) return;
|
|
739
|
+
|
|
740
|
+
while (user_version < YAP_DATABASE_CURRENT_VERION)
|
|
741
|
+
{
|
|
742
|
+
// Invoke method upgradeTable_X_Y
|
|
743
|
+
// where X == current_version, and Y == current_version+1.
|
|
744
|
+
//
|
|
745
|
+
// Do this until we're up-to-date.
|
|
746
|
+
|
|
747
|
+
int new_user_version = user_version + 1;
|
|
748
|
+
|
|
749
|
+
NSString *selName = [NSString stringWithFormat:@"upgradeTable_%d_%d", user_version, new_user_version];
|
|
750
|
+
SEL sel = NSSelectorFromString(selName);
|
|
751
|
+
|
|
752
|
+
if ([self respondsToSelector:sel])
|
|
753
|
+
{
|
|
754
|
+
YDBLogInfo(@"Upgrading database (%@) from version %d to %d...",
|
|
755
|
+
[databasePath lastPathComponent], user_version, new_user_version);
|
|
756
|
+
|
|
757
|
+
#pragma clang diagnostic push
|
|
758
|
+
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
|
759
|
+
if ([self performSelector:sel])
|
|
760
|
+
#pragma clang diagnostic pop
|
|
761
|
+
{
|
|
762
|
+
[self set_user_version:new_user_version];
|
|
763
|
+
}
|
|
764
|
+
else
|
|
765
|
+
{
|
|
766
|
+
YDBLogError(@"Error upgrading database (%@)", [databasePath lastPathComponent]);
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else
|
|
771
|
+
{
|
|
772
|
+
YDBLogWarn(@"Missing upgrade method: %@", selName);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
user_version = new_user_version;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
780
|
+
#pragma mark Prepare
|
|
781
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Optional override hook.
|
|
785
|
+
* Don't forget to invoke [super prepare] so super can prepare too.
|
|
786
|
+
*
|
|
787
|
+
* This method is run asynchronously on the snapshotQueue.
|
|
788
|
+
**/
|
|
789
|
+
- (void)prepare
|
|
790
|
+
{
|
|
791
|
+
// Initialize snapshot
|
|
792
|
+
|
|
793
|
+
snapshot = 0;
|
|
794
|
+
|
|
795
|
+
// Write it to disk (replacing any previous value from last app run)
|
|
796
|
+
|
|
797
|
+
[self beginTransaction];
|
|
798
|
+
[self writeSnapshot];
|
|
799
|
+
[self fetchPreviouslyRegisteredExtensionNames];
|
|
800
|
+
[self commitTransaction];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
- (void)beginTransaction
|
|
804
|
+
{
|
|
805
|
+
int status = status = sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
|
806
|
+
if (status != SQLITE_OK)
|
|
807
|
+
{
|
|
808
|
+
YDBLogError(@"Error in '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
- (void)commitTransaction
|
|
813
|
+
{
|
|
814
|
+
int status = status = sqlite3_exec(db, "COMMIT TRANSACTION;", NULL, NULL, NULL);
|
|
815
|
+
if (status != SQLITE_OK)
|
|
816
|
+
{
|
|
817
|
+
YDBLogError(@"Error in '%@': %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
- (void)writeSnapshot
|
|
822
|
+
{
|
|
823
|
+
int status;
|
|
824
|
+
sqlite3_stmt *statement;
|
|
825
|
+
|
|
826
|
+
char *stmt = "INSERT OR REPLACE INTO \"yap2\" (\"extension\", \"key\", \"data\") VALUES (?, ?, ?);";
|
|
827
|
+
|
|
828
|
+
status = sqlite3_prepare_v2(db, stmt, (int)strlen(stmt)+1, &statement, NULL);
|
|
829
|
+
if (status != SQLITE_OK)
|
|
830
|
+
{
|
|
831
|
+
YDBLogError(@"%@: Error creating statement: %d %s",
|
|
832
|
+
NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
|
|
833
|
+
}
|
|
834
|
+
else
|
|
835
|
+
{
|
|
836
|
+
char *extension = "";
|
|
837
|
+
sqlite3_bind_text(statement, 1, extension, (int)strlen(extension), SQLITE_STATIC);
|
|
838
|
+
|
|
839
|
+
char *key = "snapshot";
|
|
840
|
+
sqlite3_bind_text(statement, 2, key, (int)strlen(key), SQLITE_STATIC);
|
|
841
|
+
|
|
842
|
+
sqlite3_bind_int64(statement, 3, (sqlite3_int64)snapshot);
|
|
843
|
+
|
|
844
|
+
status = sqlite3_step(statement);
|
|
845
|
+
if (status != SQLITE_DONE)
|
|
846
|
+
{
|
|
847
|
+
YDBLogError(@"%@: Error in statement: %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
sqlite3_finalize(statement);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
- (void)fetchPreviouslyRegisteredExtensionNames
|
|
855
|
+
{
|
|
856
|
+
int status;
|
|
857
|
+
sqlite3_stmt *statement;
|
|
858
|
+
|
|
859
|
+
char *stmt = "SELECT DISTINCT \"extension\" FROM \"yap2\";";
|
|
860
|
+
|
|
861
|
+
NSMutableArray *extensionNames = [NSMutableArray array];
|
|
862
|
+
|
|
863
|
+
status = sqlite3_prepare_v2(db, stmt, (int)strlen(stmt)+1, &statement, NULL);
|
|
864
|
+
if (status != SQLITE_OK)
|
|
865
|
+
{
|
|
866
|
+
YDBLogError(@"%@: Error creating statement: %d %s",
|
|
867
|
+
NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
|
|
868
|
+
}
|
|
869
|
+
else
|
|
870
|
+
{
|
|
871
|
+
while ((status = sqlite3_step(statement)) == SQLITE_ROW)
|
|
872
|
+
{
|
|
873
|
+
const unsigned char *text = sqlite3_column_text(statement, 0);
|
|
874
|
+
int textSize = sqlite3_column_bytes(statement, 0);
|
|
875
|
+
|
|
876
|
+
NSString *extensionName =
|
|
877
|
+
[[NSString alloc] initWithBytes:text length:textSize encoding:NSUTF8StringEncoding];
|
|
878
|
+
|
|
879
|
+
if ([extensionName length] > 0)
|
|
880
|
+
{
|
|
881
|
+
[extensionNames addObject:extensionName];
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (status != SQLITE_DONE)
|
|
886
|
+
{
|
|
887
|
+
YDBLogError(@"%@: Error in statement: %d %s", NSStringFromSelector(_cmd), status, sqlite3_errmsg(db));
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
sqlite3_finalize(statement);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
previouslyRegisteredExtensionNames = extensionNames;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
897
|
+
#pragma mark Defaults
|
|
898
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
899
|
+
|
|
900
|
+
- (YapDatabaseDefaults *)defaults
|
|
901
|
+
{
|
|
902
|
+
__block YapDatabaseDefaults *result = nil;
|
|
903
|
+
|
|
904
|
+
dispatch_sync(internalQueue, ^{
|
|
905
|
+
|
|
906
|
+
result = [defaults copy];
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
return result;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
- (BOOL)defaultObjectCacheEnabled
|
|
913
|
+
{
|
|
914
|
+
__block BOOL result = NO;
|
|
915
|
+
|
|
916
|
+
dispatch_sync(internalQueue, ^{
|
|
917
|
+
|
|
918
|
+
result = defaults.objectCacheEnabled;
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
- (void)setDefaultObjectCacheEnabled:(BOOL)defaultObjectCacheEnabled
|
|
925
|
+
{
|
|
926
|
+
dispatch_sync(internalQueue, ^{
|
|
927
|
+
|
|
928
|
+
defaults.objectCacheEnabled = defaultObjectCacheEnabled;
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
- (NSUInteger)defaultObjectCacheLimit
|
|
933
|
+
{
|
|
934
|
+
__block NSUInteger result = NO;
|
|
935
|
+
|
|
936
|
+
dispatch_sync(internalQueue, ^{
|
|
937
|
+
|
|
938
|
+
result = defaults.objectCacheLimit;
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
- (void)setDefaultObjectCacheLimit:(NSUInteger)defaultObjectCacheLimit
|
|
945
|
+
{
|
|
946
|
+
dispatch_sync(internalQueue, ^{
|
|
947
|
+
|
|
948
|
+
defaults.objectCacheLimit = defaultObjectCacheLimit;
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
- (BOOL)defaultMetadataCacheEnabled
|
|
953
|
+
{
|
|
954
|
+
__block BOOL result = NO;
|
|
955
|
+
|
|
956
|
+
dispatch_sync(internalQueue, ^{
|
|
957
|
+
|
|
958
|
+
result = defaults.metadataCacheEnabled;
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
- (void)setDefaultMetadataCacheEnabled:(BOOL)defaultMetadataCacheEnabled
|
|
965
|
+
{
|
|
966
|
+
dispatch_sync(internalQueue, ^{
|
|
967
|
+
|
|
968
|
+
defaults.metadataCacheEnabled = defaultMetadataCacheEnabled;
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
- (NSUInteger)defaultMetadataCacheLimit
|
|
973
|
+
{
|
|
974
|
+
__block NSUInteger result = 0;
|
|
975
|
+
|
|
976
|
+
dispatch_sync(internalQueue, ^{
|
|
977
|
+
|
|
978
|
+
result = defaults.metadataCacheLimit;
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
- (void)setDefaultMetadataCacheLimit:(NSUInteger)defaultMetadataCacheLimit
|
|
985
|
+
{
|
|
986
|
+
dispatch_sync(internalQueue, ^{
|
|
987
|
+
|
|
988
|
+
defaults.metadataCacheLimit = defaultMetadataCacheLimit;
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
- (YapDatabasePolicy)defaultObjectPolicy
|
|
993
|
+
{
|
|
994
|
+
__block YapDatabasePolicy result = YapDatabasePolicyShare;
|
|
995
|
+
|
|
996
|
+
dispatch_sync(internalQueue, ^{
|
|
997
|
+
|
|
998
|
+
result = defaults.objectPolicy;
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
- (void)setDefaultObjectPolicy:(YapDatabasePolicy)defaultObjectPolicy
|
|
1005
|
+
{
|
|
1006
|
+
dispatch_sync(internalQueue, ^{
|
|
1007
|
+
|
|
1008
|
+
defaults.objectPolicy = defaultObjectPolicy;
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
- (YapDatabasePolicy)defaultMetadataPolicy
|
|
1013
|
+
{
|
|
1014
|
+
__block YapDatabasePolicy result = YapDatabasePolicyShare;
|
|
1015
|
+
|
|
1016
|
+
dispatch_sync(internalQueue, ^{
|
|
1017
|
+
|
|
1018
|
+
result = defaults.metadataPolicy;
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
- (void)setDefaultMetadataPolicy:(YapDatabasePolicy)defaultMetadataPolicy
|
|
1025
|
+
{
|
|
1026
|
+
dispatch_sync(internalQueue, ^{
|
|
1027
|
+
|
|
1028
|
+
defaults.metadataPolicy = defaultMetadataPolicy;
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
#if TARGET_OS_IPHONE
|
|
1033
|
+
|
|
1034
|
+
- (int)defaultAutoFlushMemoryLevel
|
|
1035
|
+
{
|
|
1036
|
+
__block int result = YapDatabaseConnectionFlushMemoryLevelNone;
|
|
1037
|
+
|
|
1038
|
+
dispatch_sync(internalQueue, ^{
|
|
1039
|
+
|
|
1040
|
+
result = defaults.autoFlushMemoryLevel;
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
- (void)setDefaultAutoFlushMemoryLevel:(int)defaultAutoFlushMemoryLevel
|
|
1047
|
+
{
|
|
1048
|
+
dispatch_sync(internalQueue, ^{
|
|
1049
|
+
|
|
1050
|
+
defaults.autoFlushMemoryLevel = defaultAutoFlushMemoryLevel;
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
#endif
|
|
1055
|
+
|
|
1056
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1057
|
+
#pragma mark Connections
|
|
1058
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* This method is called from newConnection, either above or from a subclass.
|
|
1062
|
+
**/
|
|
1063
|
+
- (void)addConnection:(YapDatabaseConnection *)connection
|
|
1064
|
+
{
|
|
1065
|
+
// We can asynchronously add the connection to the state table.
|
|
1066
|
+
// This is safe as the connection itself must go through the same queue in order to do anything.
|
|
1067
|
+
//
|
|
1068
|
+
// The primary motivation in adding the asynchronous functionality is due to the following common use case:
|
|
1069
|
+
//
|
|
1070
|
+
// YapDatabase *database = [[YapDatabase alloc] initWithPath:path];
|
|
1071
|
+
// YapDatabaseConnection *databaseConnection = [database newConnection];
|
|
1072
|
+
//
|
|
1073
|
+
// The YapDatabase init method is asynchronously preparing itself through the snapshot queue.
|
|
1074
|
+
// We'd like to avoid blocking the very next line of code and allow the asynchronous prepare to continue.
|
|
1075
|
+
|
|
1076
|
+
dispatch_async(connection->connectionQueue, ^{
|
|
1077
|
+
|
|
1078
|
+
dispatch_sync(snapshotQueue, ^{ @autoreleasepool {
|
|
1079
|
+
|
|
1080
|
+
// Add the connection to the state table
|
|
1081
|
+
|
|
1082
|
+
YapDatabaseConnectionState *state = [[YapDatabaseConnectionState alloc] initWithConnection:connection];
|
|
1083
|
+
[connectionStates addObject:state];
|
|
1084
|
+
|
|
1085
|
+
YDBLogVerbose(@"Created new connection(%p) for <%@ %p: databaseName=%@, connectionCount=%lu>",
|
|
1086
|
+
connection, [self class], self, [databasePath lastPathComponent],
|
|
1087
|
+
(unsigned long)[connectionStates count]);
|
|
1088
|
+
|
|
1089
|
+
// Invoke the one-time prepare method, so the connection can perform any needed initialization.
|
|
1090
|
+
// Be sure to do this within the snapshotQueue, as the prepare method depends on this.
|
|
1091
|
+
|
|
1092
|
+
[connection prepare];
|
|
1093
|
+
}});
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* This method is called from YapDatabaseConnection's dealloc method.
|
|
1099
|
+
**/
|
|
1100
|
+
- (void)removeConnection:(YapDatabaseConnection *)connection
|
|
1101
|
+
{
|
|
1102
|
+
dispatch_block_t block = ^{ @autoreleasepool {
|
|
1103
|
+
|
|
1104
|
+
NSUInteger index = 0;
|
|
1105
|
+
for (YapDatabaseConnectionState *state in connectionStates)
|
|
1106
|
+
{
|
|
1107
|
+
if (state->connection == connection)
|
|
1108
|
+
{
|
|
1109
|
+
[connectionStates removeObjectAtIndex:index];
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
index++;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
YDBLogVerbose(@"Removed connection(%p) from <%@ %p: databaseName=%@, connectionCount=%lu>",
|
|
1117
|
+
connection, [self class], self, [databasePath lastPathComponent],
|
|
1118
|
+
(unsigned long)[connectionStates count]);
|
|
1119
|
+
}};
|
|
1120
|
+
|
|
1121
|
+
// We prefer to invoke this method synchronously.
|
|
1122
|
+
//
|
|
1123
|
+
// The connection may be the last object retaining the database.
|
|
1124
|
+
// It's easier to trace object deallocations when they happen in a predictable order.
|
|
1125
|
+
|
|
1126
|
+
if (dispatch_get_specific(IsOnSnapshotQueueKey))
|
|
1127
|
+
block();
|
|
1128
|
+
else
|
|
1129
|
+
dispatch_sync(snapshotQueue, block);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* This is a public method called to create a new connection.
|
|
1134
|
+
**/
|
|
1135
|
+
- (YapDatabaseConnection *)newConnection
|
|
1136
|
+
{
|
|
1137
|
+
YapDatabaseConnection *connection = [[YapDatabaseConnection alloc] initWithDatabase:self];
|
|
1138
|
+
|
|
1139
|
+
[self addConnection:connection];
|
|
1140
|
+
return connection;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1144
|
+
#pragma mark Extensions
|
|
1145
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Registers the extension with the database using the given name.
|
|
1149
|
+
* After registration everything works automatically using just the extension name.
|
|
1150
|
+
*
|
|
1151
|
+
* The registration process is equivalent to a readwrite transaction.
|
|
1152
|
+
* It involves persisting various information about the extension to the database,
|
|
1153
|
+
* as well as possibly populating the extension by enumerating existing rows in the database.
|
|
1154
|
+
*
|
|
1155
|
+
* @return
|
|
1156
|
+
* YES if the extension was properly registered.
|
|
1157
|
+
* NO if an error occurred, such as the extensionName is already registered.
|
|
1158
|
+
*
|
|
1159
|
+
* @see asyncRegisterExtension:withName:completionBlock:
|
|
1160
|
+
* @see asyncRegisterExtension:withName:completionBlock:completionQueue:
|
|
1161
|
+
**/
|
|
1162
|
+
- (BOOL)registerExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName
|
|
1163
|
+
{
|
|
1164
|
+
__block BOOL ready = NO;
|
|
1165
|
+
|
|
1166
|
+
dispatch_sync(writeQueue, ^{ @autoreleasepool {
|
|
1167
|
+
|
|
1168
|
+
ready = [self _registerExtension:extension withName:extensionName];
|
|
1169
|
+
}});
|
|
1170
|
+
|
|
1171
|
+
return ready;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Asynchronoulsy starts the extension registration process.
|
|
1176
|
+
* After registration everything works automatically using just the extension name.
|
|
1177
|
+
*
|
|
1178
|
+
* The registration process is equivalent to a readwrite transaction.
|
|
1179
|
+
* It involves persisting various information about the extension to the database,
|
|
1180
|
+
* as well as possibly populating the extension by enumerating existing rows in the database.
|
|
1181
|
+
*
|
|
1182
|
+
* An optional completion block may be used.
|
|
1183
|
+
* If the extension registration was successful then the ready parameter will be YES.
|
|
1184
|
+
*
|
|
1185
|
+
* The completionBlock will be invoked on the main thread (dispatch_get_main_queue()).
|
|
1186
|
+
**/
|
|
1187
|
+
- (void)asyncRegisterExtension:(YapDatabaseExtension *)extension
|
|
1188
|
+
withName:(NSString *)extensionName
|
|
1189
|
+
completionBlock:(void(^)(BOOL ready))completionBlock
|
|
1190
|
+
{
|
|
1191
|
+
[self asyncRegisterExtension:extension
|
|
1192
|
+
withName:extensionName
|
|
1193
|
+
completionBlock:completionBlock
|
|
1194
|
+
completionQueue:NULL];
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* Asynchronoulsy starts the extension registration process.
|
|
1199
|
+
* After registration everything works automatically using just the extension name.
|
|
1200
|
+
*
|
|
1201
|
+
* The registration process is equivalent to a readwrite transaction.
|
|
1202
|
+
* It involves persisting various information about the extension to the database,
|
|
1203
|
+
* as well as possibly populating the extension by enumerating existing rows in the database.
|
|
1204
|
+
*
|
|
1205
|
+
* An optional completion block may be used.
|
|
1206
|
+
* If the extension registration was successful then the ready parameter will be YES.
|
|
1207
|
+
*
|
|
1208
|
+
* Additionally the dispatch_queue to invoke the completion block may also be specified.
|
|
1209
|
+
* If NULL, dispatch_get_main_queue() is automatically used.
|
|
1210
|
+
**/
|
|
1211
|
+
- (void)asyncRegisterExtension:(YapDatabaseExtension *)extension
|
|
1212
|
+
withName:(NSString *)extensionName
|
|
1213
|
+
completionBlock:(void(^)(BOOL ready))completionBlock
|
|
1214
|
+
completionQueue:(dispatch_queue_t)completionQueue
|
|
1215
|
+
{
|
|
1216
|
+
if (completionQueue == NULL && completionBlock != NULL)
|
|
1217
|
+
completionQueue = dispatch_get_main_queue();
|
|
1218
|
+
|
|
1219
|
+
dispatch_async(writeQueue, ^{ @autoreleasepool {
|
|
1220
|
+
|
|
1221
|
+
BOOL ready = [self _registerExtension:extension withName:extensionName];
|
|
1222
|
+
|
|
1223
|
+
if (completionBlock)
|
|
1224
|
+
{
|
|
1225
|
+
dispatch_async(completionQueue, ^{ @autoreleasepool {
|
|
1226
|
+
|
|
1227
|
+
completionBlock(ready);
|
|
1228
|
+
}});
|
|
1229
|
+
}
|
|
1230
|
+
}});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
*
|
|
1235
|
+
**/
|
|
1236
|
+
- (void)unregisterExtension:(NSString *)extensionName
|
|
1237
|
+
{
|
|
1238
|
+
dispatch_sync(writeQueue, ^{ @autoreleasepool {
|
|
1239
|
+
|
|
1240
|
+
[self _unregisterExtension:extensionName];
|
|
1241
|
+
}});
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
- (void)asyncUnregisterExtension:(NSString *)extensionName
|
|
1245
|
+
completionBlock:(dispatch_block_t)completionBlock
|
|
1246
|
+
{
|
|
1247
|
+
[self asyncUnregisterExtension:extensionName
|
|
1248
|
+
completionBlock:completionBlock
|
|
1249
|
+
completionQueue:NULL];
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
- (void)asyncUnregisterExtension:(NSString *)extensionName
|
|
1253
|
+
completionBlock:(dispatch_block_t)completionBlock
|
|
1254
|
+
completionQueue:(dispatch_queue_t)completionQueue
|
|
1255
|
+
{
|
|
1256
|
+
if (completionQueue == NULL && completionBlock != NULL)
|
|
1257
|
+
completionQueue = dispatch_get_main_queue();
|
|
1258
|
+
|
|
1259
|
+
dispatch_async(writeQueue, ^{ @autoreleasepool {
|
|
1260
|
+
|
|
1261
|
+
[self _unregisterExtension:extensionName];
|
|
1262
|
+
|
|
1263
|
+
if (completionBlock)
|
|
1264
|
+
{
|
|
1265
|
+
dispatch_async(completionQueue, ^{ @autoreleasepool {
|
|
1266
|
+
|
|
1267
|
+
completionBlock();
|
|
1268
|
+
}});
|
|
1269
|
+
}
|
|
1270
|
+
}});
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* Internal utility method.
|
|
1275
|
+
* Handles lazy creation and destruction of short-lived registrationConnection instance.
|
|
1276
|
+
*
|
|
1277
|
+
* @see _registerExtension:withName:
|
|
1278
|
+
* @see _unregisterExtension:
|
|
1279
|
+
**/
|
|
1280
|
+
- (YapDatabaseConnection *)registrationConnection
|
|
1281
|
+
{
|
|
1282
|
+
if (registrationConnection == nil)
|
|
1283
|
+
{
|
|
1284
|
+
registrationConnection = [self newConnection];
|
|
1285
|
+
registrationConnection.name = @"YapDatabase_extensionRegistrationConnection";
|
|
1286
|
+
|
|
1287
|
+
NSTimeInterval delayInSeconds = 10.0;
|
|
1288
|
+
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
|
|
1289
|
+
dispatch_after(popTime, writeQueue, ^(void){
|
|
1290
|
+
|
|
1291
|
+
registrationConnection = nil;
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
return registrationConnection;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Internal method that handles extension registration.
|
|
1300
|
+
* This method must be invoked on the writeQueue.
|
|
1301
|
+
**/
|
|
1302
|
+
- (BOOL)_registerExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName
|
|
1303
|
+
{
|
|
1304
|
+
NSAssert(dispatch_get_specific(IsOnWriteQueueKey), @"Must go through writeQueue.");
|
|
1305
|
+
|
|
1306
|
+
// Validate parameters
|
|
1307
|
+
|
|
1308
|
+
if (extension == nil)
|
|
1309
|
+
{
|
|
1310
|
+
YDBLogError(@"Error registering extension: extension parameter is nil");
|
|
1311
|
+
return NO;
|
|
1312
|
+
}
|
|
1313
|
+
if ([extensionName length] == 0)
|
|
1314
|
+
{
|
|
1315
|
+
YDBLogError(@"Error registering extension: extensionName parameter is nil or empty string");
|
|
1316
|
+
return NO;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Check to ensure extension isn't already registered,
|
|
1320
|
+
// or that the extensionName isn't already taken.
|
|
1321
|
+
|
|
1322
|
+
NSDictionary *_registeredExtensions = [self registeredExtensions];
|
|
1323
|
+
|
|
1324
|
+
if (extension.registeredName != nil)
|
|
1325
|
+
{
|
|
1326
|
+
YDBLogError(@"Error registering extension: extension is already registered");
|
|
1327
|
+
return NO;
|
|
1328
|
+
}
|
|
1329
|
+
if ([_registeredExtensions objectForKey:extensionName] != nil)
|
|
1330
|
+
{
|
|
1331
|
+
YDBLogError(@"Error registering extension: extensionName(%@) already registered", extensionName);
|
|
1332
|
+
return NO;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// Make sure the extension can be supported
|
|
1336
|
+
|
|
1337
|
+
if (![extension supportsDatabase:self withRegisteredExtensions:_registeredExtensions])
|
|
1338
|
+
{
|
|
1339
|
+
YDBLogError(@"Error registering extension: extension doesn't database type or configuration");
|
|
1340
|
+
return NO;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// Set the registeredName now.
|
|
1344
|
+
// The extension will need this in order to perform the registration tasks such as creating tables, etc.
|
|
1345
|
+
|
|
1346
|
+
extension.registeredName = extensionName;
|
|
1347
|
+
|
|
1348
|
+
// Attempt registration
|
|
1349
|
+
|
|
1350
|
+
BOOL result = [[self registrationConnection] registerExtension:extension withName:extensionName];
|
|
1351
|
+
if (result)
|
|
1352
|
+
{
|
|
1353
|
+
// Extension registered
|
|
1354
|
+
// Record dependencies (if there are any)
|
|
1355
|
+
|
|
1356
|
+
NSSet *dependencies = [extension dependencies];
|
|
1357
|
+
|
|
1358
|
+
if (dependencies == nil)
|
|
1359
|
+
dependencies = [NSSet set];
|
|
1360
|
+
|
|
1361
|
+
NSMutableDictionary *newExtensionDependencies = [extensionDependencies mutableCopy];
|
|
1362
|
+
[newExtensionDependencies setObject:dependencies forKey:extensionName];
|
|
1363
|
+
|
|
1364
|
+
extensionDependencies = [newExtensionDependencies copy];
|
|
1365
|
+
}
|
|
1366
|
+
else
|
|
1367
|
+
{
|
|
1368
|
+
// Registration failed
|
|
1369
|
+
|
|
1370
|
+
extension.registeredName = nil;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
return result;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Internal method that handles extension unregistration.
|
|
1378
|
+
* This method must be invoked on the writeQueue.
|
|
1379
|
+
**/
|
|
1380
|
+
- (void)_unregisterExtension:(NSString *)extensionName
|
|
1381
|
+
{
|
|
1382
|
+
NSAssert(dispatch_get_specific(IsOnWriteQueueKey), @"Must go through writeQueue.");
|
|
1383
|
+
|
|
1384
|
+
if ([extensionName length] == 0)
|
|
1385
|
+
{
|
|
1386
|
+
YDBLogError(@"Error unregistering extension: extensionName parameter is nil or empty string");
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
YapDatabaseExtension *extension = [self registeredExtension:extensionName];
|
|
1391
|
+
|
|
1392
|
+
[[self registrationConnection] unregisterExtension:extensionName];
|
|
1393
|
+
extension.registeredName = nil;
|
|
1394
|
+
|
|
1395
|
+
NSMutableDictionary *newExtensionDependencies = [extensionDependencies mutableCopy];
|
|
1396
|
+
[newExtensionDependencies removeObjectForKey:extensionName];
|
|
1397
|
+
|
|
1398
|
+
// Automatically unregister any extensions that were dependent upon this one.
|
|
1399
|
+
|
|
1400
|
+
BOOL done;
|
|
1401
|
+
do
|
|
1402
|
+
{
|
|
1403
|
+
__block NSString *dependentExtName = nil;
|
|
1404
|
+
[newExtensionDependencies enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
|
|
1405
|
+
|
|
1406
|
+
// __unsafe_unretained NSString *extName = (NSString *)key;
|
|
1407
|
+
__unsafe_unretained NSSet *extDependencies = (NSSet *)obj;
|
|
1408
|
+
|
|
1409
|
+
if ([extDependencies containsObject:extensionName])
|
|
1410
|
+
{
|
|
1411
|
+
dependentExtName = (NSString *)key;
|
|
1412
|
+
*stop = YES;
|
|
1413
|
+
}
|
|
1414
|
+
}];
|
|
1415
|
+
|
|
1416
|
+
if (dependentExtName)
|
|
1417
|
+
{
|
|
1418
|
+
YapDatabaseExtension *dependentExt = [self registeredExtension:dependentExtName];
|
|
1419
|
+
|
|
1420
|
+
[[self registrationConnection] unregisterExtension:dependentExtName];
|
|
1421
|
+
dependentExt.registeredName = nil;
|
|
1422
|
+
|
|
1423
|
+
[newExtensionDependencies removeObjectForKey:dependentExtName];
|
|
1424
|
+
|
|
1425
|
+
done = NO;
|
|
1426
|
+
}
|
|
1427
|
+
else
|
|
1428
|
+
{
|
|
1429
|
+
done = YES;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
} while (!done);
|
|
1433
|
+
|
|
1434
|
+
extensionDependencies = [newExtensionDependencies copy];
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Returns the registered extension with the given name.
|
|
1439
|
+
**/
|
|
1440
|
+
- (id)registeredExtension:(NSString *)extensionName
|
|
1441
|
+
{
|
|
1442
|
+
// This method is public
|
|
1443
|
+
|
|
1444
|
+
__block YapDatabaseExtension *result = nil;
|
|
1445
|
+
|
|
1446
|
+
dispatch_block_t block = ^{
|
|
1447
|
+
|
|
1448
|
+
result = [registeredExtensions objectForKey:extensionName];
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
if (dispatch_get_specific(IsOnSnapshotQueueKey))
|
|
1452
|
+
block();
|
|
1453
|
+
else
|
|
1454
|
+
dispatch_sync(snapshotQueue, block);
|
|
1455
|
+
|
|
1456
|
+
return result;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Returns all currently registered extensions as a dictionary.
|
|
1461
|
+
* The key is the registed name (NSString), and the value is the extension (YapDatabaseExtension subclass).
|
|
1462
|
+
**/
|
|
1463
|
+
- (NSDictionary *)registeredExtensions
|
|
1464
|
+
{
|
|
1465
|
+
// This method is public
|
|
1466
|
+
|
|
1467
|
+
__block NSDictionary *extensionsCopy = nil;
|
|
1468
|
+
|
|
1469
|
+
dispatch_block_t block = ^{
|
|
1470
|
+
|
|
1471
|
+
extensionsCopy = registeredExtensions;
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
if (dispatch_get_specific(IsOnSnapshotQueueKey))
|
|
1475
|
+
block();
|
|
1476
|
+
else
|
|
1477
|
+
dispatch_sync(snapshotQueue, block);
|
|
1478
|
+
|
|
1479
|
+
return extensionsCopy;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* This method is only accessible from within the snapshotQueue.
|
|
1484
|
+
* Used by [YapDatabaseConnection prepare].
|
|
1485
|
+
**/
|
|
1486
|
+
- (NSArray *)extensionsOrder
|
|
1487
|
+
{
|
|
1488
|
+
NSAssert(dispatch_get_specific(IsOnSnapshotQueueKey), @"Must go through snapshotQueue for atomic access.");
|
|
1489
|
+
|
|
1490
|
+
return extensionsOrder;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1494
|
+
#pragma mark Pooling
|
|
1495
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1496
|
+
|
|
1497
|
+
- (NSUInteger)maxConnectionPoolCount
|
|
1498
|
+
{
|
|
1499
|
+
__block NSUInteger count = 0;
|
|
1500
|
+
|
|
1501
|
+
dispatch_sync(internalQueue, ^{
|
|
1502
|
+
|
|
1503
|
+
count = maxConnectionPoolCount;
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
return count;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
- (void)setMaxConnectionPoolCount:(NSUInteger)count
|
|
1510
|
+
{
|
|
1511
|
+
dispatch_sync(internalQueue, ^{
|
|
1512
|
+
|
|
1513
|
+
// Update ivar
|
|
1514
|
+
maxConnectionPoolCount = count;
|
|
1515
|
+
|
|
1516
|
+
// Immediately drop any excess connections
|
|
1517
|
+
if ([connectionPoolValues count] > maxConnectionPoolCount)
|
|
1518
|
+
{
|
|
1519
|
+
do
|
|
1520
|
+
{
|
|
1521
|
+
sqlite3 *aDb = (sqlite3 *)[[connectionPoolValues objectAtIndex:0] pointerValue];
|
|
1522
|
+
|
|
1523
|
+
int status = sqlite3_close(aDb);
|
|
1524
|
+
if (status != SQLITE_OK)
|
|
1525
|
+
{
|
|
1526
|
+
YDBLogError(@"Error in sqlite_close: %d %s", status, sqlite3_errmsg(aDb));
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
[connectionPoolValues removeObjectAtIndex:0];
|
|
1530
|
+
[connectionPoolDates removeObjectAtIndex:0];
|
|
1531
|
+
|
|
1532
|
+
} while ([connectionPoolValues count] > maxConnectionPoolCount);
|
|
1533
|
+
|
|
1534
|
+
[self resetConnectionPoolTimer];
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
- (NSTimeInterval)connectionPoolLifetime
|
|
1540
|
+
{
|
|
1541
|
+
__block NSTimeInterval lifetime = 0;
|
|
1542
|
+
|
|
1543
|
+
dispatch_sync(internalQueue, ^{
|
|
1544
|
+
|
|
1545
|
+
lifetime = connectionPoolLifetime;
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
return lifetime;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
- (void)setConnectionPoolLifetime:(NSTimeInterval)lifetime
|
|
1552
|
+
{
|
|
1553
|
+
dispatch_sync(internalQueue, ^{
|
|
1554
|
+
|
|
1555
|
+
// Update ivar
|
|
1556
|
+
connectionPoolLifetime = lifetime;
|
|
1557
|
+
|
|
1558
|
+
// Update timer (if needed)
|
|
1559
|
+
[self resetConnectionPoolTimer];
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* Adds the given connection to the connection pool if possible.
|
|
1565
|
+
*
|
|
1566
|
+
* Returns YES if the instance was added to the pool.
|
|
1567
|
+
* If so, the YapDatabaseConnection must not close the instance.
|
|
1568
|
+
*
|
|
1569
|
+
* Returns NO if the instance was not added to the pool.
|
|
1570
|
+
* If so, the YapDatabaseConnection must close the instance.
|
|
1571
|
+
**/
|
|
1572
|
+
- (BOOL)connectionPoolEnqueue:(sqlite3 *)aDb
|
|
1573
|
+
{
|
|
1574
|
+
__block BOOL result = NO;
|
|
1575
|
+
|
|
1576
|
+
dispatch_sync(internalQueue, ^{
|
|
1577
|
+
|
|
1578
|
+
if ([connectionPoolValues count] < maxConnectionPoolCount)
|
|
1579
|
+
{
|
|
1580
|
+
if (connectionPoolValues == nil)
|
|
1581
|
+
{
|
|
1582
|
+
connectionPoolValues = [[NSMutableArray alloc] init];
|
|
1583
|
+
connectionPoolDates = [[NSMutableArray alloc] init];
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
YDBLogVerbose(@"Enqueuing connection to pool: %p", aDb);
|
|
1587
|
+
|
|
1588
|
+
[connectionPoolValues addObject:[NSValue valueWithPointer:(const void *)aDb]];
|
|
1589
|
+
[connectionPoolDates addObject:[NSDate date]];
|
|
1590
|
+
|
|
1591
|
+
result = YES;
|
|
1592
|
+
|
|
1593
|
+
if ([connectionPoolValues count] == 1)
|
|
1594
|
+
{
|
|
1595
|
+
[self resetConnectionPoolTimer];
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
return result;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Retrieves a connection from the connection pool if available.
|
|
1605
|
+
* Returns NULL if no connections are available.
|
|
1606
|
+
**/
|
|
1607
|
+
- (sqlite3 *)connectionPoolDequeue
|
|
1608
|
+
{
|
|
1609
|
+
__block sqlite3 *aDb = NULL;
|
|
1610
|
+
|
|
1611
|
+
dispatch_sync(internalQueue, ^{
|
|
1612
|
+
|
|
1613
|
+
if ([connectionPoolValues count] > 0)
|
|
1614
|
+
{
|
|
1615
|
+
aDb = (sqlite3 *)[[connectionPoolValues objectAtIndex:0] pointerValue];
|
|
1616
|
+
|
|
1617
|
+
YDBLogVerbose(@"Dequeuing connection from pool: %p", aDb);
|
|
1618
|
+
|
|
1619
|
+
[connectionPoolValues removeObjectAtIndex:0];
|
|
1620
|
+
[connectionPoolDates removeObjectAtIndex:0];
|
|
1621
|
+
|
|
1622
|
+
[self resetConnectionPoolTimer];
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
return aDb;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/**
|
|
1630
|
+
* Internal utility method to handle setting/resetting the timer.
|
|
1631
|
+
**/
|
|
1632
|
+
- (void)resetConnectionPoolTimer
|
|
1633
|
+
{
|
|
1634
|
+
YDBLogAutoTrace();
|
|
1635
|
+
|
|
1636
|
+
if (connectionPoolLifetime <= 0.0 || [connectionPoolValues count] == 0)
|
|
1637
|
+
{
|
|
1638
|
+
if (connectionPoolTimer)
|
|
1639
|
+
{
|
|
1640
|
+
dispatch_source_cancel(connectionPoolTimer);
|
|
1641
|
+
connectionPoolTimer = NULL;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
BOOL isNewTimer = NO;
|
|
1648
|
+
|
|
1649
|
+
if (connectionPoolTimer == NULL)
|
|
1650
|
+
{
|
|
1651
|
+
connectionPoolTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, internalQueue);
|
|
1652
|
+
|
|
1653
|
+
__weak YapDatabase *weakSelf = self;
|
|
1654
|
+
dispatch_source_set_event_handler(connectionPoolTimer, ^{ @autoreleasepool {
|
|
1655
|
+
|
|
1656
|
+
__strong YapDatabase *strongSelf = weakSelf;
|
|
1657
|
+
if (strongSelf)
|
|
1658
|
+
{
|
|
1659
|
+
[strongSelf handleConnectionPoolTimerFire];
|
|
1660
|
+
}
|
|
1661
|
+
}});
|
|
1662
|
+
|
|
1663
|
+
#if !OS_OBJECT_USE_OBJC
|
|
1664
|
+
dispatch_source_t timer = connectionPoolTimer;
|
|
1665
|
+
dispatch_source_set_cancel_handler(connectionPoolTimer, ^{
|
|
1666
|
+
dispatch_release(timer);
|
|
1667
|
+
});
|
|
1668
|
+
#endif
|
|
1669
|
+
|
|
1670
|
+
isNewTimer = YES;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
NSDate *date = [connectionPoolDates objectAtIndex:0];
|
|
1674
|
+
NSTimeInterval interval = [date timeIntervalSinceNow] + connectionPoolLifetime;
|
|
1675
|
+
|
|
1676
|
+
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (interval * NSEC_PER_SEC));
|
|
1677
|
+
dispatch_source_set_timer(connectionPoolTimer, tt, DISPATCH_TIME_FOREVER, 0);
|
|
1678
|
+
|
|
1679
|
+
if (isNewTimer) {
|
|
1680
|
+
dispatch_resume(connectionPoolTimer);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Internal method to handle removing stale connections from the connection pool.
|
|
1686
|
+
**/
|
|
1687
|
+
- (void)handleConnectionPoolTimerFire
|
|
1688
|
+
{
|
|
1689
|
+
YDBLogAutoTrace();
|
|
1690
|
+
|
|
1691
|
+
NSDate *now = [NSDate date];
|
|
1692
|
+
|
|
1693
|
+
BOOL done = NO;
|
|
1694
|
+
while ([connectionPoolValues count] > 0 && !done)
|
|
1695
|
+
{
|
|
1696
|
+
NSTimeInterval interval = [[connectionPoolDates objectAtIndex:0] timeIntervalSinceDate:now] * -1.0;
|
|
1697
|
+
|
|
1698
|
+
if ((interval >= connectionPoolLifetime) || (interval < 0))
|
|
1699
|
+
{
|
|
1700
|
+
sqlite3 *aDb = (sqlite3 *)[[connectionPoolValues objectAtIndex:0] pointerValue];
|
|
1701
|
+
|
|
1702
|
+
YDBLogVerbose(@"Closing connection from pool: %p", aDb);
|
|
1703
|
+
|
|
1704
|
+
int status = sqlite3_close(aDb);
|
|
1705
|
+
if (status != SQLITE_OK)
|
|
1706
|
+
{
|
|
1707
|
+
YDBLogError(@"Error in sqlite_close: %d %s", status, sqlite3_errmsg(aDb));
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
[connectionPoolValues removeObjectAtIndex:0];
|
|
1711
|
+
[connectionPoolDates removeObjectAtIndex:0];
|
|
1712
|
+
}
|
|
1713
|
+
else
|
|
1714
|
+
{
|
|
1715
|
+
done = YES;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
[self resetConnectionPoolTimer];
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1723
|
+
#pragma mark Tables
|
|
1724
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* This method is only accessible from within the snapshotQueue.
|
|
1728
|
+
* Used by [YapDatabaseConnection prepare].
|
|
1729
|
+
**/
|
|
1730
|
+
- (NSDictionary *)registeredTables
|
|
1731
|
+
{
|
|
1732
|
+
NSAssert(dispatch_get_specific(IsOnSnapshotQueueKey), @"Must go through snapshotQueue for atomic access.");
|
|
1733
|
+
|
|
1734
|
+
return registeredTables;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1738
|
+
#pragma mark Snapshot Architecture
|
|
1739
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* The snapshot represents when the database was last modified by a read-write transaction.
|
|
1743
|
+
* This information isn persisted to the 'yap' database, and is separately held in memory.
|
|
1744
|
+
* It serves multiple purposes.
|
|
1745
|
+
*
|
|
1746
|
+
* First is assists in validation of a connection's cache.
|
|
1747
|
+
* When a connection begins a new transaction, it may have items sitting in the cache.
|
|
1748
|
+
* However the connection doesn't know if the items are still valid because another connection may have made changes.
|
|
1749
|
+
*
|
|
1750
|
+
* The snapshot also assists in correcting for a race condition.
|
|
1751
|
+
* It order to minimize blocking we allow read-write transactions to commit outside the context
|
|
1752
|
+
* of the snapshotQueue. This is because the commit may be a time consuming operation, and we
|
|
1753
|
+
* don't want to block read-only transactions during this period. The race condition occurs if a read-only
|
|
1754
|
+
* transactions starts in the midst of a read-write commit, and the read-only transaction gets
|
|
1755
|
+
* a "yap-level" snapshot that's out of sync with the "sql-level" snapshot. This is easily correctable if caught.
|
|
1756
|
+
* Thus we maintain the snapshot in memory, and fetchable via a select query.
|
|
1757
|
+
* One represents the "yap-level" snapshot, and the other represents the "sql-level" snapshot.
|
|
1758
|
+
*
|
|
1759
|
+
* The snapshot is simply a 64-bit integer.
|
|
1760
|
+
* It is reset when the YapDatabase instance is initialized,
|
|
1761
|
+
* and incremented by each read-write transaction (if changes are actually made).
|
|
1762
|
+
**/
|
|
1763
|
+
- (uint64_t)snapshot
|
|
1764
|
+
{
|
|
1765
|
+
if (dispatch_get_specific(IsOnSnapshotQueueKey))
|
|
1766
|
+
{
|
|
1767
|
+
// Very common case.
|
|
1768
|
+
// This method is called on just about every transaction.
|
|
1769
|
+
return snapshot;
|
|
1770
|
+
}
|
|
1771
|
+
else
|
|
1772
|
+
{
|
|
1773
|
+
// Non-common case.
|
|
1774
|
+
// Public access implementation.
|
|
1775
|
+
__block uint64_t result = 0;
|
|
1776
|
+
|
|
1777
|
+
dispatch_sync(snapshotQueue, ^{
|
|
1778
|
+
result = snapshot;
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
return result;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* This method is only accessible from within the snapshotQueue.
|
|
1787
|
+
*
|
|
1788
|
+
* Prior to starting the sqlite commit, the connection must report its changeset to the database.
|
|
1789
|
+
* The database will store the changeset, and provide it to other connections if needed (due to a race condition).
|
|
1790
|
+
*
|
|
1791
|
+
* The following MUST be in the dictionary:
|
|
1792
|
+
*
|
|
1793
|
+
* - snapshot : NSNumber with the changeset's snapshot
|
|
1794
|
+
**/
|
|
1795
|
+
- (void)notePendingChanges:(NSDictionary *)pendingChangeset fromConnection:(YapDatabaseConnection *)sender
|
|
1796
|
+
{
|
|
1797
|
+
NSAssert(dispatch_get_specific(IsOnSnapshotQueueKey), @"Must go through snapshotQueue for atomic access.");
|
|
1798
|
+
NSAssert([pendingChangeset objectForKey:YapDatabaseSnapshotKey], @"Missing required change key: snapshot");
|
|
1799
|
+
|
|
1800
|
+
// The sender is preparing to start the sqlite commit.
|
|
1801
|
+
// We save the changeset in advance to handle possible edge cases.
|
|
1802
|
+
|
|
1803
|
+
[changesets addObject:pendingChangeset];
|
|
1804
|
+
|
|
1805
|
+
YDBLogVerbose(@"Adding pending changeset %@ for database: %@",
|
|
1806
|
+
[[changesets lastObject] objectForKey:YapDatabaseSnapshotKey], self);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/**
|
|
1810
|
+
* This method is only accessible from within the snapshotQueue.
|
|
1811
|
+
*
|
|
1812
|
+
* This method is used if a transaction finds itself in a race condition.
|
|
1813
|
+
* It should retrieve the database's pending and/or committed changes,
|
|
1814
|
+
* and then process them via [connection noteCommittedChanges:].
|
|
1815
|
+
**/
|
|
1816
|
+
- (NSArray *)pendingAndCommittedChangesSince:(uint64_t)connectionSnapshot until:(uint64_t)maxSnapshot
|
|
1817
|
+
{
|
|
1818
|
+
NSAssert(dispatch_get_specific(IsOnSnapshotQueueKey), @"Must go through snapshotQueue for atomic access.");
|
|
1819
|
+
|
|
1820
|
+
NSMutableArray *relevantChangesets = [NSMutableArray arrayWithCapacity:[changesets count]];
|
|
1821
|
+
|
|
1822
|
+
for (NSDictionary *changeset in changesets)
|
|
1823
|
+
{
|
|
1824
|
+
uint64_t changesetSnapshot = [[changeset objectForKey:YapDatabaseSnapshotKey] unsignedLongLongValue];
|
|
1825
|
+
|
|
1826
|
+
if ((changesetSnapshot > connectionSnapshot) && (changesetSnapshot <= maxSnapshot))
|
|
1827
|
+
{
|
|
1828
|
+
[relevantChangesets addObject:changeset];
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
return relevantChangesets;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
/**
|
|
1836
|
+
* This method is only accessible from within the snapshotQueue.
|
|
1837
|
+
*
|
|
1838
|
+
* Upon completion of a readwrite transaction, the connection should report it's changeset to the database.
|
|
1839
|
+
* The database will then forward the changes to all other connection's.
|
|
1840
|
+
*
|
|
1841
|
+
* The following MUST be in the dictionary:
|
|
1842
|
+
*
|
|
1843
|
+
* - snapshot : NSNumber with the changeset's snapshot
|
|
1844
|
+
**/
|
|
1845
|
+
- (void)noteCommittedChanges:(NSDictionary *)changeset fromConnection:(YapDatabaseConnection *)sender
|
|
1846
|
+
{
|
|
1847
|
+
NSAssert(dispatch_get_specific(IsOnSnapshotQueueKey), @"Must go through snapshotQueue for atomic access.");
|
|
1848
|
+
NSAssert([changeset objectForKey:YapDatabaseSnapshotKey], @"Missing required change key: snapshot");
|
|
1849
|
+
|
|
1850
|
+
// The sender has finished the sqlite commit, and all data is now written to disk.
|
|
1851
|
+
|
|
1852
|
+
// Update the in-memory snapshot,
|
|
1853
|
+
// which represents the most recent snapshot of the last committed readwrite transaction.
|
|
1854
|
+
|
|
1855
|
+
snapshot = [[changeset objectForKey:YapDatabaseSnapshotKey] unsignedLongLongValue];
|
|
1856
|
+
|
|
1857
|
+
// Update registeredExtensions, if changed.
|
|
1858
|
+
|
|
1859
|
+
NSDictionary *newRegisteredExtensions = [changeset objectForKey:YapDatabaseRegisteredExtensionsKey];
|
|
1860
|
+
if (newRegisteredExtensions)
|
|
1861
|
+
{
|
|
1862
|
+
registeredExtensions = newRegisteredExtensions;
|
|
1863
|
+
extensionsOrder = [changeset objectForKey:YapDatabaseExtensionsOrderKey];
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// Update registeredTables, if changed.
|
|
1867
|
+
|
|
1868
|
+
NSDictionary *newRegisteredTables = [changeset objectForKey:YapDatabaseRegisteredTablesKey];
|
|
1869
|
+
if (newRegisteredTables)
|
|
1870
|
+
{
|
|
1871
|
+
registeredTables = newRegisteredTables;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// Forward the changeset to all other connections so they can perform any needed updates.
|
|
1875
|
+
// Generally this means updating the in-memory components such as the cache.
|
|
1876
|
+
|
|
1877
|
+
dispatch_group_t group = NULL;
|
|
1878
|
+
|
|
1879
|
+
for (YapDatabaseConnectionState *state in connectionStates)
|
|
1880
|
+
{
|
|
1881
|
+
if (state->connection != sender)
|
|
1882
|
+
{
|
|
1883
|
+
// Create strong reference (state->connection is weak)
|
|
1884
|
+
__strong YapDatabaseConnection *connection = state->connection;
|
|
1885
|
+
|
|
1886
|
+
if (connection)
|
|
1887
|
+
{
|
|
1888
|
+
if (group == NULL)
|
|
1889
|
+
group = dispatch_group_create();
|
|
1890
|
+
|
|
1891
|
+
dispatch_group_async(group, connection->connectionQueue, ^{ @autoreleasepool {
|
|
1892
|
+
|
|
1893
|
+
[connection noteCommittedChanges:changeset];
|
|
1894
|
+
}});
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// Schedule block to be executed once all connections have processed the changes.
|
|
1900
|
+
|
|
1901
|
+
BOOL isInternalChangeset = (sender == nil);
|
|
1902
|
+
|
|
1903
|
+
dispatch_block_t block = ^{
|
|
1904
|
+
|
|
1905
|
+
// All connections have now processed the changes.
|
|
1906
|
+
// So we no longer need to retain the changeset in memory.
|
|
1907
|
+
|
|
1908
|
+
if (isInternalChangeset)
|
|
1909
|
+
{
|
|
1910
|
+
YDBLogVerbose(@"Completed internal changeset %@ for database: %@",
|
|
1911
|
+
[changeset objectForKey:YapDatabaseSnapshotKey], self);
|
|
1912
|
+
}
|
|
1913
|
+
else
|
|
1914
|
+
{
|
|
1915
|
+
YDBLogVerbose(@"Dropping processed changeset %@ for database: %@",
|
|
1916
|
+
[changeset objectForKey:YapDatabaseSnapshotKey], self);
|
|
1917
|
+
|
|
1918
|
+
[changesets removeObjectAtIndex:0];
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
#if !OS_OBJECT_USE_OBJC
|
|
1922
|
+
if (group)
|
|
1923
|
+
dispatch_release(group);
|
|
1924
|
+
#endif
|
|
1925
|
+
};
|
|
1926
|
+
|
|
1927
|
+
if (group)
|
|
1928
|
+
dispatch_group_notify(group, snapshotQueue, block);
|
|
1929
|
+
else
|
|
1930
|
+
block();
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1934
|
+
#pragma mark Manual Checkpointing
|
|
1935
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1936
|
+
|
|
1937
|
+
/**
|
|
1938
|
+
* This method should be called whenever the maximum checkpointable snapshot is incremented.
|
|
1939
|
+
* That is, the state of every connection is known to the system.
|
|
1940
|
+
* And a snaphot cannot be checkpointed until every connection is at or past that snapshot.
|
|
1941
|
+
* Thus, we can know the point at which a snapshot becomes checkpointable,
|
|
1942
|
+
* and we can thus optimize the checkpoint invocations such that
|
|
1943
|
+
* each invocation is able to checkpoint one or more commits.
|
|
1944
|
+
**/
|
|
1945
|
+
- (void)asyncCheckpoint:(uint64_t)maxCheckpointableSnapshot
|
|
1946
|
+
{
|
|
1947
|
+
__weak YapDatabase *weakSelf = self;
|
|
1948
|
+
|
|
1949
|
+
dispatch_async(checkpointQueue, ^{ @autoreleasepool {
|
|
1950
|
+
|
|
1951
|
+
__strong YapDatabase *strongSelf = weakSelf;
|
|
1952
|
+
if (strongSelf == nil) return;
|
|
1953
|
+
|
|
1954
|
+
YDBLogVerbose(@"Checkpointing up to snapshot %llu", maxCheckpointableSnapshot);
|
|
1955
|
+
|
|
1956
|
+
// We're ready to checkpoint more frames.
|
|
1957
|
+
//
|
|
1958
|
+
// So we're going to execute a passive checkpoint.
|
|
1959
|
+
// That is, without disrupting any connections, we're going to write pages from the WAL into the database.
|
|
1960
|
+
// The checkpoint can only write pages from snapshots if all connections are at or beyond the snapshot.
|
|
1961
|
+
// Thus, this method is only called by a connection that moves the min snapshot forward.
|
|
1962
|
+
|
|
1963
|
+
int frameCount = 0;
|
|
1964
|
+
int checkpointCount = 0;
|
|
1965
|
+
|
|
1966
|
+
int result = sqlite3_wal_checkpoint_v2(strongSelf->db, "main",
|
|
1967
|
+
SQLITE_CHECKPOINT_PASSIVE, &frameCount, &checkpointCount);
|
|
1968
|
+
|
|
1969
|
+
// frameCount = total number of frames in the log file
|
|
1970
|
+
// checkpointCount = total number of checkpointed frames
|
|
1971
|
+
// (including any that were already checkpointed before the function was called)
|
|
1972
|
+
|
|
1973
|
+
if (result != SQLITE_OK)
|
|
1974
|
+
{
|
|
1975
|
+
if (result == SQLITE_BUSY) {
|
|
1976
|
+
YDBLogVerbose(@"sqlite3_wal_checkpoint_v2 returned SQLITE_BUSY");
|
|
1977
|
+
}
|
|
1978
|
+
else {
|
|
1979
|
+
YDBLogWarn(@"sqlite3_wal_checkpoint_v2 returned error code: %d", result);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
return;// from_block
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
YDBLogVerbose(@"Post-checkpoint (%llu): frames(%d) checkpointed(%d)",
|
|
1986
|
+
maxCheckpointableSnapshot, frameCount, checkpointCount);
|
|
1987
|
+
|
|
1988
|
+
// Have we checkpointed the entire WAL yet?
|
|
1989
|
+
|
|
1990
|
+
if (frameCount == checkpointCount)
|
|
1991
|
+
{
|
|
1992
|
+
// We've checkpointed every single frame.
|
|
1993
|
+
// This means the next read-write transaction will reset the WAL (instead of appending to it).
|
|
1994
|
+
//
|
|
1995
|
+
// However, this will get spoiled if there are active read-only transactions that
|
|
1996
|
+
// were started before our checkpoint finished, and continue to exist during the next read-write.
|
|
1997
|
+
// It's not a big deal if the occasional read-only transaction happens to spoil the WAL reset.
|
|
1998
|
+
// In those cases, the WAL generally gets reset shortly thereafter.
|
|
1999
|
+
// Long-lived read transactions are a different case entirely.
|
|
2000
|
+
// These transactions spoil it every single time, and could potentially cause the WAL to grow indefinitely.
|
|
2001
|
+
//
|
|
2002
|
+
// The solution is to notify active long-lived connections, and tell them to re-begin their transaction
|
|
2003
|
+
// on the same snapshot. But this time the sqlite machinery will read directly from the database,
|
|
2004
|
+
// and thus unlock the WAL so it can be reset.
|
|
2005
|
+
|
|
2006
|
+
dispatch_async(snapshotQueue, ^{
|
|
2007
|
+
|
|
2008
|
+
for (YapDatabaseConnectionState *state in connectionStates)
|
|
2009
|
+
{
|
|
2010
|
+
if (state->yapLevelSharedReadLock &&
|
|
2011
|
+
state->longLivedReadTransaction &&
|
|
2012
|
+
state->lastKnownSnapshot == snapshot)
|
|
2013
|
+
{
|
|
2014
|
+
[state->connection maybeResetLongLivedReadTransaction];
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
}});
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
@end
|