diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 02fdd4a..7bb27c4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,12 +9,11 @@ jobs: - uses: szenius/set-timezone@v1.2 with: timezoneMacos: "Asia/Baghdad" - - uses: actions/checkout@v3 - - name: Install Cocoapods - run: | - gem install cocoapods - pod install --project-directory=Example - - name: Run Tests - run: | - set -eo pipefail - xcodebuild test -enableCodeCoverage YES -workspace Example/MuslimData.xcworkspace -scheme MuslimData-Example -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' | xcpretty + - uses: swift-actions/setup-swift@v2 + with: + swift-version: "5.10" + - uses: actions/checkout@v4 + - name: Build + run: swift build + - name: Run tests + run: swift test diff --git a/.gitignore b/.gitignore index 93c86d3..5d4c3a3 100755 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,10 @@ DerivedData Carthage/Build +### SwiftPackageManager ### +Packages +xcuserdata + # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control diff --git a/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata old mode 100755 new mode 100644 similarity index 100% rename from Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index 4a2d9ab..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -osx_image: xcode10.2 -language: swift -before_script: - - sudo systemsetup -settimezone Asia/Baghdad -script: - - set -o pipefail && xcodebuild -enableCodeCoverage YES -workspace Example/MuslimData.xcworkspace -scheme MuslimData-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,name=iPhone XS,OS=12.2' test | xcpretty --test --color - - pod repo update - - pod lib lint --allow-warnings -deploy: - provider: script - script: bash scripts/push.sh - on: - tags: true diff --git a/Example/.swiftformat b/Example/.swiftformat deleted file mode 100755 index 3fa3cc7..0000000 --- a/Example/.swiftformat +++ /dev/null @@ -1,10 +0,0 @@ -# format options! ---swiftversion 5.1 - -# file options ---exclude Pods - -# rules ---disable trailingCommas,unusedArguments ---disable wrapMultilineStatementBraces - diff --git a/Example/.swiftlint.yml b/Example/.swiftlint.yml deleted file mode 100755 index 8a425c0..0000000 --- a/Example/.swiftlint.yml +++ /dev/null @@ -1,12 +0,0 @@ -excluded: - - Pods - - ../MuslimData/Classes/Models/PrayerTimes/Prayer.swift -included: - - ../MuslimData -identifier_name: - excluded: - - id - - en - - ar - - fa - - ru diff --git a/Example/MuslimData.xcodeproj/project.pbxproj b/Example/MuslimData.xcodeproj/project.pbxproj deleted file mode 100755 index 2918e82..0000000 --- a/Example/MuslimData.xcodeproj/project.pbxproj +++ /dev/null @@ -1,719 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 2D110EDE216BE7FB002035FA /* PrayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D110EDD216BE7FB002035FA /* PrayerTests.swift */; }; - 2D1D63A32B9CCF2100DBC74B /* MIGRATION_GUIDE.md in Resources */ = {isa = PBXBuildFile; fileRef = 2D1D63A22B9CCF2100DBC74B /* MIGRATION_GUIDE.md */; }; - 2D27AC4B2188E621004722BD /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D27AC4A2188E621004722BD /* LocationViewController.swift */; }; - 2D4558BA21694A5200A08DAF /* LocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4558B921694A5200A08DAF /* LocationTests.swift */; }; - 2D6B21C421708D600054F8C0 /* NamesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6B21C321708D600054F8C0 /* NamesTest.swift */; }; - 2D9FC355217278E0003EF074 /* AzkarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9FC354217278E0003EF074 /* AzkarTests.swift */; }; - 2DBD7E7B218E01AF00B6595B /* AzkarChaptersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBD7E7A218E01AF00B6595B /* AzkarChaptersViewController.swift */; }; - 2DBD7E7E218E05FD00B6595B /* AzkarDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBD7E7D218E05FD00B6595B /* AzkarDetailViewController.swift */; }; - 2DCAA592218C8A7D00F4B065 /* PrayersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCAA591218C8A7D00F4B065 /* PrayersViewController.swift */; }; - 2DCAA594218C97A800F4B065 /* NamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCAA593218C97A800F4B065 /* NamesViewController.swift */; }; - 2DEFB3D9218B29ED005D94F4 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DEFB3D8218B29ED005D94F4 /* Location.swift */; }; - 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; - 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; - 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; - 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; - AB4BCD02C3DCCBDDC1561720 /* Pods_MuslimData_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADF54AF43802AA1A3AAAFA2C /* Pods_MuslimData_Example.framework */; }; - CDB24AAE23DBDE3BEC76E09E /* Pods_MuslimData_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6C6730E9A81B0B385FC0760 /* Pods_MuslimData_Tests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 607FACC81AFB9204008FA782 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 607FACCF1AFB9204008FA782; - remoteInfo = MuslimData; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 1FA7CF45010583A8DE450B74 /* Pods-MuslimData_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MuslimData_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.debug.xcconfig"; sourceTree = ""; }; - 2D110EDD216BE7FB002035FA /* PrayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrayerTests.swift; sourceTree = ""; }; - 2D1D63A22B9CCF2100DBC74B /* MIGRATION_GUIDE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = MIGRATION_GUIDE.md; path = ../MIGRATION_GUIDE.md; sourceTree = ""; }; - 2D27AC4A2188E621004722BD /* LocationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = ""; }; - 2D4558B921694A5200A08DAF /* LocationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationTests.swift; sourceTree = ""; }; - 2D6B21C321708D600054F8C0 /* NamesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamesTest.swift; sourceTree = ""; }; - 2D9FC354217278E0003EF074 /* AzkarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzkarTests.swift; sourceTree = ""; }; - 2DBD7E7A218E01AF00B6595B /* AzkarChaptersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzkarChaptersViewController.swift; sourceTree = ""; }; - 2DBD7E7D218E05FD00B6595B /* AzkarDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzkarDetailViewController.swift; sourceTree = ""; }; - 2DCAA591218C8A7D00F4B065 /* PrayersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrayersViewController.swift; sourceTree = ""; }; - 2DCAA593218C97A800F4B065 /* NamesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamesViewController.swift; sourceTree = ""; }; - 2DEFB3D8218B29ED005D94F4 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; }; - 5665FE91640402C3D36C56F2 /* Pods-MuslimData_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MuslimData_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.release.xcconfig"; sourceTree = ""; }; - 607FACD01AFB9204008FA782 /* MuslimData_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MuslimData_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 607FACE51AFB9204008FA782 /* MuslimData_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MuslimData_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 74AFAA0290358FD0025B4CFB /* Pods-MuslimData_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MuslimData_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.debug.xcconfig"; sourceTree = ""; }; - ADF54AF43802AA1A3AAAFA2C /* Pods_MuslimData_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MuslimData_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B03E607FABE06466BB50FF62 /* Pods-MuslimData_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MuslimData_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.release.xcconfig"; sourceTree = ""; }; - C7F9702CA1CFE6102812811C /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - CBF44BC9BD957BA32BBAB0D7 /* MuslimData.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = MuslimData.podspec; path = ../MuslimData.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - D6C6730E9A81B0B385FC0760 /* Pods_MuslimData_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MuslimData_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E0705505FB7482A035EE85A3 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 607FACCD1AFB9204008FA782 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AB4BCD02C3DCCBDDC1561720 /* Pods_MuslimData_Example.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 607FACE21AFB9204008FA782 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CDB24AAE23DBDE3BEC76E09E /* Pods_MuslimData_Tests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 209A686A541F4C24A075E63C /* Pods */ = { - isa = PBXGroup; - children = ( - 1FA7CF45010583A8DE450B74 /* Pods-MuslimData_Example.debug.xcconfig */, - B03E607FABE06466BB50FF62 /* Pods-MuslimData_Example.release.xcconfig */, - 74AFAA0290358FD0025B4CFB /* Pods-MuslimData_Tests.debug.xcconfig */, - 5665FE91640402C3D36C56F2 /* Pods-MuslimData_Tests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - 2DBD7E7C218E01BF00B6595B /* Azkars */ = { - isa = PBXGroup; - children = ( - 2DBD7E7A218E01AF00B6595B /* AzkarChaptersViewController.swift */, - 2DBD7E7D218E05FD00B6595B /* AzkarDetailViewController.swift */, - ); - name = Azkars; - sourceTree = ""; - }; - 2DBD7E7F218E346600B6595B /* Prayers */ = { - isa = PBXGroup; - children = ( - 2DEFB3D7218B29D8005D94F4 /* Locations */, - 2DCAA591218C8A7D00F4B065 /* PrayersViewController.swift */, - ); - name = Prayers; - sourceTree = ""; - }; - 2DBD7E80218E347500B6595B /* Names */ = { - isa = PBXGroup; - children = ( - 2DCAA593218C97A800F4B065 /* NamesViewController.swift */, - ); - name = Names; - sourceTree = ""; - }; - 2DBD7E81218E34A400B6595B /* Assets */ = { - isa = PBXGroup; - children = ( - 607FACD91AFB9204008FA782 /* Main.storyboard */, - 607FACDC1AFB9204008FA782 /* Images.xcassets */, - 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, - ); - name = Assets; - sourceTree = ""; - }; - 2DEFB3D7218B29D8005D94F4 /* Locations */ = { - isa = PBXGroup; - children = ( - 2D27AC4A2188E621004722BD /* LocationViewController.swift */, - 2DEFB3D8218B29ED005D94F4 /* Location.swift */, - ); - name = Locations; - sourceTree = ""; - }; - 41A62F07BE87B6355FB1E1CE /* Frameworks */ = { - isa = PBXGroup; - children = ( - ADF54AF43802AA1A3AAAFA2C /* Pods_MuslimData_Example.framework */, - D6C6730E9A81B0B385FC0760 /* Pods_MuslimData_Tests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 607FACC71AFB9204008FA782 = { - isa = PBXGroup; - children = ( - 607FACF51AFB993E008FA782 /* Podspec Metadata */, - 607FACD21AFB9204008FA782 /* Example for MuslimData */, - 607FACE81AFB9204008FA782 /* Tests */, - 607FACD11AFB9204008FA782 /* Products */, - 209A686A541F4C24A075E63C /* Pods */, - 41A62F07BE87B6355FB1E1CE /* Frameworks */, - ); - sourceTree = ""; - }; - 607FACD11AFB9204008FA782 /* Products */ = { - isa = PBXGroup; - children = ( - 607FACD01AFB9204008FA782 /* MuslimData_Example.app */, - 607FACE51AFB9204008FA782 /* MuslimData_Tests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 607FACD21AFB9204008FA782 /* Example for MuslimData */ = { - isa = PBXGroup; - children = ( - 607FACD51AFB9204008FA782 /* AppDelegate.swift */, - 2DBD7E7F218E346600B6595B /* Prayers */, - 2DBD7E80218E347500B6595B /* Names */, - 2DBD7E7C218E01BF00B6595B /* Azkars */, - 2DBD7E81218E34A400B6595B /* Assets */, - 607FACD31AFB9204008FA782 /* Supporting Files */, - ); - name = "Example for MuslimData"; - path = MuslimData; - sourceTree = ""; - }; - 607FACD31AFB9204008FA782 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 607FACD41AFB9204008FA782 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 607FACE81AFB9204008FA782 /* Tests */ = { - isa = PBXGroup; - children = ( - 2D9FC354217278E0003EF074 /* AzkarTests.swift */, - 2D6B21C321708D600054F8C0 /* NamesTest.swift */, - 2D110EDD216BE7FB002035FA /* PrayerTests.swift */, - 2D4558B921694A5200A08DAF /* LocationTests.swift */, - 607FACE91AFB9204008FA782 /* Supporting Files */, - ); - path = Tests; - sourceTree = ""; - }; - 607FACE91AFB9204008FA782 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 607FACEA1AFB9204008FA782 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { - isa = PBXGroup; - children = ( - CBF44BC9BD957BA32BBAB0D7 /* MuslimData.podspec */, - E0705505FB7482A035EE85A3 /* README.md */, - 2D1D63A22B9CCF2100DBC74B /* MIGRATION_GUIDE.md */, - C7F9702CA1CFE6102812811C /* LICENSE */, - ); - name = "Podspec Metadata"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 607FACCF1AFB9204008FA782 /* MuslimData_Example */ = { - isa = PBXNativeTarget; - buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MuslimData_Example" */; - buildPhases = ( - 908034E7F08C00D8F832C38F /* [CP] Check Pods Manifest.lock */, - 2DABCDEA216296CC008F4B9B /* SwiftFormat */, - 607FACCC1AFB9204008FA782 /* Sources */, - 607FACCD1AFB9204008FA782 /* Frameworks */, - 607FACCE1AFB9204008FA782 /* Resources */, - A00438D347074F12FE01F3D3 /* [CP] Embed Pods Frameworks */, - 2DABCDE821628FFF008F4B9B /* SwiftLint */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = MuslimData_Example; - productName = MuslimData; - productReference = 607FACD01AFB9204008FA782 /* MuslimData_Example.app */; - productType = "com.apple.product-type.application"; - }; - 607FACE41AFB9204008FA782 /* MuslimData_Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MuslimData_Tests" */; - buildPhases = ( - 379ECBACD51077EED9BCFABC /* [CP] Check Pods Manifest.lock */, - 607FACE11AFB9204008FA782 /* Sources */, - 607FACE21AFB9204008FA782 /* Frameworks */, - 607FACE31AFB9204008FA782 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 607FACE71AFB9204008FA782 /* PBXTargetDependency */, - ); - name = MuslimData_Tests; - productName = Tests; - productReference = 607FACE51AFB9204008FA782 /* MuslimData_Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 607FACC81AFB9204008FA782 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 1420; - ORGANIZATIONNAME = CocoaPods; - TargetAttributes = { - 607FACCF1AFB9204008FA782 = { - CreatedOnToolsVersion = 6.3.1; - DevelopmentTeam = 4HFFW4423Q; - LastSwiftMigration = 1020; - }; - 607FACE41AFB9204008FA782 = { - CreatedOnToolsVersion = 6.3.1; - DevelopmentTeam = 4HFFW4423Q; - LastSwiftMigration = 1020; - TestTargetID = 607FACCF1AFB9204008FA782; - }; - }; - }; - buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "MuslimData" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 607FACC71AFB9204008FA782; - productRefGroup = 607FACD11AFB9204008FA782 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 607FACCF1AFB9204008FA782 /* MuslimData_Example */, - 607FACE41AFB9204008FA782 /* MuslimData_Tests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 607FACCE1AFB9204008FA782 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2D1D63A32B9CCF2100DBC74B /* MIGRATION_GUIDE.md in Resources */, - 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, - 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, - 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 607FACE31AFB9204008FA782 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 2DABCDE821628FFF008F4B9B /* SwiftLint */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = SwiftLint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; - }; - 2DABCDEA216296CC008F4B9B /* SwiftFormat */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = SwiftFormat; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftformat > /dev/null; then\n swiftformat .\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n"; - }; - 379ECBACD51077EED9BCFABC /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-MuslimData_Tests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 908034E7F08C00D8F832C38F /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-MuslimData_Example-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - A00438D347074F12FE01F3D3 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/GRDB.swift/GRDB.framework", - "${BUILT_PRODUCTS_DIR}/MuslimData/MuslimData.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRDB.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MuslimData.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 607FACCC1AFB9204008FA782 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2D27AC4B2188E621004722BD /* LocationViewController.swift in Sources */, - 2DCAA594218C97A800F4B065 /* NamesViewController.swift in Sources */, - 2DBD7E7B218E01AF00B6595B /* AzkarChaptersViewController.swift in Sources */, - 2DEFB3D9218B29ED005D94F4 /* Location.swift in Sources */, - 2DBD7E7E218E05FD00B6595B /* AzkarDetailViewController.swift in Sources */, - 2DCAA592218C8A7D00F4B065 /* PrayersViewController.swift in Sources */, - 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 607FACE11AFB9204008FA782 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2D110EDE216BE7FB002035FA /* PrayerTests.swift in Sources */, - 2D6B21C421708D600054F8C0 /* NamesTest.swift in Sources */, - 2D4558BA21694A5200A08DAF /* LocationTests.swift in Sources */, - 2D9FC355217278E0003EF074 /* AzkarTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 607FACCF1AFB9204008FA782 /* MuslimData_Example */; - targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 607FACD91AFB9204008FA782 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 607FACDA1AFB9204008FA782 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { - isa = PBXVariantGroup; - children = ( - 607FACDF1AFB9204008FA782 /* Base */, - ); - name = LaunchScreen.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 607FACED1AFB9204008FA782 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 607FACEE1AFB9204008FA782 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 607FACF01AFB9204008FA782 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 1FA7CF45010583A8DE450B74 /* Pods-MuslimData_Example.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 4HFFW4423Q; - INFOPLIST_FILE = MuslimData/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MODULE_NAME = ExampleApp; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 607FACF11AFB9204008FA782 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B03E607FABE06466BB50FF62 /* Pods-MuslimData_Example.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 4HFFW4423Q; - INFOPLIST_FILE = MuslimData/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MODULE_NAME = ExampleApp; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 607FACF31AFB9204008FA782 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 74AFAA0290358FD0025B4CFB /* Pods-MuslimData_Tests.debug.xcconfig */; - buildSettings = { - DEVELOPMENT_TEAM = 4HFFW4423Q; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MuslimData_Example.app/MuslimData_Example"; - }; - name = Debug; - }; - 607FACF41AFB9204008FA782 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5665FE91640402C3D36C56F2 /* Pods-MuslimData_Tests.release.xcconfig */; - buildSettings = { - DEVELOPMENT_TEAM = 4HFFW4423Q; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - INFOPLIST_FILE = Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MuslimData_Example.app/MuslimData_Example"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "MuslimData" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 607FACED1AFB9204008FA782 /* Debug */, - 607FACEE1AFB9204008FA782 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MuslimData_Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 607FACF01AFB9204008FA782 /* Debug */, - 607FACF11AFB9204008FA782 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MuslimData_Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 607FACF31AFB9204008FA782 /* Debug */, - 607FACF41AFB9204008FA782 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 607FACC81AFB9204008FA782 /* Project object */; -} diff --git a/Example/MuslimData.xcodeproj/xcshareddata/xcschemes/MuslimData-Example.xcscheme b/Example/MuslimData.xcodeproj/xcshareddata/xcschemes/MuslimData-Example.xcscheme deleted file mode 100755 index de1a63f..0000000 --- a/Example/MuslimData.xcodeproj/xcshareddata/xcschemes/MuslimData-Example.xcscheme +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/MuslimData.xcworkspace/contents.xcworkspacedata b/Example/MuslimData.xcworkspace/contents.xcworkspacedata deleted file mode 100755 index 7ac79f1..0000000 --- a/Example/MuslimData.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Example/MuslimData.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/MuslimData.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100755 index 18d9810..0000000 --- a/Example/MuslimData.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Example/MuslimData/AppDelegate.swift b/Example/MuslimData/AppDelegate.swift deleted file mode 100755 index 54b8afa..0000000 --- a/Example/MuslimData/AppDelegate.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AppDelegate.swift -// MuslimData -// -// Created by Kosrat D. Ahmad on 09/27/2018. -// Copyright (c) 2018 Kosrat D. Ahmad. All rights reserved. -// - -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } -} diff --git a/Example/MuslimData/AzkarChaptersViewController.swift b/Example/MuslimData/AzkarChaptersViewController.swift deleted file mode 100755 index c52cb5b..0000000 --- a/Example/MuslimData/AzkarChaptersViewController.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// AzkarChaptersViewController.swift -// MuslimData_Example -// -// Created by Kosrat D. Ahmad on 11/3/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -import MuslimData -import UIKit - -class AzkarChaptersViewController: UIViewController { - // MARK: - Outlets - - @IBOutlet var chaptersTable: UITableView! - - // MARK: - Properties - - var chapters: [AzkarChapter] = [] - - // MARK: - View Lifecycles - - override func viewDidLoad() { - super.viewDidLoad() - - chaptersTable.delegate = self - chaptersTable.dataSource = self - - // Get azkar chapters from MuslimData library. - Task.init { - let chapters = try! await MuslimRepository().getAzkarChapters(language: .en) - self.chapters = chapters! - self.chaptersTable.reloadData() - } - } -} - -// MARK: - UITableViewDelegate - -extension AzkarChaptersViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let controller = storyboard?.instantiateViewController(withIdentifier: "azkarController") as? AzkarDetailViewController - controller?.azkarChapter = chapters[indexPath.row] - navigationController?.pushViewController(controller!, animated: true) - tableView.deselectRow(at: indexPath, animated: true) - } -} - -// MARK: - UITableViewDataSource - -extension AzkarChaptersViewController: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - chapters.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "chapterCell", for: indexPath) - let chapter = chapters[indexPath.row] - cell.textLabel?.text = chapter.name - - return cell - } -} diff --git a/Example/MuslimData/AzkarDetailViewController.swift b/Example/MuslimData/AzkarDetailViewController.swift deleted file mode 100755 index 883a466..0000000 --- a/Example/MuslimData/AzkarDetailViewController.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// AzkarDetailViewController.swift -// MuslimData_Example -// -// Created by Kosrat D. Ahmad on 11/3/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -import MuslimData -import UIKit - -class AzkarDetailViewController: UIViewController { - // MARK: - Outlets - - @IBOutlet var azkarTable: UITableView! - - // MARK: - Properties - - var azkarChapter: AzkarChapter? - var azkars: [AzkarItem] = [] - - // MARK: - View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - title = azkarChapter!.name - - azkarTable.dataSource = self - azkarTable.tableFooterView = UIView() - - // Get azkar items from MuslimData library. - Task.init { - let items = try! await MuslimRepository().getAzkarItems(language: .en, chapterId: azkarChapter!.id) - self.azkars = items! - self.azkarTable.reloadData() - } - } -} - -// MARK: - UITableViewDataSource - -extension AzkarDetailViewController: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - azkars.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "azkarCell", for: indexPath) - let azkar = azkars[indexPath.row] - cell.textLabel?.text = azkar.item - cell.detailTextLabel?.text = "\(azkar.translation) \n\nReference: \(azkar.reference)" - - return cell - } -} diff --git a/Example/MuslimData/Base.lproj/LaunchScreen.xib b/Example/MuslimData/Base.lproj/LaunchScreen.xib deleted file mode 100755 index 2b1f6e2..0000000 --- a/Example/MuslimData/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/MuslimData/Base.lproj/Main.storyboard b/Example/MuslimData/Base.lproj/Main.storyboard deleted file mode 100755 index 66b78f6..0000000 --- a/Example/MuslimData/Base.lproj/Main.storyboard +++ /dev/null @@ -1,452 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/MuslimData/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/MuslimData/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100755 index 7006c9e..0000000 --- a/Example/MuslimData/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/Example/MuslimData/Images.xcassets/Contents.json b/Example/MuslimData/Images.xcassets/Contents.json deleted file mode 100755 index da4a164..0000000 --- a/Example/MuslimData/Images.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Example/MuslimData/Info.plist b/Example/MuslimData/Info.plist deleted file mode 100755 index aab61f4..0000000 --- a/Example/MuslimData/Info.plist +++ /dev/null @@ -1,41 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - - UIUserInterfaceStyle - Light - - diff --git a/Example/MuslimData/LocationViewController.swift b/Example/MuslimData/LocationViewController.swift deleted file mode 100755 index 80b7b60..0000000 --- a/Example/MuslimData/LocationViewController.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// LocationViewController.swift -// MuslimData_Example -// -// Created by Kosrat D. Ahmad on 10/30/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -import MuslimData -import UIKit - -class LocationViewController: UIViewController { - // MARK: - Outlets - - @IBOutlet var locationTitle: UILabel! - @IBOutlet var locationTable: UITableView! - - var locations: [Location] = [] { - didSet { - locationTable.reloadData() - } - } - - // MARK: - View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - if #available(iOS 11.0, *) { - navigationItem.searchController = searchBar() - navigationItem.searchController?.searchBar.placeholder = "Search locations" - } - - locationTable.delegate = self - locationTable.dataSource = self - - displayLocation() - } - - // MARK: - Helper Methods - - /// Create search controller. - /// - /// - Returns: UISearchController - func searchBar() -> UISearchController { - let searchController = UISearchController(searchResultsController: nil) - searchController.searchBar.delegate = self - return searchController - } - - /// Display selected location on the screen. - func displayLocation() { - let location = Location.loadSavedLocation() - locationTitle.text = "\(location.name), \(location.countryName)" - } - - /// Dismiss and return to the parent screen. - /// - /// - Parameter sender: Any - @IBAction func done(_ sender: Any) { - dismiss(animated: true, completion: nil) - } -} - -// MARK: - UISearchBarDelegate - -extension LocationViewController: UISearchBarDelegate { - func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { - if searchText == "" { - locations.removeAll() - return - } - Task.init { - let locations = try! await MuslimRepository().searchLocation(locationName: searchBar.text!) - if let locations = locations { - self.locations = locations - } - } - } -} - -// MARK: - UITableViewDelegate - -extension LocationViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let location = locations[indexPath.row] - - location.saveLocation() - displayLocation() - if #available(iOS 11.0, *) { - navigationItem.searchController?.isActive = false - } - } -} - -// MARK: - UITableViewDataSource - -extension LocationViewController: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - locations.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath) - let location = locations[indexPath.row] - cell.textLabel?.text = location.name - cell.detailTextLabel?.text = location.countryName - - return cell - } -} diff --git a/Example/MuslimData/NamesViewController.swift b/Example/MuslimData/NamesViewController.swift deleted file mode 100755 index 480d8b8..0000000 --- a/Example/MuslimData/NamesViewController.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// NamesViewController.swift -// MuslimData_Example -// -// Created by Kosrat D. Ahmad on 11/2/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -import MuslimData -import UIKit - -class NamesViewController: UIViewController { - // MARK: - Outlets - - @IBOutlet var namesTable: UITableView! - - // MARK: - Properties - - var names: [Name] = [] - - // MARK: - View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - namesTable.dataSource = self - - Task.init { - let names = try! await MuslimRepository().getNamesOfAllah(language: .en) - self.names = names! - self.namesTable.reloadData() - } - } -} - -// MARK: - UITableViewDataSource - -extension NamesViewController: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - names.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) - let name = names[indexPath.row] - cell.textLabel?.text = name.name - cell.detailTextLabel?.text = name.translated - - return cell - } -} diff --git a/Example/MuslimData/PrayersViewController.swift b/Example/MuslimData/PrayersViewController.swift deleted file mode 100755 index 0e31a44..0000000 --- a/Example/MuslimData/PrayersViewController.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// PrayersViewController.swift -// MuslimData_Example -// -// Created by Kosrat D. Ahmad on 11/2/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -import MuslimData -import UIKit - -class PrayersViewController: UIViewController { - // MARK: - Outlets - - @IBOutlet var locationDetail: UILabel! - @IBOutlet var prayerTable: UITableView! - - // MARK: - Properties - - var prayerTimes: [String] = [] - let prayerNames = ["Fajr", "Sunrise", "Dhuhr", "Asr", "Maghrib", "Isha"] - - // MARK: - View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - prayerTable.dataSource = self - prayerTable.tableFooterView = UIView() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - // Get prayer times - Task.init { - try! await getPrayers() - } - } - - // MARK: - Helper Methods - - /// Get prayer times from the MuslimData library - func getPrayers() async throws { - let offsets = [Double](repeating: 0, count: 6) - let location = Location.loadSavedLocation() - let attributes = PrayerAttribute(method: .makkah, asrMethod: .shafii, adjustAngle: .angleBased, offsets: offsets) - - let prayer = try await MuslimRepository().getPrayerTimes(location: location, date: Date(), attributes: attributes) - prayerTimes = prayer!.formatPrayers(.time12) - prayerTable.reloadData() - - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd MMM yyyy" - let stringDate = dateFormatter.string(from: Date()) - - locationDetail.text = "\(location.name), \(location.countryName)\n\(stringDate)" - } -} - -// MARK: - UITableViewDataSource - -extension PrayersViewController: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - prayerTimes.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "prayerCell", for: indexPath) - cell.textLabel?.text = prayerNames[indexPath.row] - cell.detailTextLabel?.text = prayerTimes[indexPath.row] - - return cell - } -} diff --git a/Example/MuslimDataExample.xcodeproj/project.pbxproj b/Example/MuslimDataExample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..691f396 --- /dev/null +++ b/Example/MuslimDataExample.xcodeproj/project.pbxproj @@ -0,0 +1,491 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 2D13C26A2BB1E582007081CA /* PrayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D13C2692BB1E582007081CA /* PrayerViewModel.swift */; }; + 2D13C26C2BB1E9C9007081CA /* LocationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D13C26B2BB1E9C9007081CA /* LocationExtensions.swift */; }; + 2D13C26E2BB1F1F9007081CA /* PrayerRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D13C26D2BB1F1F9007081CA /* PrayerRowView.swift */; }; + 2D13C2742BB20AA6007081CA /* LocationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D13C2732BB20AA6007081CA /* LocationViewModel.swift */; }; + 2D13C2762BB20ABA007081CA /* LocationRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D13C2752BB20ABA007081CA /* LocationRowView.swift */; }; + 2D13C2782BB20AC9007081CA /* LocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D13C2772BB20AC9007081CA /* LocationScreen.swift */; }; + 2D9B68CA2BB190E30029E4D0 /* ChapterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68C92BB190E30029E4D0 /* ChapterViewModel.swift */; }; + 2D9B68CD2BB191270029E4D0 /* ChapterRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68CC2BB191270029E4D0 /* ChapterRow.swift */; }; + 2D9B68CF2BB1913A0029E4D0 /* AzkarChaptersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68CE2BB1913A0029E4D0 /* AzkarChaptersScreen.swift */; }; + 2D9B68D22BB195FF0029E4D0 /* AzkarItemsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68D12BB195FF0029E4D0 /* AzkarItemsScreen.swift */; }; + 2D9B68D42BB19AFB0029E4D0 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68D32BB19AFB0029E4D0 /* ItemViewModel.swift */; }; + 2D9B68D62BB19B0F0029E4D0 /* ItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68D52BB19B0F0029E4D0 /* ItemRow.swift */; }; + 2D9B68D92BB1A9410029E4D0 /* PrayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9B68D82BB1A9410029E4D0 /* PrayerScreen.swift */; }; + 6A22480D2BAED7840094967B /* NamesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A22480C2BAED7840094967B /* NamesScreen.swift */; }; + 6A22480F2BAED7960094967B /* NamesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A22480E2BAED7960094967B /* NamesRowView.swift */; }; + 6A2248112BAEDAEE0094967B /* NamesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2248102BAEDAED0094967B /* NamesViewModel.swift */; }; + 6A2248152BAEDB4E0094967B /* MuslimData in Frameworks */ = {isa = PBXBuildFile; productRef = 6A2248142BAEDB4E0094967B /* MuslimData */; }; + 6A787B1C2BAECF6F00DA0107 /* MuslimDataExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A787B1B2BAECF6F00DA0107 /* MuslimDataExampleApp.swift */; }; + 6A787B1E2BAECF6F00DA0107 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A787B1D2BAECF6F00DA0107 /* ContentView.swift */; }; + 6A787B202BAECF7200DA0107 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A787B1F2BAECF7200DA0107 /* Assets.xcassets */; }; + 6A787B232BAECF7200DA0107 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A787B222BAECF7200DA0107 /* Preview Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2D13C2692BB1E582007081CA /* PrayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrayerViewModel.swift; sourceTree = ""; }; + 2D13C26B2BB1E9C9007081CA /* LocationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationExtensions.swift; sourceTree = ""; }; + 2D13C26D2BB1F1F9007081CA /* PrayerRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrayerRowView.swift; sourceTree = ""; }; + 2D13C2732BB20AA6007081CA /* LocationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewModel.swift; sourceTree = ""; }; + 2D13C2752BB20ABA007081CA /* LocationRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRowView.swift; sourceTree = ""; }; + 2D13C2772BB20AC9007081CA /* LocationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationScreen.swift; sourceTree = ""; }; + 2D9B68C92BB190E30029E4D0 /* ChapterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterViewModel.swift; sourceTree = ""; }; + 2D9B68CC2BB191270029E4D0 /* ChapterRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterRow.swift; sourceTree = ""; }; + 2D9B68CE2BB1913A0029E4D0 /* AzkarChaptersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzkarChaptersScreen.swift; sourceTree = ""; }; + 2D9B68D12BB195FF0029E4D0 /* AzkarItemsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzkarItemsScreen.swift; sourceTree = ""; }; + 2D9B68D32BB19AFB0029E4D0 /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = ""; }; + 2D9B68D52BB19B0F0029E4D0 /* ItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRow.swift; sourceTree = ""; }; + 2D9B68D82BB1A9410029E4D0 /* PrayerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrayerScreen.swift; sourceTree = ""; }; + 6A22480C2BAED7840094967B /* NamesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamesScreen.swift; sourceTree = ""; }; + 6A22480E2BAED7960094967B /* NamesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamesRowView.swift; sourceTree = ""; }; + 6A2248102BAEDAED0094967B /* NamesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamesViewModel.swift; sourceTree = ""; }; + 6A2248132BAEDB050094967B /* muslim-data-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "muslim-data-ios"; path = ..; sourceTree = ""; }; + 6A787B182BAECF6F00DA0107 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A787B1B2BAECF6F00DA0107 /* MuslimDataExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuslimDataExampleApp.swift; sourceTree = ""; }; + 6A787B1D2BAECF6F00DA0107 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 6A787B1F2BAECF7200DA0107 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6A787B222BAECF7200DA0107 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6A787B152BAECF6F00DA0107 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A2248152BAEDB4E0094967B /* MuslimData in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2D13C2712BB204DC007081CA /* Extensions */ = { + isa = PBXGroup; + children = ( + 2D13C26B2BB1E9C9007081CA /* LocationExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 2D13C2722BB20A82007081CA /* Location */ = { + isa = PBXGroup; + children = ( + 2D13C2772BB20AC9007081CA /* LocationScreen.swift */, + 2D13C2752BB20ABA007081CA /* LocationRowView.swift */, + 2D13C2732BB20AA6007081CA /* LocationViewModel.swift */, + ); + path = Location; + sourceTree = ""; + }; + 2D9B68CB2BB190F30029E4D0 /* Azkars */ = { + isa = PBXGroup; + children = ( + 2D9B68D72BB1A3600029E4D0 /* AzkarChapters */, + 2D9B68D02BB191870029E4D0 /* AzkarItems */, + ); + path = Azkars; + sourceTree = ""; + }; + 2D9B68D02BB191870029E4D0 /* AzkarItems */ = { + isa = PBXGroup; + children = ( + 2D9B68D12BB195FF0029E4D0 /* AzkarItemsScreen.swift */, + 2D9B68D52BB19B0F0029E4D0 /* ItemRow.swift */, + 2D9B68D32BB19AFB0029E4D0 /* ItemViewModel.swift */, + ); + path = AzkarItems; + sourceTree = ""; + }; + 2D9B68D72BB1A3600029E4D0 /* AzkarChapters */ = { + isa = PBXGroup; + children = ( + 2D9B68CE2BB1913A0029E4D0 /* AzkarChaptersScreen.swift */, + 2D9B68CC2BB191270029E4D0 /* ChapterRow.swift */, + 2D9B68C92BB190E30029E4D0 /* ChapterViewModel.swift */, + ); + path = AzkarChapters; + sourceTree = ""; + }; + 2D9B68DA2BB1A9490029E4D0 /* PrayerTimes */ = { + isa = PBXGroup; + children = ( + 2D9B68D82BB1A9410029E4D0 /* PrayerScreen.swift */, + 2D13C26D2BB1F1F9007081CA /* PrayerRowView.swift */, + 2D13C2692BB1E582007081CA /* PrayerViewModel.swift */, + ); + path = PrayerTimes; + sourceTree = ""; + }; + 6A22480B2BAED7730094967B /* Names */ = { + isa = PBXGroup; + children = ( + 6A22480C2BAED7840094967B /* NamesScreen.swift */, + 6A22480E2BAED7960094967B /* NamesRowView.swift */, + 6A2248102BAEDAED0094967B /* NamesViewModel.swift */, + ); + path = Names; + sourceTree = ""; + }; + 6A2248122BAEDB050094967B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6A2248132BAEDB050094967B /* muslim-data-ios */, + ); + name = Frameworks; + sourceTree = ""; + }; + 6A787B0F2BAECF6F00DA0107 = { + isa = PBXGroup; + children = ( + 6A787B1A2BAECF6F00DA0107 /* MuslimDataExample */, + 6A787B192BAECF6F00DA0107 /* Products */, + 6A2248122BAEDB050094967B /* Frameworks */, + ); + sourceTree = ""; + }; + 6A787B192BAECF6F00DA0107 /* Products */ = { + isa = PBXGroup; + children = ( + 6A787B182BAECF6F00DA0107 /* Example.app */, + ); + name = Products; + sourceTree = ""; + }; + 6A787B1A2BAECF6F00DA0107 /* MuslimDataExample */ = { + isa = PBXGroup; + children = ( + 2D9B68DA2BB1A9490029E4D0 /* PrayerTimes */, + 2D13C2722BB20A82007081CA /* Location */, + 6A22480B2BAED7730094967B /* Names */, + 2D9B68CB2BB190F30029E4D0 /* Azkars */, + 2D13C2712BB204DC007081CA /* Extensions */, + 6A787B1B2BAECF6F00DA0107 /* MuslimDataExampleApp.swift */, + 6A787B1D2BAECF6F00DA0107 /* ContentView.swift */, + 6A787B1F2BAECF7200DA0107 /* Assets.xcassets */, + 6A787B212BAECF7200DA0107 /* Preview Content */, + ); + path = MuslimDataExample; + sourceTree = ""; + }; + 6A787B212BAECF7200DA0107 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 6A787B222BAECF7200DA0107 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6A787B172BAECF6F00DA0107 /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6A787B262BAECF7200DA0107 /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + 6A787B142BAECF6F00DA0107 /* Sources */, + 6A787B152BAECF6F00DA0107 /* Frameworks */, + 6A787B162BAECF6F00DA0107 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Example; + packageProductDependencies = ( + 6A2248142BAEDB4E0094967B /* MuslimData */, + ); + productName = Example; + productReference = 6A787B182BAECF6F00DA0107 /* Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6A787B102BAECF6F00DA0107 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + 6A787B172BAECF6F00DA0107 = { + CreatedOnToolsVersion = 15.3; + }; + }; + }; + buildConfigurationList = 6A787B132BAECF6F00DA0107 /* Build configuration list for PBXProject "MuslimDataExample" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6A787B0F2BAECF6F00DA0107; + productRefGroup = 6A787B192BAECF6F00DA0107 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6A787B172BAECF6F00DA0107 /* Example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6A787B162BAECF6F00DA0107 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A787B232BAECF7200DA0107 /* Preview Assets.xcassets in Resources */, + 6A787B202BAECF7200DA0107 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6A787B142BAECF6F00DA0107 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D9B68CA2BB190E30029E4D0 /* ChapterViewModel.swift in Sources */, + 2D9B68D92BB1A9410029E4D0 /* PrayerScreen.swift in Sources */, + 2D9B68CF2BB1913A0029E4D0 /* AzkarChaptersScreen.swift in Sources */, + 2D9B68CD2BB191270029E4D0 /* ChapterRow.swift in Sources */, + 2D13C2762BB20ABA007081CA /* LocationRowView.swift in Sources */, + 2D9B68D42BB19AFB0029E4D0 /* ItemViewModel.swift in Sources */, + 2D13C2782BB20AC9007081CA /* LocationScreen.swift in Sources */, + 6A22480F2BAED7960094967B /* NamesRowView.swift in Sources */, + 6A22480D2BAED7840094967B /* NamesScreen.swift in Sources */, + 2D13C2742BB20AA6007081CA /* LocationViewModel.swift in Sources */, + 6A787B1E2BAECF6F00DA0107 /* ContentView.swift in Sources */, + 6A787B1C2BAECF6F00DA0107 /* MuslimDataExampleApp.swift in Sources */, + 2D9B68D62BB19B0F0029E4D0 /* ItemRow.swift in Sources */, + 2D13C26A2BB1E582007081CA /* PrayerViewModel.swift in Sources */, + 6A2248112BAEDAEE0094967B /* NamesViewModel.swift in Sources */, + 2D13C26C2BB1E9C9007081CA /* LocationExtensions.swift in Sources */, + 2D9B68D22BB195FF0029E4D0 /* AzkarItemsScreen.swift in Sources */, + 2D13C26E2BB1F1F9007081CA /* PrayerRowView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 6A787B242BAECF7200DA0107 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 6A787B252BAECF7200DA0107 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6A787B272BAECF7200DA0107 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"MuslimDataExample/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.muslim.data.example.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6A787B282BAECF7200DA0107 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"MuslimDataExample/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.muslim.data.example.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6A787B132BAECF6F00DA0107 /* Build configuration list for PBXProject "MuslimDataExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6A787B242BAECF7200DA0107 /* Debug */, + 6A787B252BAECF7200DA0107 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6A787B262BAECF7200DA0107 /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6A787B272BAECF7200DA0107 /* Debug */, + 6A787B282BAECF7200DA0107 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 6A2248142BAEDB4E0094967B /* MuslimData */ = { + isa = XCSwiftPackageProductDependency; + productName = MuslimData; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 6A787B102BAECF6F00DA0107 /* Project object */; +} diff --git a/Example/MuslimData.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/MuslimDataExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata old mode 100755 new mode 100644 similarity index 70% rename from Example/MuslimData.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Example/MuslimDataExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 4273183..919434a --- a/Example/MuslimData.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Example/MuslimDataExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Example/MuslimData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/MuslimDataExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Example/MuslimData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Example/MuslimDataExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Example/MuslimDataExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/MuslimDataExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..4a52a9c --- /dev/null +++ b/Example/MuslimDataExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "3b2a1b99aedb4ce24cdd643637ecbc2ac01b64f9fcc6462aa023925eb3dfc85c", + "pins" : [ + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/GRDB.swift.git", + "state" : { + "revision" : "77b85bed259b7f107710a0b78c439b1c5839dc45", + "version" : "6.26.0" + } + } + ], + "version" : 3 +} diff --git a/Example/MuslimDataExample.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/MuslimDataExample.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 0000000..7639a29 --- /dev/null +++ b/Example/MuslimDataExample.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/MuslimDataExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/MuslimDataExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Example/MuslimDataExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/MuslimDataExample/Assets.xcassets/Contents.json b/Example/MuslimDataExample/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/MuslimDataExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/MuslimData/Images.xcassets/ic_nav_location.imageset/Contents.json b/Example/MuslimDataExample/Assets.xcassets/ic_location.imageset/Contents.json old mode 100755 new mode 100644 similarity index 50% rename from Example/MuslimData/Images.xcassets/ic_nav_location.imageset/Contents.json rename to Example/MuslimDataExample/Assets.xcassets/ic_location.imageset/Contents.json index 17689aa..e5bd772 --- a/Example/MuslimData/Images.xcassets/ic_nav_location.imageset/Contents.json +++ b/Example/MuslimDataExample/Assets.xcassets/ic_location.imageset/Contents.json @@ -1,15 +1,15 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_settings_location.pdf" + "filename" : "ic_settings_location.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "preserves-vector-representation" : true } -} \ No newline at end of file +} diff --git a/Example/MuslimData/Images.xcassets/ic_nav_location.imageset/ic_settings_location.pdf b/Example/MuslimDataExample/Assets.xcassets/ic_location.imageset/ic_settings_location.pdf old mode 100755 new mode 100644 similarity index 100% rename from Example/MuslimData/Images.xcassets/ic_nav_location.imageset/ic_settings_location.pdf rename to Example/MuslimDataExample/Assets.xcassets/ic_location.imageset/ic_settings_location.pdf diff --git a/Example/MuslimData/Images.xcassets/ic_nav_azkars.imageset/Contents.json b/Example/MuslimDataExample/Assets.xcassets/ic_nav_azkars.imageset/Contents.json old mode 100755 new mode 100644 similarity index 51% rename from Example/MuslimData/Images.xcassets/ic_nav_azkars.imageset/Contents.json rename to Example/MuslimDataExample/Assets.xcassets/ic_nav_azkars.imageset/Contents.json index c307315..5d9a289 --- a/Example/MuslimData/Images.xcassets/ic_nav_azkars.imageset/Contents.json +++ b/Example/MuslimDataExample/Assets.xcassets/ic_nav_azkars.imageset/Contents.json @@ -1,15 +1,15 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_nav_azkars.pdf" + "filename" : "ic_nav_azkars.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "preserves-vector-representation" : true } -} \ No newline at end of file +} diff --git a/Example/MuslimData/Images.xcassets/ic_nav_azkars.imageset/ic_nav_azkars.pdf b/Example/MuslimDataExample/Assets.xcassets/ic_nav_azkars.imageset/ic_nav_azkars.pdf old mode 100755 new mode 100644 similarity index 100% rename from Example/MuslimData/Images.xcassets/ic_nav_azkars.imageset/ic_nav_azkars.pdf rename to Example/MuslimDataExample/Assets.xcassets/ic_nav_azkars.imageset/ic_nav_azkars.pdf diff --git a/Example/MuslimDataExample/Assets.xcassets/ic_nav_location.imageset/Contents.json b/Example/MuslimDataExample/Assets.xcassets/ic_nav_location.imageset/Contents.json new file mode 100644 index 0000000..e5bd772 --- /dev/null +++ b/Example/MuslimDataExample/Assets.xcassets/ic_nav_location.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_settings_location.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Example/MuslimDataExample/Assets.xcassets/ic_nav_location.imageset/ic_settings_location.pdf b/Example/MuslimDataExample/Assets.xcassets/ic_nav_location.imageset/ic_settings_location.pdf new file mode 100644 index 0000000..0861274 Binary files /dev/null and b/Example/MuslimDataExample/Assets.xcassets/ic_nav_location.imageset/ic_settings_location.pdf differ diff --git a/Example/MuslimData/Images.xcassets/ic_nav_names.imageset/Contents.json b/Example/MuslimDataExample/Assets.xcassets/ic_nav_names.imageset/Contents.json old mode 100755 new mode 100644 similarity index 51% rename from Example/MuslimData/Images.xcassets/ic_nav_names.imageset/Contents.json rename to Example/MuslimDataExample/Assets.xcassets/ic_nav_names.imageset/Contents.json index d164f23..6f6ce09 --- a/Example/MuslimData/Images.xcassets/ic_nav_names.imageset/Contents.json +++ b/Example/MuslimDataExample/Assets.xcassets/ic_nav_names.imageset/Contents.json @@ -1,15 +1,15 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_nav_names.pdf" + "filename" : "ic_nav_names.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "preserves-vector-representation" : true } -} \ No newline at end of file +} diff --git a/Example/MuslimData/Images.xcassets/ic_nav_names.imageset/ic_nav_names.pdf b/Example/MuslimDataExample/Assets.xcassets/ic_nav_names.imageset/ic_nav_names.pdf old mode 100755 new mode 100644 similarity index 100% rename from Example/MuslimData/Images.xcassets/ic_nav_names.imageset/ic_nav_names.pdf rename to Example/MuslimDataExample/Assets.xcassets/ic_nav_names.imageset/ic_nav_names.pdf diff --git a/Example/MuslimData/Images.xcassets/ic_nav_prayers.imageset/Contents.json b/Example/MuslimDataExample/Assets.xcassets/ic_nav_prayers.imageset/Contents.json old mode 100755 new mode 100644 similarity index 50% rename from Example/MuslimData/Images.xcassets/ic_nav_prayers.imageset/Contents.json rename to Example/MuslimDataExample/Assets.xcassets/ic_nav_prayers.imageset/Contents.json index fef4bea..d72bae5 --- a/Example/MuslimData/Images.xcassets/ic_nav_prayers.imageset/Contents.json +++ b/Example/MuslimDataExample/Assets.xcassets/ic_nav_prayers.imageset/Contents.json @@ -1,15 +1,15 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_nav_my_prayers.pdf" + "filename" : "ic_nav_my_prayers.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "preserves-vector-representation" : true } -} \ No newline at end of file +} diff --git a/Example/MuslimData/Images.xcassets/ic_nav_prayers.imageset/ic_nav_my_prayers.pdf b/Example/MuslimDataExample/Assets.xcassets/ic_nav_prayers.imageset/ic_nav_my_prayers.pdf old mode 100755 new mode 100644 similarity index 100% rename from Example/MuslimData/Images.xcassets/ic_nav_prayers.imageset/ic_nav_my_prayers.pdf rename to Example/MuslimDataExample/Assets.xcassets/ic_nav_prayers.imageset/ic_nav_my_prayers.pdf diff --git a/Example/MuslimDataExample/Azkars/AzkarChapters/AzkarChaptersScreen.swift b/Example/MuslimDataExample/Azkars/AzkarChapters/AzkarChaptersScreen.swift new file mode 100644 index 0000000..c75e276 --- /dev/null +++ b/Example/MuslimDataExample/Azkars/AzkarChapters/AzkarChaptersScreen.swift @@ -0,0 +1,35 @@ +// +// AzkarsScreen.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI + +/// Display the azkar chapter list. +struct AzkarChaptersScreen: View { + private var azkarViewModel = ChapterViewModel() + + var body: some View { + NavigationSplitView { + List { + ForEach(azkarViewModel.azkarChapters, id: \.id) { chapter in + NavigationLink { + AzkarItemsScreen(chapterId: chapter.id) + } label: { + ChapterRow(azkarChapter: chapter) + .frame(height: 36) + } + } + } + .navigationTitle("Azkars") + } detail: { + Text("Select an Azkar") + } + } +} + +#Preview { + AzkarChaptersScreen() +} diff --git a/Example/MuslimDataExample/Azkars/AzkarChapters/ChapterRow.swift b/Example/MuslimDataExample/Azkars/AzkarChapters/ChapterRow.swift new file mode 100644 index 0000000..9b2c37b --- /dev/null +++ b/Example/MuslimDataExample/Azkars/AzkarChapters/ChapterRow.swift @@ -0,0 +1,28 @@ +// +// AzkarRow.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI +import MuslimData + +/// Displays a single row that is representing an azkar chapter. +/// +/// - Parameters: +/// - azkarChapter: the `AzkarChapter` instance that needs to be displayed in the row. +struct ChapterRow: View { + var azkarChapter: AzkarChapter + + var body: some View { + VStack(alignment: .leading) { + Text(azkarChapter.name) + .font(.body) + } + } +} + +#Preview { + ChapterRow(azkarChapter: AzkarChapter(id: 1, categoryId: 1, name: "Morning Azkars")) +} diff --git a/Example/MuslimDataExample/Azkars/AzkarChapters/ChapterViewModel.swift b/Example/MuslimDataExample/Azkars/AzkarChapters/ChapterViewModel.swift new file mode 100644 index 0000000..38c33e3 --- /dev/null +++ b/Example/MuslimDataExample/Azkars/AzkarChapters/ChapterViewModel.swift @@ -0,0 +1,30 @@ +// +// AzkarViewModel.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import Foundation +import MuslimData + +@Observable +/// Manages the state for the azkar chapters list and responsible for fetching data. +class ChapterViewModel { + private(set) var azkarChapters: [AzkarChapter] = [] + + init() { + getAzkarChapters() + } + + /// Fetches the azkar chapters from the `MuslimRepository`. + private func getAzkarChapters() { + Task { + do { + azkarChapters = try await MuslimRepository().getAzkarChapters(language: .en) ?? [] + } catch { + print("Error loading azkars: \(error.localizedDescription)") + } + } + } +} diff --git a/Example/MuslimDataExample/Azkars/AzkarItems/AzkarItemsScreen.swift b/Example/MuslimDataExample/Azkars/AzkarItems/AzkarItemsScreen.swift new file mode 100644 index 0000000..bfa5600 --- /dev/null +++ b/Example/MuslimDataExample/Azkars/AzkarItems/AzkarItemsScreen.swift @@ -0,0 +1,31 @@ +// +// AzkarDetailScreen.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI + +/// Display the detail of the selected azkar chapter. +struct AzkarItemsScreen: View { + var itemsViewModel: ItemViewModel + + init(chapterId: Int) { + itemsViewModel = ItemViewModel(chapterId: chapterId) + } + + var body: some View { + List { + ForEach(itemsViewModel.azkarItems, id: \.id) { item in + ItemRow(azkarItem: item) + } + } + .listRowSpacing(8) + .navigationTitle("Azkar Details") + } +} + +#Preview { + AzkarItemsScreen(chapterId: 1) +} diff --git a/Example/MuslimDataExample/Azkars/AzkarItems/ItemRow.swift b/Example/MuslimDataExample/Azkars/AzkarItems/ItemRow.swift new file mode 100644 index 0000000..fa80249 --- /dev/null +++ b/Example/MuslimDataExample/Azkars/AzkarItems/ItemRow.swift @@ -0,0 +1,41 @@ +// +// ItemRow.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI +import MuslimData + +/// Displays a single row that is representing an Azkar Item. Includes the Arabic azkar, its tranlation, +/// and the azkar reference. +/// +/// - Parameters: +/// - azkarItem: the `AzkarItem` instance that needs to be displayed in the row. +struct ItemRow: View { + var azkarItem: AzkarItem + + var body: some View { + VStack(alignment: .leading) { + Text(azkarItem.item) + .font(.headline) + .padding(.bottom, 4) + Text(azkarItem.translation) + .font(.body) + .padding(.bottom, 8) + Text(azkarItem.reference) + .font(.caption) + } + } +} + +#Preview { + ItemRow(azkarItem: AzkarItem( + id: 1, + chapterId: 1, + item: "Azkar detail in Arabic language", + translation: "translated azkar into user's language", + reference: "Azkar references") + ) +} diff --git a/Example/MuslimDataExample/Azkars/AzkarItems/ItemViewModel.swift b/Example/MuslimDataExample/Azkars/AzkarItems/ItemViewModel.swift new file mode 100644 index 0000000..a063af6 --- /dev/null +++ b/Example/MuslimDataExample/Azkars/AzkarItems/ItemViewModel.swift @@ -0,0 +1,32 @@ +// +// ItemViewModel.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import Foundation +import MuslimData + +@Observable +/// Manages the state for the azkar item list and responsible for fetching data. +class ItemViewModel { + private(set) var azkarItems: [AzkarItem] = [] + let chapterId: Int + + init(chapterId: Int) { + self.chapterId = chapterId + getAzkarItems() + } + + /// Fetches the azkar items from the `MuslimRepository`. + private func getAzkarItems() { + Task { + do { + azkarItems = try await MuslimRepository().getAzkarItems(chapterId: chapterId, language: .en) ?? [] + } catch { + print("Error getting azkar items: \(error.localizedDescription)") + } + } + } +} diff --git a/Example/MuslimDataExample/ContentView.swift b/Example/MuslimDataExample/ContentView.swift new file mode 100644 index 0000000..e16a679 --- /dev/null +++ b/Example/MuslimDataExample/ContentView.swift @@ -0,0 +1,44 @@ +// +// +// ContentView.swift +// Example +// +// Created by Muhammad Azad on 23/03/2024 +// Copyright © 2024 MuslimData. All rights reserved. +// + + +import SwiftUI + +struct ContentView: View { + @State private var selection: AppTabs = .prayerTime + enum AppTabs { + case prayerTime + case names + case azkar + } + + var body: some View { + TabView(selection: $selection) { + PrayerScreen() + .tabItem { + Image("ic_nav_prayers") + Text("Prayer Times") + }.tag(AppTabs.prayerTime) + NamesScreen() + .tabItem { + Image("ic_nav_names") + Text("Names") + }.tag(AppTabs.names) + AzkarChaptersScreen() + .tabItem { + Image("ic_nav_azkars") + Text("Azkar") + }.tag(AppTabs.azkar) + } + } +} + +#Preview { + ContentView() +} diff --git a/Example/MuslimData/Location.swift b/Example/MuslimDataExample/Extensions/LocationExtensions.swift old mode 100755 new mode 100644 similarity index 91% rename from Example/MuslimData/Location.swift rename to Example/MuslimDataExample/Extensions/LocationExtensions.swift index c2e8c51..42e4170 --- a/Example/MuslimData/Location.swift +++ b/Example/MuslimDataExample/Extensions/LocationExtensions.swift @@ -1,16 +1,13 @@ // -// Location.swift -// MuslimData_Example +// LocatoinExtensions.swift +// Example // -// Created by Kosrat D. Ahmad on 11/1/18. -// Copyright © 2018 CocoaPods. All rights reserved. +// Created by Kosrat Ahmed on 25/03/2024. // import Foundation import MuslimData -// MARK: - Location Extensions - extension Location { /// Save location in the User Defaults. func saveLocation() { diff --git a/Example/MuslimDataExample/Location/LocationRowView.swift b/Example/MuslimDataExample/Location/LocationRowView.swift new file mode 100644 index 0000000..f9bdd68 --- /dev/null +++ b/Example/MuslimDataExample/Location/LocationRowView.swift @@ -0,0 +1,46 @@ +// +// LocationRowView.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI +import MuslimData + +/// Displays a single row of the prayer times. Includes the prayer name and time. +/// +/// - Parameters: +/// - location: The location instance. +/// - selectedLocation: The selected location instance. +struct LocationRowView: View { + var location: Location + @Binding var selectedLocation: Location + + var body: some View { + HStack { + VStack(alignment: .leading) { + Text(location.name) + .font(.headline) + Text(location.countryName) + } + + Spacer() + + if selectedLocation.id == location.id { + Image(systemName: "checkmark") + .foregroundColor(.accentColor) + } + } + .contentShape(Rectangle()) + .onTapGesture { + self.selectedLocation = location + } + } +} + +#Preview { + LocationRowView(location: Location.loadSavedLocation(), + selectedLocation: .constant(Location.loadSavedLocation()) + ) +} diff --git a/Example/MuslimDataExample/Location/LocationScreen.swift b/Example/MuslimDataExample/Location/LocationScreen.swift new file mode 100644 index 0000000..89ebc99 --- /dev/null +++ b/Example/MuslimDataExample/Location/LocationScreen.swift @@ -0,0 +1,64 @@ +// +// LocationScreen.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI +import MuslimData + +/// Location screen that displays the selected location and searching for locations. +struct LocationScreen: View { + @Environment(\.dismiss) private var dismiss + + var locationViewModel = LocationViewModel() + var prayerViewModel: PrayerViewModel + + @State var searchKeyword: String = "" + @State var selectedLocation: Location = Location.loadSavedLocation() + + var body: some View { + List { + Section(header: Text("Selected location")) { + VStack(alignment: .leading){ + Text(selectedLocation.name) + .font(.headline) + Text(selectedLocation.countryName) + } + } + + if locationViewModel.locations.count > 0 { + Section(header: Text("Search Result")) { + ForEach(locationViewModel.locations, id: \.id) { location in + LocationRowView(location: location, selectedLocation: $selectedLocation) + } + } + } + } + .listSectionSpacing(8) + .navigationTitle("Locations") + .searchable(text: $searchKeyword, + placement: .navigationBarDrawer(displayMode: .always)) + .onChange(of: searchKeyword) { oldValue, newValue in + if !newValue.isEmpty && newValue.count > 2 { + locationViewModel.searchResult(searchKeyword: newValue) + } else { + locationViewModel.locations.removeAll() + } + } + .toolbar { + Button { + selectedLocation.saveLocation() + prayerViewModel.updatePrayerTimes() + dismiss() + } label: { + Text("Save") + } + } + } +} + +#Preview { + LocationScreen(prayerViewModel: PrayerViewModel()) +} diff --git a/Example/MuslimDataExample/Location/LocationViewModel.swift b/Example/MuslimDataExample/Location/LocationViewModel.swift new file mode 100644 index 0000000..acec437 --- /dev/null +++ b/Example/MuslimDataExample/Location/LocationViewModel.swift @@ -0,0 +1,27 @@ +// +// LocationViewModel.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import Foundation +import MuslimData + +@Observable +/// Manages the state for the location list and responsible for searching a location in the MuslimData. +class LocationViewModel { + var locations: [Location] = [] + + /// Search for location in the `MuslimRepository`. + /// - Parameter searchKeyword: Location search keyword. + func searchResult(searchKeyword: String) { + Task { + do { + locations = try await MuslimRepository().searchLocation(locationName: searchKeyword) ?? [] + } catch { + print("Error searching for locations: \(error.localizedDescription)") + } + } + } +} diff --git a/Example/MuslimDataExample/MuslimDataExampleApp.swift b/Example/MuslimDataExample/MuslimDataExampleApp.swift new file mode 100644 index 0000000..db0c6f3 --- /dev/null +++ b/Example/MuslimDataExample/MuslimDataExampleApp.swift @@ -0,0 +1,20 @@ +// +// +// MuslimDataExampleApp.swift +// MuslimDataExample +// +// Created by Muhammad Azad on 23/03/2024 +// Copyright © 2024 MuslimData. All rights reserved. +// + + +import SwiftUI + +@main +struct MuslimDataExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Example/MuslimDataExample/Names/NamesRowView.swift b/Example/MuslimDataExample/Names/NamesRowView.swift new file mode 100644 index 0000000..e081fb5 --- /dev/null +++ b/Example/MuslimDataExample/Names/NamesRowView.swift @@ -0,0 +1,34 @@ +// +// +// NamesRowView.swift +// Example +// +// Created by Muhammad Azad on 23/03/2024 +// Copyright © 2024 MuslimData. All rights reserved. +// + + +import SwiftUI +import MuslimData + +/// Displays a single row which is representing a Name of Allah. Includes the Arabic name +/// and its translation. +/// +/// - Parameters: +/// - name: The `Name` instance that needs to be displayed in the row. +struct NamesRowView: View { + let name: Name + + var body: some View { + VStack(alignment: .leading){ + Text(name.name) + .font(.body) + Text(name.translated) + .font(.caption) + } + } +} + +#Preview { + NamesRowView(name: .init(name: "اللە", translated: "Allah")) +} diff --git a/Example/MuslimDataExample/Names/NamesScreen.swift b/Example/MuslimDataExample/Names/NamesScreen.swift new file mode 100644 index 0000000..ff8a701 --- /dev/null +++ b/Example/MuslimDataExample/Names/NamesScreen.swift @@ -0,0 +1,31 @@ +// +// +// NamesScreen.swift +// Example +// +// Created by Muhammad Azad on 23/03/2024 +// Copyright © 2024 MuslimData. All rights reserved. +// + + +import SwiftUI + +/// Displays a list of the Names of Allah. +struct NamesScreen: View { + private var viewModel = NamesViewModel() + + var body: some View { + NavigationStack{ + List { + ForEach(viewModel.names, id: \.name) { name in + NamesRowView(name: name) + } + } + .navigationTitle("Names of Allah") + } + } +} + +#Preview { + NamesScreen() +} diff --git a/Example/MuslimDataExample/Names/NamesViewModel.swift b/Example/MuslimDataExample/Names/NamesViewModel.swift new file mode 100644 index 0000000..dc074b2 --- /dev/null +++ b/Example/MuslimDataExample/Names/NamesViewModel.swift @@ -0,0 +1,34 @@ +// +// +// NamesViewModel.swift +// Example +// +// Created by Muhammad Azad on 23/03/2024 +// Copyright © 2024 MuslimData. All rights reserved. +// + + +import Foundation +import MuslimData + +@Observable +/// Manages the state for a list of Names of Allah and responsible for fetching data. +class NamesViewModel { + private(set) var names: [Name] = [] + + init() { + getNamesOfAllah() + } + + /// Fetches the Names of Allah from the `MuslimRepository`. + func getNamesOfAllah() { + Task { + do { + names = try await MuslimRepository().getNamesOfAllah(language: .en) ?? [] + } catch { + print("Error getting names: \(error.localizedDescription)") + } + } + } +} + diff --git a/Example/MuslimDataExample/PrayerTimes/PrayerRowView.swift b/Example/MuslimDataExample/PrayerTimes/PrayerRowView.swift new file mode 100644 index 0000000..605cfd0 --- /dev/null +++ b/Example/MuslimDataExample/PrayerTimes/PrayerRowView.swift @@ -0,0 +1,31 @@ +// +// PrayerRow.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI +import MuslimData + +/// Displays a single row of the prayer times. Includes the prayer name and time. +/// +/// - Parameters: +/// - prayerName: The prayer name string. +/// - prayerTime: The formatted prayer time string. +struct PrayerRowView: View { + let prayerName: String + let prayerTime: String + + var body: some View { + HStack { + Text(prayerName) + Spacer() + Text(prayerTime) + } + } +} + +#Preview { + PrayerRowView(prayerName: "Fajr", prayerTime: "04:55 AM") +} diff --git a/Example/MuslimDataExample/PrayerTimes/PrayerScreen.swift b/Example/MuslimDataExample/PrayerTimes/PrayerScreen.swift new file mode 100644 index 0000000..994b497 --- /dev/null +++ b/Example/MuslimDataExample/PrayerTimes/PrayerScreen.swift @@ -0,0 +1,53 @@ +// +// PrayerTimes.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import SwiftUI +import MuslimData + +/// Displays a list of the prayer times. +struct PrayerScreen: View { + var prayerViewModel = PrayerViewModel() + var prayerNames: [String] { + prayerViewModel.prayerNames + } + var location: Location { + prayerViewModel.location + } + var prayerTimes: [String] { + prayerViewModel.prayerTimes + } + + var body: some View { + NavigationStack { + List { + VStack(alignment: .leading) { + Text("\(location.name), \(location.countryCode)") + .font(.headline) + Text(prayerViewModel.todayString()) + }.padding(.bottom) + + if prayerTimes.count > 0 { + ForEach(Array(prayerNames.enumerated()), id: \.offset) { index, name in + PrayerRowView(prayerName: name, prayerTime: prayerTimes[index]) + } + } + } + .navigationTitle("Prayer Times") + .toolbar { + NavigationLink { + LocationScreen(prayerViewModel: prayerViewModel) + } label: { + Image("ic_location") + } + } + } + } +} + +#Preview { + PrayerScreen() +} diff --git a/Example/MuslimDataExample/PrayerTimes/PrayerViewModel.swift b/Example/MuslimDataExample/PrayerTimes/PrayerViewModel.swift new file mode 100644 index 0000000..403b0da --- /dev/null +++ b/Example/MuslimDataExample/PrayerTimes/PrayerViewModel.swift @@ -0,0 +1,50 @@ +// +// PrayerViewModel.swift +// Example +// +// Created by Kosrat Ahmed on 25/03/2024. +// + +import Foundation +import MuslimData + +@Observable +/// Manages the state for the prayer times and responsible for fetching data. +class PrayerViewModel { + var location: Location + private(set) var prayerTimes: [String] = [] + private(set) var prayerNames = ["Fajr", "Sunrise", "Dhuhr", "Asr", "Maghrib", "Isha"] + + init() { + location = Location.loadSavedLocation() + getPrayerTimes() + } + + /// Fetches the prayer times from the `MuslimRepository` and then assign it to the local variable. + private func getPrayerTimes() { + Task { + do { + let offsets: [Double] = [0, 0, 0, 0, 0, 0] + let prayerAttribute = PrayerAttribute(method: .makkah, asrMethod: .shafii, adjustAngle: .angleBased, offsets: offsets) + let prayer = try await MuslimRepository().getPrayerTimes(location: location, date: Date(), attributes: prayerAttribute) + prayerTimes = prayer?.formatPrayers(.time12) ?? [] + } catch { + print("Error Loading prayer times: \(error.localizedDescription)") + } + } + } + + /// Formate today's date as `dd MMM yyyy` and return it as string. + /// - Returns: Formatted today's date. + func todayString() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd MMM yyyy" + return dateFormatter.string(from: Date()) + } + + /// Update location and prayer times. + func updatePrayerTimes() { + location = Location.loadSavedLocation() + getPrayerTimes() + } +} diff --git a/Example/MuslimDataExample/Preview Content/Preview Assets.xcassets/Contents.json b/Example/MuslimDataExample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/MuslimDataExample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Podfile b/Example/Podfile deleted file mode 100755 index eccf6ea..0000000 --- a/Example/Podfile +++ /dev/null @@ -1,12 +0,0 @@ -use_frameworks! -inhibit_all_warnings! - -target 'MuslimData_Example' do - pod 'MuslimData', :path => '../' - - target 'MuslimData_Tests' do - inherit! :search_paths - - - end -end diff --git a/Example/Podfile.lock b/Example/Podfile.lock deleted file mode 100755 index 853bebd..0000000 --- a/Example/Podfile.lock +++ /dev/null @@ -1,25 +0,0 @@ -PODS: - - GRDB.swift (4.0.1): - - GRDB.swift/standard (= 4.0.1) - - GRDB.swift/standard (4.0.1) - - MuslimData (1.0.0-beta.8): - - GRDB.swift - -DEPENDENCIES: - - MuslimData (from `../`) - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - GRDB.swift - -EXTERNAL SOURCES: - MuslimData: - :path: "../" - -SPEC CHECKSUMS: - GRDB.swift: 106a830decf1d92a3fc63c6d6a2f6586f6187297 - MuslimData: 85ec0559a561eb03188b3e5cbb33dbbb36b91c8d - -PODFILE CHECKSUM: f1a8413ff73c18d27d5283425e84b7905893b20c - -COCOAPODS: 1.6.0 diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Configuration.swift b/Example/Pods/GRDB.swift/GRDB/Core/Configuration.swift deleted file mode 100755 index 0cb8a17..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Configuration.swift +++ /dev/null @@ -1,203 +0,0 @@ -import Foundation -import Dispatch -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// Configuration for a DatabaseQueue or DatabasePool. -public struct Configuration { - - // MARK: - Misc options - - /// If true, foreign key constraints are checked. - /// - /// Default: true - public var foreignKeysEnabled: Bool = true - - /// If true, database modifications are disallowed. - /// - /// Default: false - public var readonly: Bool = false - - /// The database label. - /// - /// You can query this label at runtime: - /// - /// var configuration = Configuration() - /// configuration.label = "MyDatabase" - /// let dbQueue = try DatabaseQueue(path: ..., configuration: configuration) - /// - /// try dbQueue.read { db in - /// print(db.configuration.label) // Prints "MyDatabase" - /// } - /// - /// The database label is also used to name the various dispatch queues - /// created by GRDB, visible in debugging sessions and crash logs. However - /// those dispatch queue labels are intended for debugging only. Their - /// format may change between GRDB releases. Applications should not depend - /// on the GRDB dispatch queue labels. - /// - /// If the database label is nil, the current GRDB implementation uses the - /// following dispatch queue labels: - /// - /// - `GRDB.DatabaseQueue`: the (unique) dispatch queue of a DatabaseQueue - /// - `GRDB.DatabasePool.writer`: the (unique) writer dispatch queue of - /// a DatabasePool - /// - `GRDB.DatabasePool.reader.N`, where N is 1, 2, ...: one of the reader - /// dispatch queue(s) of a DatabasePool. N grows with the number of SQLite - /// connections: it may get bigger than the maximum number of concurrent - /// readers, as SQLite connections get closed and new ones are opened. - /// - `GRDB.DatabasePool.snapshot.N`: the dispatch queue of a - /// DatabaseSnapshot. N grows with the number of snapshots. - /// - /// If the database label is not nil, for example "MyDatabase", the current - /// GRDB implementation uses the following dispatch queue labels: - /// - /// - `MyDatabase`: the (unique) dispatch queue of a DatabaseQueue - /// - `MyDatabase.writer`: the (unique) writer dispatch queue of - /// a DatabasePool - /// - `MyDatabase.reader.N`, where N is 1, 2, ...: one of the reader - /// dispatch queue(s) of a DatabasePool. N grows with the number of SQLite - /// connections: it may get bigger than the maximum number of concurrent - /// readers, as SQLite connections get closed and new ones are opened. - /// - `MyDatabase.snapshot.N`: the dispatch queue of a - /// DatabaseSnapshot. N grows with the number of snapshots. - /// - /// The default label is nil. - public var label: String? = nil - - /// A function that is called on every statement executed by the database. - /// - /// Default: nil - public var trace: TraceFunction? - - - // MARK: - Encryption - - #if SQLITE_HAS_CODEC - /// The passphrase for the encrypted database. - /// - /// Default: nil - public var passphrase: String? - - #endif - - /// If set, allows custom configuration to be run every time - /// a new connection is opened. - /// - /// This block is run after the Database's connection has opened, but - /// before that connection has been made available to any read/write - /// API's. - /// - /// For example: - /// - /// var config = Configuration() - /// config.prepareDatabase = { db in - /// try db.execute(sql: "PRAGMA kdf_iter = 10000") - /// } - public var prepareDatabase: ((Database) throws -> Void)? - - // MARK: - Transactions - - /// The default kind of transaction. - /// - /// Default: deferred - public var defaultTransactionKind: Database.TransactionKind = .deferred - - /// If false, it is a programmer error to leave a transaction opened at the - /// end of a database access block. - /// - /// For example: - /// - /// let dbQueue = DatabaseQueue() - /// - /// // fatal error: A transaction has been left opened at the end of a database access - /// try dbQueue.inDatabase { db in - /// try db.beginTransaction() - /// } - /// - /// If true, one can leave opened transaction at the end of database access - /// blocks: - /// - /// var config = Configuration() - /// config.allowsUnsafeTransactions = true - /// let dbQueue = DatabaseQueue(configuration: config) - /// - /// try dbQueue.inDatabase { db in - /// try db.beginTransaction() - /// } - /// - /// try dbQueue.inDatabase { db in - /// try db.commit() - /// } - /// - /// This configuration flag has no effect on DatabasePool readers: those - /// never allow leaving a transaction opened at the end of a read access. - /// - /// Default: false - public var allowsUnsafeTransactions: Bool = false - - // MARK: - Concurrency - - /// The behavior in case of SQLITE_BUSY error. See https://www.sqlite.org/rescode.html#busy - /// - /// Default: immediateError - public var busyMode: Database.BusyMode = .immediateError - - /// The maximum number of concurrent readers (applies to database - /// pools only). - /// - /// Default: 5 - public var maximumReaderCount: Int = 5 - - /// The quality of service class for the work performed by the database. - /// - /// The quality of service is ignored if you supply a target queue. - /// - /// Default: .default (.unspecified on macOS < 10.10) - public var qos: DispatchQoS - - /// The target queue for the work performed by the database. - /// - /// Default: nil - public var targetQueue: DispatchQueue? = nil - - // MARK: - Factory Configuration - - /// Creates a factory configuration - public init() { - if #available(OSX 10.10, *) { - qos = .default - } else { - qos = .unspecified - } - } - - - // MARK: - Not Public - - var threadingMode: Database.ThreadingMode = .`default` - var SQLiteConnectionDidOpen: (() -> ())? - var SQLiteConnectionWillClose: ((SQLiteConnection) -> ())? - var SQLiteConnectionDidClose: (() -> ())? - var SQLiteOpenFlags: Int32 { - let readWriteFlags = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE) - return threadingMode.SQLiteOpenFlags | readWriteFlags - } - - func makeDispatchQueue(defaultLabel: String, purpose: String? = nil) -> DispatchQueue { - let label = (self.label ?? defaultLabel) + (purpose.map { "." + $0 } ?? "") - if let targetQueue = targetQueue { - return DispatchQueue(label: label, target: targetQueue) - } else { - return DispatchQueue(label: label, qos: qos) - } - } -} - -/// A tracing function that takes an SQL string. -public typealias TraceFunction = (String) -> Void diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Cursor.swift b/Example/Pods/GRDB.swift/GRDB/Core/Cursor.swift deleted file mode 100755 index 51ef3b3..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Cursor.swift +++ /dev/null @@ -1,758 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Parts of this file are derived from the Swift.org open source project: -// -// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// -//===----------------------------------------------------------------------===// - -// MARK: - Array, Sequence, Set extensions - -extension Array { - /// Creates an array containing the elements of a cursor. - /// - /// let cursor = try String.fetchCursor(db, sql: "SELECT 'foo' UNION ALL SELECT 'bar'") - /// let strings = try Array(cursor) // ["foo", "bar"] - @inlinable - public init(_ cursor: C) throws where C.Element == Element { - self.init() - while let element = try cursor.next() { - append(element) - } - } -} - -extension Sequence { - - /// Returns a cursor over the concatenated results of mapping transform - /// over self. - public func flatMap(_ transform: @escaping (Iterator.Element) throws -> SegmentOfResult) -> FlattenCursor, SegmentOfResult>> { - return AnyCursor(self).flatMap(transform) - } -} - -extension Set { - /// Creates a set containing the elements of a cursor. - /// - /// let cursor = try String.fetchCursor(db, sql: "SELECT 'foo' UNION ALL SELECT 'foo'") - /// let strings = try Set(cursor) // ["foo"] - public init(_ cursor: C) throws where C.Element == Element { - self.init() - while let element = try cursor.next() { - insert(element) - } - } -} - -// MARK: - Cursor - -/// A type that supplies the values of some external resource, one at a time. -/// -/// ## Overview -/// -/// The most common way to iterate over the elements of a cursor is to use a -/// `while` loop: -/// -/// let cursor = ... -/// while let element = try cursor.next() { -/// ... -/// } -/// -/// ## Relationship with standard Sequence and IteratorProtocol -/// -/// Cursors share traits with lazy sequences and iterators from the Swift -/// standard library. Differences are: -/// -/// - Cursor types are classes, and have a lifetime. -/// - Cursor iteration may throw errors. -/// - A cursor can not be repeated. -/// -/// The protocol comes with default implementations for many operations similar -/// to those defined by Swift's Sequence protocol: `contains`, `dropFirst`, -/// `dropLast`, `drop(while:)`, `enumerated`, `filter`, `first`, `flatMap`, -/// `forEach`, `joined`, `joined(separator:)`, `max`, `max(by:)`, `min`, -/// `min(by:)`, `map`, `prefix`, `prefix(while:)`, `reduce`, `reduce(into:)`, -/// `suffix`. -public protocol Cursor : class { - /// The type of element traversed by the cursor. - associatedtype Element - - /// Advances to the next element and returns it, or nil if no next element - /// exists. Once nil has been returned, all subsequent calls return nil. - func next() throws -> Element? -} - -extension Cursor { - /// Returns a Boolean value indicating whether the cursor contains - /// an element. - public func isEmpty() throws -> Bool { - return try next() == nil - } - - /// Returns a Boolean value indicating whether the cursor contains an - /// element that satisfies the given predicate. - /// - /// - parameter predicate: A closure that takes an element of the cursor as - /// its argument and returns a Boolean value that indicates whether the - /// passed element represents a match. - /// - returns: true if the cursor contains an element that satisfies - /// predicate; otherwise, false. - public func contains(where predicate: (Element) throws -> Bool) throws -> Bool { - while let element = try next() { - if try predicate(element) { - return true - } - } - return false - } - - /// Returns a cursor of pairs (n, x), where n represents a consecutive - /// integer starting at zero, and x represents an element of the cursor. - /// - /// let cursor = try String.fetchCursor(db, sql: "SELECT 'foo' UNION ALL SELECT 'bar'") - /// let c = cursor.enumerated() - /// while let (n, x) = c.next() { - /// print("\(n): \(x)") - /// } - /// // Prints: "0: foo" - /// // Prints: "1: bar" - public func enumerated() -> EnumeratedCursor { - return EnumeratedCursor(self) - } - - /// Returns the elements of the cursor that satisfy the given predicate. - public func filter(_ isIncluded: @escaping (Element) throws -> Bool) -> FilterCursor { - return FilterCursor(self, isIncluded) - } - - /// Returns the first element of the cursor that satisfies the given - /// predicate or nil if no such element is found. - public func first(where predicate: (Element) throws -> Bool) throws -> Element? { - while let element = try next() { - if try predicate(element) { - return element - } - } - return nil - } - - /// Returns a cursor over the concatenated non-nil results of mapping - /// transform over this cursor. - public func compactMap(_ transform: @escaping (Element) throws -> ElementOfResult?) -> MapCursor>, ElementOfResult> { - return map(transform).filter { $0 != nil }.map { $0! } - } - - /// Returns a cursor that skips any initial elements that satisfy - /// `predicate`. - /// - /// - Parameter predicate: A closure that takes an element of the cursir as - /// its argument and returns `true` if the element should be skipped or - /// `false` otherwise. Once `predicate` returns `false` it will not be - /// called again. - public func drop(while predicate: @escaping (Element) throws -> Bool) -> DropWhileCursor { - return DropWhileCursor(self, predicate: predicate) - } - - /// Returns a cursor containing all but the given number of initial - /// elements. - /// - /// If the number of elements to drop exceeds the number of elements in - /// the cursor, the result is an empty cursor. - /// - /// let numbers = AnyCursor([1, 2, 3, 4, 5]) - /// try print(numbers.dropFirst(2)) - /// // Prints "[3, 4, 5]" - /// try print(numbers.dropFirst(10)) - /// // Prints "[]" - /// - /// - Parameter n: The number of elements to drop from the beginning of - /// the cursor. `n` must be greater than or equal to zero. - /// - Returns: A cursor starting after the specified number of - /// elements. - public func dropFirst(_ n: Int) -> DropFirstCursor { - return DropFirstCursor(self, limit: n) - } - - /// Returns a cursor containing all but the first element of the cursor. - /// - /// The following example drops the first element from a cursor of integers. - /// - /// let numbers = AnyCursor([1, 2, 3, 4, 5]) - /// try print(numbers.dropFirst()) - /// // Prints "[2, 3, 4, 5]" - /// - /// If the cursor has no elements, the result is an empty cursor. - /// - /// - Returns: A cursor starting after the first element of the cursor. - public func dropFirst() -> DropFirstCursor { - return dropFirst(1) - } - - /// Returns an array containing all but the given number of final - /// elements. - /// - /// The cursor must be finite. If the number of elements to drop exceeds - /// the number of elements in the cursor, the result is an empty array. - /// - /// let numbers = AnyCursor([1, 2, 3, 4, 5]) - /// try print(numbers.dropLast(2)) - /// // Prints "[1, 2, 3]" - /// try print(numbers.dropLast(10)) - /// // Prints "[]" - /// - /// - Parameter n: The number of elements to drop off the end of the - /// cursor. `n` must be greater than or equal to zero. - /// - Returns: An array leaving off the specified number of elements. - public func dropLast(_ n: Int) throws -> [Element] { - GRDBPrecondition(n >= 0, "Can't drop a negative number of elements from a cursor") - if n == 0 { return try Array(self) } - - var result: [Element] = [] - var ringBuffer: [Element] = [] - var i = ringBuffer.startIndex - - while let element = try next() { - if ringBuffer.count < n { - ringBuffer.append(element) - } else { - result.append(ringBuffer[i]) - ringBuffer[i] = element - i = ringBuffer.index(after: i) % n - } - } - return result - } - - /// Returns an array containing all but the last element of the cursor. - /// - /// The following example drops the last element from a cursor of integers. - /// - /// let numbers = AnyCursor([1, 2, 3, 4, 5]) - /// try print(numbers.dropLast()) - /// // Prints "[1, 2, 3, 4]" - /// - /// If the cursor has no elements, the result is an empty cursor. - /// - /// - Returns: An array leaving off the last element of the cursor. - public func dropLast() throws -> [Element] { - return try dropLast(1) - } - - /// Returns a cursor over the concatenated results of mapping transform - /// over self. - public func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor>> { - return flatMap { try AnyCursor(transform($0)) } - } - - /// Returns a cursor over the concatenated results of mapping transform - /// over self. - public func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor> { - return map(transform).joined() - } - - /// Calls the given closure on each element in the cursor. - public func forEach(_ body: (Element) throws -> Void) throws { - while let element = try next() { - try body(element) - } - } - - /// Returns a cursor over the results of the transform function applied to - /// this cursor's elements. - public func map(_ transform: @escaping (Element) throws -> T) -> MapCursor { - return MapCursor(self, transform) - } - - /// Returns the maximum element in the cursor, using the given predicate as - /// the comparison between elements. - /// - /// - Parameter areInIncreasingOrder: A predicate that returns `true` - /// if its first argument should be ordered before its second - /// argument; otherwise, `false`. - /// - Returns: The cursor's maximum element, according to - /// `areInIncreasingOrder`. If the cursor has no elements, returns `nil`. - public func max(by areInIncreasingOrder: (Element, Element) throws -> Bool) throws -> Element? { - guard var result = try next() else { - return nil - } - while let e = try next() { - if try areInIncreasingOrder(result, e) { - result = e - } - } - return result - } - - /// Returns the minimum element in the cursor, using the given predicate as - /// the comparison between elements. - /// - /// - Parameter areInIncreasingOrder: A predicate that returns `true` - /// if its first argument should be ordered before its second - /// argument; otherwise, `false`. - /// - Returns: The cursor's minimum element, according to - /// `areInIncreasingOrder`. If the cursor has no elements, returns `nil`. - public func min(by areInIncreasingOrder: (Element, Element) throws -> Bool) throws -> Element? { - guard var result = try next() else { - return nil - } - while let e = try next() { - if try areInIncreasingOrder(e, result) { - result = e - } - } - return result - } - - /// Returns a cursor, up to the specified maximum length, containing the - /// initial elements of the cursor. - /// - /// If the maximum length exceeds the number of elements in the cursor, - /// the result contains all the elements in the cursor. - /// - /// let numbers = AnyCursor([1, 2, 3, 4, 5]) - /// try print(numbers.prefix(2)) - /// // Prints "[1, 2]" - /// try print(numbers.prefix(10)) - /// // Prints "[1, 2, 3, 4, 5]" - /// - /// - Parameter maxLength: The maximum number of elements to return. The - /// value of `maxLength` must be greater than or equal to zero. - /// - Returns: A cursor starting at the beginning of this cursor - /// with at most `maxLength` elements. - public func prefix(_ maxLength: Int) -> PrefixCursor { - return PrefixCursor(self, maxLength: maxLength) - } - - /// Returns a cursor of the initial consecutive elements that satisfy - /// `predicate`. - /// - /// - Parameter predicate: A closure that takes an element of the cursor as - /// its argument and returns `true` if the element should be included or - /// `false` otherwise. Once `predicate` returns `false` it will not be - /// called again. - public func prefix(while predicate: @escaping (Element) throws -> Bool) -> PrefixWhileCursor { - return PrefixWhileCursor(self, predicate: predicate) - } - - /// Returns the result of calling the given combining closure with each - /// element of this cursor and an accumulating value. - public func reduce( - _ initialResult: Result, - _ nextPartialResult: (Result, Element) throws -> Result) - throws -> Result - { - var accumulator = initialResult - while let element = try next() { - accumulator = try nextPartialResult(accumulator, element) - } - return accumulator - } - - /// Returns the result of calling the given combining closure with each - /// element of this cursor and an accumulating value. - public func reduce( - into initialResult: Result, - _ updateAccumulatingResult: (inout Result, Element) throws -> Void) - throws -> Result - { - var accumulator = initialResult - while let element = try next() { - try updateAccumulatingResult(&accumulator, element) - } - return accumulator - } - - /// Returns an array, up to the given maximum length, containing the - /// final elements of the cursor. - /// - /// The cursor must be finite. If the maximum length exceeds the number of - /// elements in the cursor, the result contains all the elements in the - /// cursor. - /// - /// let numbers = AnyCursor([1, 2, 3, 4, 5]) - /// try print(numbers.suffix(2)) - /// // Prints "[4, 5]" - /// try print(numbers.suffix(10)) - /// // Prints "[1, 2, 3, 4, 5]" - /// - /// - Parameter maxLength: The maximum number of elements to return. The - /// value of `maxLength` must be greater than or equal to zero. - public func suffix(_ maxLength: Int) throws -> [Element] { - GRDBPrecondition(maxLength >= 0, "Can't take a suffix of negative length from a cursor") - if maxLength == 0 { - return [] - } - - var ringBuffer: [Element] = [] - ringBuffer.reserveCapacity(maxLength) - - var i = ringBuffer.startIndex - - while let element = try next() { - if ringBuffer.count < maxLength { - ringBuffer.append(element) - } else { - ringBuffer[i] = element - i += 1 - i %= maxLength - } - } - - if i != ringBuffer.startIndex { - let s0 = ringBuffer[i.. Bool { - while let e = try next() { - if e == element { - return true - } - } - return false - } -} - -// MARK: Comparable elements - -extension Cursor where Element: Comparable { - /// Returns the maximum element in the cursor. - /// - /// - Parameter areInIncreasingOrder: A predicate that returns `true` - /// if its first argument should be ordered before its second - /// argument; otherwise, `false`. - /// - Returns: The cursor's maximum element, according to - /// `areInIncreasingOrder`. If the cursor has no elements, returns - /// `nil`. - public func max() throws -> Element? { - return try max(by: <) - } - - /// Returns the minimum element in the cursor. - /// - /// - Parameter areInIncreasingOrder: A predicate that returns `true` - /// if its first argument should be ordered before its second - /// argument; otherwise, `false`. - /// - Returns: The cursor's minimum element, according to - /// `areInIncreasingOrder`. If the cursor has no elements, returns - /// `nil`. - public func min() throws -> Element? { - return try min(by: <) - } -} - -// MARK: Cursor elements - -extension Cursor where Element: Cursor { - /// Returns the elements of this cursor of cursors, concatenated. - public func joined() -> FlattenCursor { - return FlattenCursor(self) - } -} - -// MARK: Sequence elements - -extension Cursor where Element: Sequence { - /// Returns the elements of this cursor of sequences, concatenated. - public func joined() -> FlattenCursor>> { - return flatMap { $0 } - } -} - -// MARK: String elements - -extension Cursor where Element: StringProtocol { - /// Returns the elements of this cursor of sequences, concatenated. - public func joined(separator: String = "") throws -> String { - if separator.isEmpty { - var result = "" - while let x = try next() { - result.append(String(x)) - } - return result - } else { - var result = "" - if let first = try next() { - result.append(String(first)) - while let next = try next() { - result.append(separator) - result.append(String(next)) - } - } - return result - } - } -} - -// MARK: Specialized Cursors - -/// A type-erased cursor of Element. -/// -/// This cursor forwards its next() method to an arbitrary underlying cursor -/// having the same Element type, hiding the specifics of the underlying -/// cursor. -public final class AnyCursor : Cursor { - private let element: () throws -> Element? - - /// Creates a cursor that wraps a base cursor but whose type depends only on - /// the base cursor’s element type - public init(_ base: C) where C.Element == Element { - element = base.next - } - - /// Creates a cursor that wraps a base iterator but whose type depends only - /// on the base iterator’s element type - public convenience init(iterator: I) where I.Element == Element { - var iterator = iterator - self.init { iterator.next() } - } - - /// Creates a cursor that wraps a base sequence but whose type depends only - /// on the base sequence’s element type - public convenience init(_ s: S) where S.Element == Element { - self.init(iterator: s.makeIterator()) - } - - /// Creates a cursor that wraps the given closure in its next() method - public init(_ body: @escaping () throws -> Element?) { - element = body - } - - /// Advances to the next element and returns it, or nil if no next - /// element exists. - /// :nodoc: - public func next() throws -> Element? { - return try element() - } -} - -/// :nodoc: -public final class DropFirstCursor : Cursor { - private let base: Base - private let limit: Int - private var dropped: Int = 0 - - init(_ base: Base, limit: Int) { - GRDBPrecondition(limit >= 0, "Can't drop a negative number of elements from a cursor") - self.base = base - self.limit = limit - } - - public func next() throws -> Base.Element? { - while dropped < limit { - if try base.next() == nil { - dropped = limit - return nil - } - dropped += 1 - } - return try base.next() - } -} - -/// A cursor whose elements consist of the elements that follow the initial -/// consecutive elements of some base cursor that satisfy a given predicate. -/// -/// :nodoc: -public final class DropWhileCursor : Cursor { - private let base: Base - private let predicate: (Base.Element) throws -> Bool - private var predicateHasFailed = false - - init(_ base: Base, predicate: @escaping (Base.Element) throws -> Bool) { - self.base = base - self.predicate = predicate - } - - public func next() throws -> Base.Element? { - if predicateHasFailed { - return try base.next() - } - - while let nextElement = try base.next() { - if try !predicate(nextElement) { - predicateHasFailed = true - return nextElement - } - } - return nil - } -} - -/// An enumeration of the elements of a cursor. -/// -/// To create an instance of `EnumeratedCursor`, call the `enumerated()` method -/// on a cursor: -/// -/// let cursor = try String.fetchCursor(db, sql: "SELECT 'foo' UNION ALL SELECT 'bar'") -/// let c = cursor.enumerated() -/// while let (n, x) = c.next() { -/// print("\(n): \(x)") -/// } -/// // Prints: "0: foo" -/// // Prints: "1: bar" -/// -/// :nodoc: -public final class EnumeratedCursor : Cursor { - private let base: Base - private var index: Int - - init(_ base: Base) { - self.base = base - self.index = 0 - } - - /// Advances to the next element and returns it, or nil if no next - /// element exists. - /// :nodoc: - public func next() throws -> (Int, Base.Element)? { - guard let element = try base.next() else { return nil } - defer { index += 1 } - return (index, element) - } -} - -/// A cursor whose elements consist of the elements of some base cursor that -/// also satisfy a given predicate. -/// -/// :nodoc: -public final class FilterCursor : Cursor { - private let base: Base - private let isIncluded: (Base.Element) throws -> Bool - - init(_ base: Base, _ isIncluded: @escaping (Base.Element) throws -> Bool) { - self.base = base - self.isIncluded = isIncluded - } - - /// Advances to the next element and returns it, or nil if no next - /// element exists. - /// :nodoc: - public func next() throws -> Base.Element? { - while let element = try base.next() { - if try isIncluded(element) { - return element - } - } - return nil - } -} - -/// A cursor consisting of all the elements contained in each segment contained -/// in some Base cursor. -/// -/// See Cursor.joined(), Cursor.flatMap(_:), Sequence.flatMap(_:) -/// -/// :nodoc: -public final class FlattenCursor : Cursor where Base.Element: Cursor { - private let base: Base - private var inner: Base.Element? - - init(_ base: Base) { - self.base = base - } - - /// Advances to the next element and returns it, or nil if no next - /// element exists. - /// :nodoc: - public func next() throws -> Base.Element.Element? { - while true { - if let element = try inner?.next() { - return element - } - guard let inner = try base.next() else { - return nil - } - self.inner = inner - } - } -} - -/// A Cursor whose elements consist of those in a Base Cursor passed through a -/// transform function returning Element. -/// -/// See Cursor.map(_:) -/// -/// :nodoc: -public final class MapCursor : Cursor { - private let base: Base - private let transform: (Base.Element) throws -> Element - - init(_ base: Base, _ transform: @escaping (Base.Element) throws -> Element) { - self.base = base - self.transform = transform - } - - /// Advances to the next element and returns it, or nil if no next - /// element exists. - /// :nodoc: - public func next() throws -> Element? { - guard let element = try base.next() else { return nil } - return try transform(element) - } -} - -/// A cursor that only consumes up to `n` elements from an underlying -/// `Base` cursor. -/// -/// :nodoc: -public final class PrefixCursor : Cursor { - private let base: Base - private let maxLength: Int - private var taken = 0 - - init(_ base: Base, maxLength: Int) { - self.base = base - self.maxLength = maxLength - } - - public func next() throws -> Base.Element? { - if taken >= maxLength { return nil } - taken += 1 - - if let next = try base.next() { - return next - } - - taken = maxLength - return nil - } -} - -/// A cursor whose elements consist of the initial consecutive elements of -/// some base cursor that satisfy a given predicate. -/// -/// :nodoc: -public final class PrefixWhileCursor : Cursor { - private let base: Base - private let predicate: (Base.Element) throws -> Bool - private var predicateHasFailed = false - - init(_ base: Base, predicate: @escaping (Base.Element) throws -> Bool) { - self.base = base - self.predicate = predicate - } - - public func next() throws -> Base.Element? { - if !predicateHasFailed, let nextElement = try base.next() { - if try predicate(nextElement) { - return nextElement - } else { - predicateHasFailed = true - } - } - return nil - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Database+Schema.swift b/Example/Pods/GRDB.swift/GRDB/Core/Database+Schema.swift deleted file mode 100755 index 0155a21..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Database+Schema.swift +++ /dev/null @@ -1,616 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -extension Database { - - // MARK: - Database Schema - - /// Clears the database schema cache. - /// - /// You may need to clear the cache manually if the database schema is - /// modified by another connection. - public func clearSchemaCache() { - SchedulingWatchdog.preconditionValidQueue(self) - schemaCache.clear() - - // We also clear updateStatementCache and selectStatementCache despite - // the automatic statement recompilation (see https://www.sqlite.org/c3ref/prepare.html) - // because the automatic statement recompilation only happens a - // limited number of times. - internalStatementCache.clear() - publicStatementCache.clear() - } - - /// Returns whether a table exists. - public func tableExists(_ name: String) throws -> Bool { - return try exists(type: .table, name: name) - } - - /// Returns whether a table is an internal SQLite table. - /// - /// Those are tables whose name begins with "sqlite_". - /// - /// For more information, see https://www.sqlite.org/fileformat2.html - public func isSQLiteInternalTable(_ tableName: String) -> Bool { - // https://www.sqlite.org/fileformat2.html#internal_schema_objects - // > The names of internal schema objects always begin with "sqlite_" - // > and any table, index, view, or trigger whose name begins with - // > "sqlite_" is an internal schema object. SQLite prohibits - // > applications from creating objects whose names begin with - // > "sqlite_". - return tableName.starts(with: "sqlite_") - } - - /// Returns whether a table is an internal GRDB table. - /// - /// Those are tables whose name begins with "grdb_". - public func isGRDBInternalTable(_ tableName: String) -> Bool { - return tableName.starts(with: "grdb_") - } - - /// Returns whether a view exists. - public func viewExists(_ name: String) throws -> Bool { - return try exists(type: .view, name: name) - } - - /// Returns whether a trigger exists. - public func triggerExists(_ name: String) throws -> Bool { - return try exists(type: .trigger, name: name) - } - - private func exists(type: SchemaObjectType, name: String) throws -> Bool { - // SQlite identifiers are case-insensitive, case-preserving: - // http://www.alberton.info/dbms_identifiers_and_case_sensitivity.html - let name = name.lowercased() - return try schema() - .names(ofType: type) - .contains { $0.lowercased() == name } - } - - /// The primary key for table named `tableName`. - /// - /// All tables have a primary key, even when it is not explicit. When a - /// table has no explicit primary key, the result is the hidden - /// "rowid" column. - /// - /// - throws: A DatabaseError if table does not exist. - public func primaryKey(_ tableName: String) throws -> PrimaryKeyInfo { - SchedulingWatchdog.preconditionValidQueue(self) - - if let primaryKey = schemaCache.primaryKey(tableName) { - return primaryKey - } - - // https://www.sqlite.org/pragma.html - // - // > PRAGMA database.table_info(table-name); - // > - // > This pragma returns one row for each column in the named table. - // > Columns in the result set include the column name, data type, - // > whether or not the column can be NULL, and the default value for - // > the column. The "pk" column in the result set is zero for columns - // > that are not part of the primary key, and is the index of the - // > column in the primary key for columns that are part of the primary - // > key. - // - // CREATE TABLE players ( - // id INTEGER PRIMARY KEY, - // name TEXT, - // score INTEGER) - // - // PRAGMA table_info("players") - // - // cid | name | type | notnull | dflt_value | pk | - // 0 | id | INTEGER | 0 | NULL | 1 | - // 1 | name | TEXT | 0 | NULL | 0 | - // 2 | score | INTEGER | 0 | NULL | 0 | - - let columns = try self.columns(in: tableName) - - let primaryKey: PrimaryKeyInfo - let pkColumns = columns - .filter { $0.primaryKeyIndex > 0 } - .sorted { $0.primaryKeyIndex < $1.primaryKeyIndex } - - switch pkColumns.count { - case 0: - // No explicit primary key => primary key is hidden rowID column - primaryKey = .hiddenRowID - case 1: - // Single column - let pkColumn = pkColumns.first! - - // https://www.sqlite.org/lang_createtable.html: - // - // > With one exception noted below, if a rowid table has a primary - // > key that consists of a single column and the declared type of - // > that column is "INTEGER" in any mixture of upper and lower - // > case, then the column becomes an alias for the rowid. Such a - // > column is usually referred to as an "integer primary key". - // > A PRIMARY KEY column only becomes an integer primary key if the - // > declared type name is exactly "INTEGER". Other integer type - // > names like "INT" or "BIGINT" or "SHORT INTEGER" or "UNSIGNED - // > INTEGER" causes the primary key column to behave as an ordinary - // > table column with integer affinity and a unique index, not as - // > an alias for the rowid. - // > - // > The exception mentioned above is that if the declaration of a - // > column with declared type "INTEGER" includes an "PRIMARY KEY - // > DESC" clause, it does not become an alias for the rowid [...] - // - // FIXME: We ignore the exception, and consider all INTEGER primary - // keys as aliases for the rowid: - if pkColumn.type.uppercased() == "INTEGER" { - primaryKey = .rowID(pkColumn.name) - } else { - primaryKey = .regular([pkColumn.name]) - } - default: - // Multi-columns primary key - primaryKey = .regular(pkColumns.map { $0.name }) - } - - schemaCache.set(primaryKey: primaryKey, forTable: tableName) - return primaryKey - } - - /// The indexes on table named `tableName`; returns the empty array if the - /// table does not exist. - /// - /// Note: SQLite does not define any index for INTEGER PRIMARY KEY columns: - /// this method does not return any index that represents this primary key. - /// - /// If you want to know if a set of columns uniquely identify a row, prefer - /// table(_:hasUniqueKey:) instead. - public func indexes(on tableName: String) throws -> [IndexInfo] { - if let indexes = schemaCache.indexes(on: tableName) { - return indexes - } - - let indexes = try Row - .fetchAll(self, sql: "PRAGMA index_list(\(tableName.quotedDatabaseIdentifier))") - .map { row -> IndexInfo in - // [seq:0 name:"index" unique:0 origin:"c" partial:0] - let indexName: String = row[1] - let unique: Bool = row[2] - let columns = try Row - .fetchAll(self, sql: "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))") - .map { row -> (Int, String) in - // [seqno:0 cid:2 name:"column"] - (row[0] as Int, row[2] as String) - } - .sorted { $0.0 < $1.0 } - .map { $0.1 } - return IndexInfo(name: indexName, columns: columns, unique: unique) - } - - if indexes.isEmpty { - // PRAGMA index_list doesn't throw any error when table does - // not exist. So let's check if table exists: - if try tableExists(tableName) == false { - throw DatabaseError(message: "no such table: \(tableName)") - } - } - - schemaCache.set(indexes: indexes, forTable: tableName) - return indexes - } - - /// True if a sequence of columns uniquely identifies a row, that is to say - /// if the columns are the primary key, or if there is a unique index on them. - public func table(_ tableName: String, hasUniqueKey columns: T) throws -> Bool where T.Iterator.Element == String { - return try columnsForUniqueKey(Array(columns), in: tableName) != nil - } - - /// The foreign keys defined on table named `tableName`. - public func foreignKeys(on tableName: String) throws -> [ForeignKeyInfo] { - if let foreignKeys = schemaCache.foreignKeys(on: tableName) { - return foreignKeys - } - - var rawForeignKeys: [(destinationTable: String, mapping: [(origin: String, destination: String?, seq: Int)])] = [] - var previousId: Int? = nil - for row in try Row.fetchAll(self, sql: "PRAGMA foreign_key_list(\(tableName.quotedDatabaseIdentifier))") { - // row = [id:0 seq:0 table:"parents" from:"parentId" to:"id" on_update:"..." on_delete:"..." match:"..."] - let id: Int = row[0] - let seq: Int = row[1] - let table: String = row[2] - let origin: String = row[3] - let destination: String? = row[4] - - if previousId == id { - rawForeignKeys[rawForeignKeys.count - 1].mapping.append((origin: origin, destination: destination, seq: seq)) - } else { - rawForeignKeys.append((destinationTable: table, mapping: [(origin: origin, destination: destination, seq: seq)])) - previousId = id - } - } - - if rawForeignKeys.isEmpty { - // PRAGMA foreign_key_list doesn't throw any error when table does - // not exist. So let's check if table exists: - if try tableExists(tableName) == false { - throw DatabaseError(message: "no such table: \(tableName)") - } - } - - let foreignKeys = try rawForeignKeys.map { (destinationTable, columnMapping) -> ForeignKeyInfo in - let orderedMapping = columnMapping - .sorted { $0.seq < $1.seq } - .map { (origin: $0.origin, destination: $0 .destination) } - - let completeMapping: [(origin: String, destination: String)] - if orderedMapping.contains(where: { (_, destination) in destination == nil }) { - let pk = try primaryKey(destinationTable) - completeMapping = zip(pk.columns, orderedMapping).map { (pkColumn, arrow) in - (origin: arrow.origin, destination: pkColumn) - } - } else { - completeMapping = orderedMapping.map { (origin, destination) in - (origin: origin, destination: destination!) - } - } - return ForeignKeyInfo(destinationTable: destinationTable, mapping: completeMapping) - } - - schemaCache.set(foreignKeys: foreignKeys, forTable: tableName) - return foreignKeys - } - - /// Returns the actual name of the database table - func canonicalTableName(_ tableName: String) throws -> String { - guard let name = try schema().canonicalName(tableName, ofType: .table) else { - throw DatabaseError(message: "no such table: \(tableName)") - } - return name - } - - func schema() throws -> SchemaInfo { - if let schemaInfo = schemaCache.schemaInfo { - return schemaInfo - } - let schemaInfo = try SchemaInfo(self) - schemaCache.schemaInfo = schemaInfo - return schemaInfo - } -} - -extension Database { - - /// The columns in the table named `tableName` - /// - /// - throws: A DatabaseError if table does not exist. - public func columns(in tableName: String) throws -> [ColumnInfo] { - if let columns = schemaCache.columns(in: tableName) { - return columns - } - - // https://www.sqlite.org/pragma.html - // - // > PRAGMA database.table_info(table-name); - // > - // > This pragma returns one row for each column in the named table. - // > Columns in the result set include the column name, data type, - // > whether or not the column can be NULL, and the default value for - // > the column. The "pk" column in the result set is zero for columns - // > that are not part of the primary key, and is the index of the - // > column in the primary key for columns that are part of the primary - // > key. - // - // CREATE TABLE players ( - // id INTEGER PRIMARY KEY, - // firstName TEXT, - // lastName TEXT) - // - // PRAGMA table_info("players") - // - // cid | name | type | notnull | dflt_value | pk | - // 0 | id | INTEGER | 0 | NULL | 1 | - // 1 | name | TEXT | 0 | NULL | 0 | - // 2 | score | INTEGER | 0 | NULL | 0 | - - if sqlite3_libversion_number() < 3008005 { - // Work around a bug in SQLite where PRAGMA table_info would - // return a result even after the table was deleted. - if try !tableExists(tableName) { - throw DatabaseError(message: "no such table: \(tableName)") - } - } - let columns = try ColumnInfo - .fetchAll(self, sql: "PRAGMA table_info(\(tableName.quotedDatabaseIdentifier))") - .sorted(by: { $0.cid < $1.cid }) - guard columns.count > 0 else { - throw DatabaseError(message: "no such table: \(tableName)") - } - - schemaCache.set(columns: columns, forTable: tableName) - return columns - } - - /// If there exists a unique key on columns, return the columns - /// ordered as the matching index (or primay key). Case of returned columns - /// is not guaranteed. - func columnsForUniqueKey(_ columns: T, in tableName: String) throws -> [String]? where T.Iterator.Element == String { - let primaryKey = try self.primaryKey(tableName) // first, so that we fail early and consistently should the table not exist - let lowercasedColumns = Set(columns.map { $0.lowercased() }) - if Set(primaryKey.columns.map { $0.lowercased() }) == lowercasedColumns { - return primaryKey.columns - } - if let index = try indexes(on: tableName).first(where: { index in index.isUnique && Set(index.columns.map { $0.lowercased() }) == lowercasedColumns }) { - // There is an explicit unique index on the columns - return index.columns - } - return nil - } -} - -/// A column of a database table. -/// -/// This type closely matches the information returned by the -/// `table_info` pragma. -/// -/// > CREATE TABLE player ( -/// id INTEGER PRIMARY KEY, -/// firstName TEXT, -/// lastName TEXT) -/// > PRAGMA table_info("player") -/// cid name type notnull dflt_value pk -/// ---- ----- ------- -------- ---------- --- -/// 0 id INTEGER 0 NULL 1 -/// 1 name TEXT 0 NULL 0 -/// 2 score INTEGER 0 NULL 0 -/// -/// See `Database.columns(in:)` and https://www.sqlite.org/pragma.html#pragma_table_info -public struct ColumnInfo : FetchableRecord { - let cid: Int - - /// The column name - public let name: String - - /// The column data type - public let type: String - - /// True if and only if the column is constrained to be not null. - public let isNotNull: Bool - - /// The SQL snippet that defines the default value, if any. - /// - /// When nil, the column has no default value. - /// - /// When not nil, it contains an SQL string that defines an expression. That - /// expression may be a literal, as `1`, or `'foo'`. It may also contain a - /// non-constant expression such as `CURRENT_TIMESTAMP`. - /// - /// For example: - /// - /// try db.execute(sql: """ - /// CREATE TABLE player( - /// id INTEGER PRIMARY KEY, - /// name TEXT DEFAULT 'Anonymous', - /// score INT DEFAULT 0, - /// creationDate DATE DEFAULT CURRENT_TIMESTAMP - /// ) - /// """) - /// let columnInfos = try db.columns(in: "player") - /// columnInfos[0].defaultValueSQL // nil - /// columnInfos[1].defaultValueSQL // "'Anoynymous'" - /// columnInfos[2].defaultValueSQL // "0" - /// columnInfos[3].defaultValueSQL // "CURRENT_TIMESTAMP" - public let defaultValueSQL: String? - - /// Zero for columns that are not part of the primary key. - /// - /// Before SQLite 3.7.16, it is 1 for columns that are part of the - /// primary key. - /// - /// Starting from SQLite 3.7.16, it is the one-based index of the column in - /// the primary key for columns that are part of the primary key. - /// - /// References: - /// - https://sqlite.org/releaselog/3_7_16.html - /// - http://mailinglists.sqlite.org/cgi-bin/mailman/private/sqlite-users/2013-April/046034.html - public let primaryKeyIndex: Int - - /// :nodoc: - public init(row: Row) { - cid = row["cid"] - name = row["name"] - type = row["type"] - isNotNull = row["notnull"] - defaultValueSQL = row["dflt_value"] - primaryKeyIndex = row["pk"] - } -} - -/// An index on a database table. -/// -/// See `Database.indexes(on:)` -public struct IndexInfo { - /// The name of the index - public let name: String - - /// The indexed columns - public let columns: [String] - - /// True if the index is unique - public let isUnique: Bool - - init(name: String, columns: [String], unique: Bool) { - self.name = name - self.columns = columns - self.isUnique = unique - } -} - -/// Primary keys are returned from the Database.primaryKey(_:) method. -/// -/// When the table's primary key is the rowid: -/// -/// // CREATE TABLE item (name TEXT) -/// let pk = try db.primaryKey("item") -/// pk.columns // ["rowid"] -/// pk.rowIDColumn // nil -/// pk.isRowID // true -/// -/// // CREATE TABLE citizen ( -/// // id INTEGER PRIMARY KEY, -/// // name TEXT -/// // ) -/// let pk = try db.primaryKey("citizen")! -/// pk.columns // ["id"] -/// pk.rowIDColumn // "id" -/// pk.isRowID // true -/// -/// When the table's primary key is not the rowid: -/// -/// // CREATE TABLE country ( -/// // isoCode TEXT NOT NULL PRIMARY KEY -/// // name TEXT -/// // ) -/// let pk = db.primaryKey("country")! -/// pk.columns // ["isoCode"] -/// pk.rowIDColumn // nil -/// pk.isRowID // false -/// -/// // CREATE TABLE citizenship ( -/// // citizenID INTEGER NOT NULL REFERENCES citizen(id) -/// // countryIsoCode TEXT NOT NULL REFERENCES country(isoCode) -/// // PRIMARY KEY (citizenID, countryIsoCode) -/// // ) -/// let pk = db.primaryKey("citizenship")! -/// pk.columns // ["citizenID", "countryIsoCode"] -/// pk.rowIDColumn // nil -/// pk.isRowID // false -public struct PrimaryKeyInfo { - private enum Impl { - /// The hidden rowID. - case hiddenRowID - - /// An INTEGER PRIMARY KEY column that aliases the Row ID. - /// Associated string is the column name. - case rowID(String) - - /// Any primary key, but INTEGER PRIMARY KEY. - /// Associated strings are column names. - case regular([String]) - } - - private let impl: Impl - - static func rowID(_ column: String) -> PrimaryKeyInfo { - return PrimaryKeyInfo(impl: .rowID(column)) - } - - static func regular(_ columns: [String]) -> PrimaryKeyInfo { - assert(!columns.isEmpty) - return PrimaryKeyInfo(impl: .regular(columns)) - } - - static let hiddenRowID = PrimaryKeyInfo(impl: .hiddenRowID) - - /// The columns in the primary key; this array is never empty. - public var columns: [String] { - switch impl { - case .hiddenRowID: - return [Column.rowID.name] - case .rowID(let column): - return [column] - case .regular(let columns): - return columns - } - } - - /// When not nil, the name of the column that contains the INTEGER PRIMARY KEY. - public var rowIDColumn: String? { - switch impl { - case .hiddenRowID: - return nil - case .rowID(let column): - return column - case .regular: - return nil - } - } - - /// When true, the primary key is the rowid: - public var isRowID: Bool { - switch impl { - case .hiddenRowID: - return true - case .rowID: - return true - case .regular: - return false - } - } -} - -/// You get foreign keys from table names, with the -/// `foreignKeys(on:)` method. -public struct ForeignKeyInfo { - /// The name of the destination table - public let destinationTable: String - - /// The column to column mapping - public let mapping: [(origin: String, destination: String)] - - /// The origin columns - public var originColumns: [String] { - return mapping.map { $0.origin } - } - - /// The destination columns - public var destinationColumns: [String] { - return mapping.map { $0.destination } - } -} - -enum SchemaObjectType: String { - case index - case table - case trigger - case view -} - -struct SchemaInfo: Equatable { - private var objects: Set - - init(_ db: Database) throws { - objects = try Set(SchemaObject.fetchCursor(db, sql: """ - SELECT type, name, tbl_name, sql, 0 AS isTemporary FROM sqlite_master \ - UNION \ - SELECT type, name, tbl_name, sql, 1 FROM sqlite_temp_master - """)) - } - - /// All names for a given type - func names(ofType type: SchemaObjectType) -> Set { - return objects.reduce(into: []) { (set, key) in - if key.type == type.rawValue { - set.insert(key.name) - } - } - } - - /// Returns the canonical name of the object: - /// - /// try db.execute(sql: "CREATE TABLE FooBar (...)") - /// try db.schema().canonicalName("foobar", ofType: .table) // "FooBar" - func canonicalName(_ name: String, ofType type: SchemaObjectType) -> String? { - let name = name.lowercased() - return objects.first { $0.name.lowercased() == name }?.name - } - - private struct SchemaObject: Codable, Hashable, FetchableRecord { - var type: String - var name: String - var tbl_name: String? - var sql: String? - var isTemporary: Bool - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Database+Statements.swift b/Example/Pods/GRDB.swift/GRDB/Core/Database+Statements.swift deleted file mode 100755 index 1553dbf..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Database+Statements.swift +++ /dev/null @@ -1,340 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -extension Database { - - // MARK: - Statements - - /// Returns a new prepared statement that can be reused. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT COUNT(*) FROM player WHERE score > ?") - /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! - /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! - /// - /// - parameter sql: An SQL query. - /// - returns: A SelectStatement. - /// - throws: A DatabaseError whenever SQLite could not parse the sql query. - public func makeSelectStatement(sql: String) throws -> SelectStatement { - return try makeSelectStatement(sql: sql, prepFlags: 0) - } - - /// Returns a new prepared statement that can be reused. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT COUNT(*) FROM player WHERE score > ?", prepFlags: 0) - /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! - /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! - /// - /// - parameter sql: An SQL query. - /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from - /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html) - /// - returns: A SelectStatement. - /// - throws: A DatabaseError whenever SQLite could not parse the sql query. - func makeSelectStatement(sql: String, prepFlags: Int32) throws -> SelectStatement { - return try SelectStatement.prepare(sql: sql, prepFlags: prepFlags, in: self) - } - - /// Returns a prepared statement that can be reused. - /// - /// let statement = try db.cachedSelectStatement(sql: "SELECT COUNT(*) FROM player WHERE score > ?") - /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! - /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! - /// - /// The returned statement may have already been used: it may or may not - /// contain values for its eventual arguments. - /// - /// - parameter sql: An SQL query. - /// - returns: An UpdateStatement. - /// - throws: A DatabaseError whenever SQLite could not parse the sql query. - public func cachedSelectStatement(sql: String) throws -> SelectStatement { - return try publicStatementCache.selectStatement(sql) - } - - /// Returns a cached statement that does not conflict with user's cached statements. - func internalCachedSelectStatement(sql: String) throws -> SelectStatement { - return try internalStatementCache.selectStatement(sql) - } - - /// Returns a new prepared statement that can be reused. - /// - /// let statement = try db.makeUpdateStatement(sql: "INSERT INTO player (name) VALUES (?)") - /// try statement.execute(arguments: ["Arthur"]) - /// try statement.execute(arguments: ["Barbara"]) - /// - /// - parameter sql: An SQL query. - /// - returns: An UpdateStatement. - /// - throws: A DatabaseError whenever SQLite could not parse the sql query. - public func makeUpdateStatement(sql: String) throws -> UpdateStatement { - return try makeUpdateStatement(sql: sql, prepFlags: 0) - } - - /// Returns a new prepared statement that can be reused. - /// - /// let statement = try db.makeUpdateStatement(sql: "INSERT INTO player (name) VALUES (?)", prepFlags: 0) - /// try statement.execute(arguments: ["Arthur"]) - /// try statement.execute(arguments: ["Barbara"]) - /// - /// - parameter sql: An SQL query. - /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from - /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html) - /// - returns: An UpdateStatement. - /// - throws: A DatabaseError whenever SQLite could not parse the sql query. - func makeUpdateStatement(sql: String, prepFlags: Int32) throws -> UpdateStatement { - return try UpdateStatement.prepare(sql: sql, prepFlags: prepFlags, in: self) - } - - /// Returns a prepared statement that can be reused. - /// - /// let statement = try db.cachedUpdateStatement(sql: "INSERT INTO player (name) VALUES (?)") - /// try statement.execute(arguments: ["Arthur"]) - /// try statement.execute(arguments: ["Barbara"]) - /// - /// The returned statement may have already been used: it may or may not - /// contain values for its eventual arguments. - /// - /// - parameter sql: An SQL query. - /// - returns: An UpdateStatement. - /// - throws: A DatabaseError whenever SQLite could not parse the sql query. - public func cachedUpdateStatement(sql: String) throws -> UpdateStatement { - return try publicStatementCache.updateStatement(sql) - } - - /// Returns a cached statement that does not conflict with user's cached statements. - func internalCachedUpdateStatement(sql: String) throws -> UpdateStatement { - return try internalStatementCache.updateStatement(sql) - } - - /// Executes one or several SQL statements, separated by semi-colons. - /// - /// try db.execute( - /// sql: "INSERT INTO player (name) VALUES (:name)", - /// arguments: ["name": "Arthur"]) - /// - /// try db.execute(sql: """ - /// INSERT INTO player (name) VALUES (?); - /// INSERT INTO player (name) VALUES (?); - /// INSERT INTO player (name) VALUES (?); - /// """, arguments: ["Arthur", "Barbara", "O'Brien"]) - /// - /// This method may throw a DatabaseError. - /// - /// - parameters: - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func execute(sql: String, arguments: StatementArguments = StatementArguments()) throws { - try execute(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Executes one or several SQL statements, separated by semi-colons. - /// - /// try db.execute(literal: SQLLiteral( - /// sql: "INSERT INTO player (name) VALUES (:name)", - /// arguments: ["name": "Arthur"])) - /// - /// try db.execute(literal: SQLLiteral(sql: """ - /// INSERT INTO player (name) VALUES (?); - /// INSERT INTO player (name) VALUES (?); - /// INSERT INTO player (name) VALUES (?); - /// """, arguments: ["Arthur", "Barbara", "O'Brien"])) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// try db.execute(literal: """ - /// INSERT INTO player (name) VALUES (\("Arthur")); - /// INSERT INTO player (name) VALUES (\("Barbara")); - /// INSERT INTO player (name) VALUES (\("O'Brien")); - /// """) - /// - /// This method may throw a DatabaseError. - /// - /// - parameter sqlLiteral: An SQLLiteral. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func execute(literal sqlLiteral: SQLLiteral) throws { - // This method is like sqlite3_exec (https://www.sqlite.org/c3ref/exec.html) - // It adds support for arguments, and the tricky part is to consume - // arguments as statements are executed. - // - // This job is performed by StatementArguments.extractBindings(forStatement:allowingRemainingValues:) - // - // And before we return, we'll check that all arguments were consumed. - - var arguments = sqlLiteral.arguments - let initialValuesCount = arguments.values.count - - // Build a C string (SQLite wants that), and execute SQL statements one - // after the other. - try sqlLiteral.sql.utf8CString.withUnsafeBufferPointer { buffer in - guard let sqlStart = buffer.baseAddress else { return } - let sqlEnd = sqlStart + buffer.count // past \0 - var statementStart = sqlStart - while statementStart < sqlEnd { - var statementEnd: UnsafePointer? = nil - let nextStatement: UpdateStatement? - - // Compile - do { - let statementCompilationAuthorizer = StatementCompilationAuthorizer() - authorizer = statementCompilationAuthorizer - defer { authorizer = nil } - - nextStatement = try UpdateStatement( - database: self, - statementStart: statementStart, - statementEnd: &statementEnd, - prepFlags: 0, - authorizer: statementCompilationAuthorizer) - } - - guard let statement = nextStatement else { - // End of SQL string - break - } - - // Extract statement arguments - let bindings = try arguments.extractBindings(forStatement: statement, allowingRemainingValues: true) - // unsafe is OK because we just extracted the correct number of arguments - statement.unsafeSetArguments(StatementArguments(bindings)) - - // Execute - try statement.execute() - - // Next - statementStart = statementEnd! - } - } - - // Check that all arguments were consumed: it is a programmer error to - // provide arguments that do not match the statement. - if arguments.values.isEmpty == false { - throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "wrong number of statement arguments: \(initialValuesCount)") - } - } -} - -extension Database { - - func updateStatementWillExecute(_ statement: UpdateStatement) { - observationBroker.updateStatementWillExecute(statement) - } - - func updateStatementDidExecute(_ statement: UpdateStatement) throws { - if statement.invalidatesDatabaseSchemaCache { - clearSchemaCache() - } - - try observationBroker.updateStatementDidExecute(statement) - } - - func updateStatementDidFail(_ statement: UpdateStatement) throws { - // Failed statements can not be reused, because sqlite3_reset won't - // be able to restore the statement to its initial state: - // https://www.sqlite.org/c3ref/reset.html - // - // So make sure we clear this statement from the cache. - internalStatementCache.remove(statement) - publicStatementCache.remove(statement) - - try observationBroker.updateStatementDidFail(statement) - } - - func selectStatementDidFail(_ statement: SelectStatement) { - // Failed statements can not be reused, because sqlite3_reset won't - // be able to restore the statement to its initial state: - // https://www.sqlite.org/c3ref/reset.html - // - // So make sure we clear this statement from the cache. - internalStatementCache.remove(statement) - publicStatementCache.remove(statement) - } -} - -/// A thread-unsafe statement cache -struct StatementCache { - unowned let db: Database - private var selectStatements: [String: SelectStatement] = [:] - private var updateStatements: [String: UpdateStatement] = [:] - - init(database: Database) { - self.db = database - } - - mutating func selectStatement(_ sql: String) throws -> SelectStatement { - if let statement = selectStatements[sql] { - return statement - } - - // http://www.sqlite.org/c3ref/c_prepare_persistent.html#sqlitepreparepersistent - // > The SQLITE_PREPARE_PERSISTENT flag is a hint to the query - // > planner that the prepared statement will be retained for a long - // > time and probably reused many times. - // - // This looks like a perfect match for cached statements. - // - // However SQLITE_PREPARE_PERSISTENT was only introduced in - // SQLite 3.20.0 http://www.sqlite.org/changes.html#version_3_20 - #if GRDBCUSTOMSQLITE || GRDBCIPHER - let statement = try db.makeSelectStatement(sql: sql, prepFlags: SQLITE_PREPARE_PERSISTENT) - #else - let statement: SelectStatement - if #available(iOS 12.0, OSX 10.14, watchOS 5.0, *) { - // SQLite 3.24.0 or more - statement = try db.makeSelectStatement(sql: sql, prepFlags: SQLITE_PREPARE_PERSISTENT) - } else { - // SQLite 3.19.3 or less - statement = try db.makeSelectStatement(sql: sql) - } - #endif - selectStatements[sql] = statement - return statement - } - - mutating func updateStatement(_ sql: String) throws -> UpdateStatement { - if let statement = updateStatements[sql] { - return statement - } - - // http://www.sqlite.org/c3ref/c_prepare_persistent.html#sqlitepreparepersistent - // > The SQLITE_PREPARE_PERSISTENT flag is a hint to the query - // > planner that the prepared statement will be retained for a long - // > time and probably reused many times. - // - // This looks like a perfect match for cached statements. - // - // However SQLITE_PREPARE_PERSISTENT was only introduced in - // SQLite 3.20.0 http://www.sqlite.org/changes.html#version_3_20 - #if GRDBCUSTOMSQLITE || GRDBCIPHER - let statement = try db.makeUpdateStatement(sql: sql, prepFlags: SQLITE_PREPARE_PERSISTENT) - #else - let statement: UpdateStatement - if #available(iOS 12.0, OSX 10.14, watchOS 5.0, *) { - // SQLite 3.24.0 or more - statement = try db.makeUpdateStatement(sql: sql, prepFlags: SQLITE_PREPARE_PERSISTENT) - } else { - // SQLite 3.19.3 or less - statement = try db.makeUpdateStatement(sql: sql) - } - #endif - updateStatements[sql] = statement - return statement - } - - mutating func clear() { - updateStatements = [:] - selectStatements = [:] - } - - mutating func remove(_ statement: SelectStatement) { - selectStatements.removeFirst { $0.value === statement } - } - - mutating func remove(_ statement: UpdateStatement) { - updateStatements.removeFirst { $0.value === statement } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Database.swift b/Example/Pods/GRDB.swift/GRDB/Core/Database.swift deleted file mode 100755 index edf239a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Database.swift +++ /dev/null @@ -1,1087 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// A raw SQLite connection, suitable for the SQLite C API. -public typealias SQLiteConnection = OpaquePointer - -/// A raw SQLite function argument. -typealias SQLiteValue = OpaquePointer - -let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self) - -/// A Database connection. -/// -/// You don't create a database directly. Instead, you use a DatabaseQueue, or -/// a DatabasePool: -/// -/// let dbQueue = DatabaseQueue(...) -/// -/// // The Database is the `db` in the closure: -/// try dbQueue.write { db in -/// try Player(...).insert(db) -/// } -public final class Database { - // The Database class is not thread-safe. An instance should always be - // used through a SerializedDatabase. - - // MARK: - SQLite C API - - /// The raw SQLite connection, suitable for the SQLite C API. - public let sqliteConnection: SQLiteConnection - - // MARK: - Configuration - - /// The error logging function. - /// - /// Quoting https://www.sqlite.org/errlog.html: - /// - /// > SQLite can be configured to invoke a callback function containing an - /// > error code and a terse error message whenever anomalies occur. This - /// > mechanism is very helpful in tracking obscure problems that occur - /// > rarely and in the field. Application developers are encouraged to take - /// > advantage of the error logging facility of SQLite in their products, - /// > as it is very low CPU and memory cost but can be a huge aid - /// > for debugging. - public static var logError: LogErrorFunction? = nil { - didSet { - if logError != nil { - registerErrorLogCallback { (_, code, message) in - guard let logError = Database.logError else { return } - guard let message = message.map({ String(cString: $0) }) else { return } - let resultCode = ResultCode(rawValue: code) - logError(resultCode, message) - } - } else { - registerErrorLogCallback(nil) - } - } - } - - /// The database configuration - public let configuration: Configuration - - // MARK: - Database Information - - /// The rowID of the most recently inserted row. - /// - /// If no row has ever been inserted using this database connection, - /// returns zero. - /// - /// For more detailed information, see https://www.sqlite.org/c3ref/last_insert_rowid.html - public var lastInsertedRowID: Int64 { - SchedulingWatchdog.preconditionValidQueue(self) - return sqlite3_last_insert_rowid(sqliteConnection) - } - - /// The number of rows modified, inserted or deleted by the most recent - /// successful INSERT, UPDATE or DELETE statement. - /// - /// For more detailed information, see https://www.sqlite.org/c3ref/changes.html - public var changesCount: Int { - SchedulingWatchdog.preconditionValidQueue(self) - return Int(sqlite3_changes(sqliteConnection)) - } - - /// The total number of rows modified, inserted or deleted by all successful - /// INSERT, UPDATE or DELETE statements since the database connection was - /// opened. - /// - /// For more detailed information, see https://www.sqlite.org/c3ref/total_changes.html - public var totalChangesCount: Int { - SchedulingWatchdog.preconditionValidQueue(self) - return Int(sqlite3_total_changes(sqliteConnection)) - } - - /// True if the database connection is currently in a transaction. - public var isInsideTransaction: Bool { - // https://sqlite.org/c3ref/get_autocommit.html - // - // > The sqlite3_get_autocommit() interface returns non-zero or zero if - // > the given database connection is or is not in autocommit mode, - // > respectively. - // - // > Autocommit mode is on by default. Autocommit mode is disabled by a - // > BEGIN statement. Autocommit mode is re-enabled by a COMMIT - // > or ROLLBACK. - // - // > If another thread changes the autocommit status of the database - // > connection while this routine is running, then the return value - // > is undefined. - SchedulingWatchdog.preconditionValidQueue(self) - if isClosed { return false } // Support for SerializedDatabasae.deinit - return sqlite3_get_autocommit(sqliteConnection) == 0 - } - - // MARK: - Internal properties - - // Caches - var schemaCache: DatabaseSchemaCache // internal so that it can be tested - lazy var internalStatementCache = StatementCache(database: self) - lazy var publicStatementCache = StatementCache(database: self) - - // Errors - var lastErrorCode: ResultCode { return ResultCode(rawValue: sqlite3_errcode(sqliteConnection)) } - var lastErrorMessage: String? { return String(cString: sqlite3_errmsg(sqliteConnection)) } - - // Statement authorizer - var authorizer: StatementAuthorizer? { - didSet { - switch (oldValue, authorizer) { - case (.some, nil): - sqlite3_set_authorizer(sqliteConnection, nil, nil) - case (nil, .some): - let dbPointer = Unmanaged.passUnretained(self).toOpaque() - sqlite3_set_authorizer(sqliteConnection, { (dbPointer, actionCode, cString1, cString2, cString3, cString4) -> Int32 in - guard let dbPointer = dbPointer else { return SQLITE_OK } - let db = Unmanaged.fromOpaque(dbPointer).takeUnretainedValue() - return db.authorizer!.authorize(actionCode, cString1, cString2, cString3, cString4) - }, dbPointer) - default: - break - } - } - } - - // Transaction observers management - lazy var observationBroker = DatabaseObservationBroker(self) - - /// The list of compile options used when building SQLite - static let sqliteCompileOptions: Set = DatabaseQueue().inDatabase { - try! Set(String.fetchCursor($0, sql: "PRAGMA COMPILE_OPTIONS")) - } - - // MARK: - Private properties - - private var busyCallback: BusyCallback? - - private var functions = Set() - private var collations = Set() - - private var isClosed: Bool = false - - // MARK: - Initializer - - init(path: String, configuration: Configuration, schemaCache: DatabaseSchemaCache) throws { - self.sqliteConnection = try Database.openConnection(path: path, flags: configuration.SQLiteOpenFlags) - self.configuration = configuration - self.schemaCache = schemaCache - } - - deinit { - assert(isClosed) - } -} - -extension Database { - - // MARK: - Database Opening - - private static func openConnection(path: String, flags: Int32) throws -> SQLiteConnection { - // See https://www.sqlite.org/c3ref/open.html - var sqliteConnection: SQLiteConnection? = nil - let code = sqlite3_open_v2(path, &sqliteConnection, flags, nil) - guard code == SQLITE_OK else { - // https://www.sqlite.org/c3ref/open.html - // > Whether or not an error occurs when it is opened, resources - // > associated with the database connection handle should be - // > released by passing it to sqlite3_close() when it is no - // > longer required. - // - // https://www.sqlite.org/c3ref/close.html - // > Calling sqlite3_close() or sqlite3_close_v2() with a NULL - // > pointer argument is a harmless no-op. - sqlite3_close(sqliteConnection) - throw DatabaseError(resultCode: code) - } - if let sqliteConnection = sqliteConnection { - return sqliteConnection - } - throw DatabaseError(resultCode: .SQLITE_INTERNAL) // WTF SQLite? - } -} - -extension Database { - - // MARK: - Database Setup - - /// This method must be called after database initialization - func setup() throws { - // Setup trace first, so that setup queries are traced. - setupTrace() - try setupForeignKeys() - setupBusyMode() - setupDefaultFunctions() - setupDefaultCollations() - observationBroker.installCommitAndRollbackHooks() - try activateExtendedCodes() - - #if SQLITE_HAS_CODEC - try validateSQLCipher() - if let passphrase = configuration.passphrase { - try setCipherPassphrase(passphrase) - } - #endif - - // Last step before we can start accessing the database. - // This is the opportunity to run SQLCipher configuration - // pragmas such as cipher_page_size, for example. - try configuration.prepareDatabase?(self) - try validateFormat() - configuration.SQLiteConnectionDidOpen?() - } - - private func setupTrace() { - guard configuration.trace != nil else { - return - } - // sqlite3_trace_v2 and sqlite3_expanded_sql were introduced in SQLite 3.14.0 http://www.sqlite.org/changes.html#version_3_14 - // It is available from iOS 10.0 and OS X 10.12 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - #if GRDBCUSTOMSQLITE || GRDBCIPHER - let dbPointer = Unmanaged.passUnretained(self).toOpaque() - sqlite3_trace_v2(sqliteConnection, UInt32(SQLITE_TRACE_STMT), { (mask, dbPointer, stmt, unexpandedSQL) -> Int32 in - return Database.trace_v2(mask, dbPointer, stmt, unexpandedSQL, sqlite3_expanded_sql) - }, dbPointer) - #elseif os(Linux) - setupTrace_v1() - #else - if #available(iOS 10.0, OSX 10.12, watchOS 3.0, *) { - let dbPointer = Unmanaged.passUnretained(self).toOpaque() - sqlite3_trace_v2(sqliteConnection, UInt32(SQLITE_TRACE_STMT), { (mask, dbPointer, stmt, unexpandedSQL) -> Int32 in - return Database.trace_v2(mask, dbPointer, stmt, unexpandedSQL, sqlite3_expanded_sql) - }, dbPointer) - } else { - setupTrace_v1() - } - #endif - } - - // Precondition: configuration.trace != nil - private func setupTrace_v1() { - let dbPointer = Unmanaged.passUnretained(self).toOpaque() - sqlite3_trace(sqliteConnection, { (dbPointer, sql) in - guard let sql = sql.map({ String(cString: $0) }) else { return } - let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue() - db.configuration.trace!(sql) - }, dbPointer) - } - - // Precondition: configuration.trace != nil - private static func trace_v2( - _ mask: UInt32, - _ dbPointer: UnsafeMutableRawPointer?, - _ stmt: UnsafeMutableRawPointer?, - _ unexpandedSQL: UnsafeMutableRawPointer?, - _ sqlite3_expanded_sql: @convention(c) (OpaquePointer?) -> UnsafeMutablePointer?) - -> Int32 - { - guard let stmt = stmt else { return SQLITE_OK } - guard let expandedSQLCString = sqlite3_expanded_sql(OpaquePointer(stmt)) else { return SQLITE_OK } - let sql = String(cString: expandedSQLCString) - sqlite3_free(expandedSQLCString) - let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue() - db.configuration.trace!(sql) - return SQLITE_OK - } - - private func setupForeignKeys() throws { - // Foreign keys are disabled by default with SQLite3 - if configuration.foreignKeysEnabled { - try execute(sql: "PRAGMA foreign_keys = ON") - } - } - - private func setupBusyMode() { - switch configuration.busyMode { - case .immediateError: - break - - case .timeout(let duration): - let milliseconds = Int32(duration * 1000) - sqlite3_busy_timeout(sqliteConnection, milliseconds) - - case .callback(let callback): - busyCallback = callback - let dbPointer = Unmanaged.passUnretained(self).toOpaque() - sqlite3_busy_handler( - sqliteConnection, - { (dbPointer, numberOfTries) in - let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue() - let callback = db.busyCallback! - return callback(Int(numberOfTries)) ? 1 : 0 - }, - dbPointer) - } - } - - private func setupDefaultFunctions() { - add(function: .capitalize) - add(function: .lowercase) - add(function: .uppercase) - - if #available(OSX 10.11, watchOS 3.0, *) { - add(function: .localizedCapitalize) - add(function: .localizedLowercase) - add(function: .localizedUppercase) - } - } - - private func setupDefaultCollations() { - add(collation: .unicodeCompare) - add(collation: .caseInsensitiveCompare) - add(collation: .localizedCaseInsensitiveCompare) - add(collation: .localizedCompare) - add(collation: .localizedStandardCompare) - } - - private func activateExtendedCodes() throws { - let code = sqlite3_extended_result_codes(sqliteConnection, 1) - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection))) - } - } - - #if SQLITE_HAS_CODEC - private func validateSQLCipher() throws { - // https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688 - // - // > In order to avoid situations where SQLite might be used - // > improperly at runtime, we strongly recommend that - // > applications institute a runtime test to ensure that the - // > application is actually using SQLCipher on the active - // > connection. - if try String.fetchOne(self, sql: "PRAGMA cipher_version") == nil { - throw DatabaseError(resultCode: .SQLITE_MISUSE, message: """ - GRDB is not linked against SQLCipher. \ - Check https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688 - """) - } - } - - private func setCipherPassphrase(_ passphrase: String) throws { - let data = passphrase.data(using: .utf8)! - #if swift(>=5.0) - let code = data.withUnsafeBytes { - sqlite3_key(sqliteConnection, $0.baseAddress, Int32($0.count)) - } - #else - let code = data.withUnsafeBytes { - sqlite3_key(sqliteConnection, $0, Int32(data.count)) - } - #endif - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection))) - } - } - #endif - - private func validateFormat() throws { - // Users are surprised when they open a picture as a database and - // see no error (https://github.com/groue/GRDB.swift/issues/54). - // - // So let's fail early if file is not a database, or encrypted with - // another passphrase. - try makeSelectStatement(sql: "SELECT * FROM sqlite_master LIMIT 1").makeCursor().next() - } -} - -extension Database { - - // MARK: - Database Closing - - /// This method must be called before database deallocation - func close() { - SchedulingWatchdog.preconditionValidQueue(self) - assert(!isClosed) - - configuration.SQLiteConnectionWillClose?(sqliteConnection) - internalStatementCache.clear() - publicStatementCache.clear() - Database.closeConnection(sqliteConnection) - isClosed = true - configuration.SQLiteConnectionDidClose?() - } - - private static func closeConnection(_ sqliteConnection: SQLiteConnection) { - // sqlite3_close_v2 was added in SQLite 3.7.14 http://www.sqlite.org/changes.html#version_3_7_14 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - #if GRDBCUSTOMSQLITE || GRDBCIPHER - closeConnection_v2(sqliteConnection, sqlite3_close_v2) - #else - if #available(OSX 10.10, OSXApplicationExtension 10.10, *) { - closeConnection_v2(sqliteConnection, sqlite3_close_v2) - } else { - closeConnection_v1(sqliteConnection) - } - #endif - } - - private static func closeConnection_v1(_ sqliteConnection: SQLiteConnection) { - // https://www.sqlite.org/c3ref/close.html - // > If the database connection is associated with unfinalized prepared - // > statements or unfinished sqlite3_backup objects then - // > sqlite3_close() will leave the database connection open and - // > return SQLITE_BUSY. - let code = sqlite3_close(sqliteConnection) - if code != SQLITE_OK, let log = logError { - // A rare situation where GRDB doesn't fatalError on - // unprocessed errors. - let message = String(cString: sqlite3_errmsg(sqliteConnection)) - log(ResultCode(rawValue: code), "could not close database: \(message)") - if code == SQLITE_BUSY { - // Let the user know about unfinalized statements that did - // prevent the connection from closing properly. - var stmt: SQLiteStatement? = sqlite3_next_stmt(sqliteConnection, nil) - while stmt != nil { - log(ResultCode(rawValue: code), "unfinalized statement: \(String(cString: sqlite3_sql(stmt)))") - stmt = sqlite3_next_stmt(sqliteConnection, stmt) - } - } - } - } - - private static func closeConnection_v2( - _ sqliteConnection: SQLiteConnection, - _ sqlite3_close_v2: @convention(c) (OpaquePointer?) -> Int32) - { - // https://www.sqlite.org/c3ref/close.html - // > If sqlite3_close_v2() is called with unfinalized prepared - // > statements and/or unfinished sqlite3_backups, then the database - // > connection becomes an unusable "zombie" which will automatically - // > be deallocated when the last prepared statement is finalized or the - // > last sqlite3_backup is finished. - let code = sqlite3_close_v2(sqliteConnection) - if code != SQLITE_OK, let log = logError { - // A rare situation where GRDB doesn't fatalError on - // unprocessed errors. - let message = String(cString: sqlite3_errmsg(sqliteConnection)) - log(ResultCode(rawValue: code), "could not close database: \(message)") - } - } -} - -extension Database { - - // MARK: - Functions - - /// Add or redefine an SQL function. - /// - /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in - /// guard let int = Int.fromDatabaseValue(dbValues[0]) else { - /// return nil - /// } - /// return int + 1 - /// } - /// db.add(function: fn) - /// try Int.fetchOne(db, sql: "SELECT succ(1)")! // 2 - public func add(function: DatabaseFunction) { - functions.update(with: function) - function.install(in: self) - } - - /// Remove an SQL function. - public func remove(function: DatabaseFunction) { - functions.remove(function) - function.uninstall(in: self) - } -} - -extension Database { - - // MARK: - Collations - - /// Add or redefine a collation. - /// - /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in - /// return (string1 as NSString).localizedStandardCompare(string2) - /// } - /// db.add(collation: collation) - /// try db.execute(sql: "CREATE TABLE files (name TEXT COLLATE localized_standard") - public func add(collation: DatabaseCollation) { - collations.update(with: collation) - let collationPointer = Unmanaged.passUnretained(collation).toOpaque() - let code = sqlite3_create_collation_v2( - sqliteConnection, - collation.name, - SQLITE_UTF8, - collationPointer, - { (collationPointer, length1, buffer1, length2, buffer2) -> Int32 in - let collation = Unmanaged.fromOpaque(collationPointer!).takeUnretainedValue() - return Int32(collation.function(length1, buffer1, length2, buffer2).rawValue) - }, nil) - guard code == SQLITE_OK else { - // Assume a GRDB bug: there is no point throwing any error. - fatalError(DatabaseError(resultCode: code, message: lastErrorMessage).description) - } - } - - /// Remove a collation. - public func remove(collation: DatabaseCollation) { - collations.remove(collation) - sqlite3_create_collation_v2( - sqliteConnection, - collation.name, - SQLITE_UTF8, - nil, nil, nil) - } -} - -extension Database { - - // MARK: - Read-Only Access - - /// Grants read-only access, starting SQLite 3.8.0 - func readOnly(_ block: () throws -> T) rethrows -> T { - if configuration.readonly { - return try block() - } - - // query_only pragma was added in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - // Assume those pragmas never fail - try! internalCachedUpdateStatement(sql: "PRAGMA query_only = 1").execute() - defer { try! internalCachedUpdateStatement(sql: "PRAGMA query_only = 0").execute() } - return try block() - } -} - -extension Database { - - // MARK: - Transactions & Savepoint - - /// Executes a block inside a database transaction. - /// - /// try dbQueue.inDatabase do { - /// try db.inTransaction { - /// try db.execute(sql: "INSERT ...") - /// return .commit - /// } - /// } - /// - /// If the block throws an error, the transaction is rollbacked and the - /// error is rethrown. - /// - /// This method is not reentrant: you can't nest transactions. - /// - /// - parameters: - /// - kind: The transaction type (default nil). If nil, the transaction - /// type is configuration.defaultTransactionKind, which itself - /// defaults to .deferred. See https://www.sqlite.org/lang_transaction.html - /// for more information. - /// - block: A block that executes SQL statements and return either - /// .commit or .rollback. - /// - throws: The error thrown by the block. - public func inTransaction(_ kind: TransactionKind? = nil, _ block: () throws -> TransactionCompletion) throws { - // Begin transaction - try beginTransaction(kind) - - // Now that transaction has begun, we'll rollback in case of error. - // But we'll throw the first caught error, so that user knows - // what happened. - var firstError: Error? = nil - let needsRollback: Bool - do { - let completion = try block() - switch completion { - case .commit: - try commit() - needsRollback = false - case .rollback: - needsRollback = true - } - } catch { - firstError = error - needsRollback = true - } - - if needsRollback { - do { - try rollback() - } catch { - if firstError == nil { - firstError = error - } - } - } - - if let firstError = firstError { - throw firstError - } - } - - /// Executes a block inside a savepoint. - /// - /// try dbQueue.inDatabase do { - /// try db.inSavepoint { - /// try db.execute(sql: "INSERT ...") - /// return .commit - /// } - /// } - /// - /// If the block throws an error, the savepoint is rollbacked and the - /// error is rethrown. - /// - /// This method is reentrant: you can nest savepoints. - /// - /// - parameter block: A block that executes SQL statements and return - /// either .commit or .rollback. - /// - throws: The error thrown by the block. - public func inSavepoint(_ block: () throws -> TransactionCompletion) throws { - // By default, top level SQLite savepoints open a deferred transaction. - // - // But GRDB database configuration mandates a default transaction kind - // that we have to honor. - // - // So when the default GRDB transaction kind is not deferred, we open a - // transaction instead - if !isInsideTransaction && configuration.defaultTransactionKind != .deferred { - return try inTransaction(configuration.defaultTransactionKind, block) - } - - // If the savepoint is top-level, we'll use ROLLBACK TRANSACTION in - // order to perform the special error handling of rollbacks (see - // the rollback method). - let topLevelSavepoint = !isInsideTransaction - - // Begin savepoint - // - // We use a single name for savepoints because there is no need - // using unique savepoint names. User could still mess with them - // with raw SQL queries, but let's assume that it is unlikely that - // the user uses "grdb" as a savepoint name. - try execute(sql: "SAVEPOINT grdb") - - // Now that savepoint has begun, we'll rollback in case of error. - // But we'll throw the first caught error, so that user knows - // what happened. - var firstError: Error? = nil - let needsRollback: Bool - do { - let completion = try block() - switch completion { - case .commit: - try execute(sql: "RELEASE SAVEPOINT grdb") - assert(!topLevelSavepoint || !isInsideTransaction) - needsRollback = false - case .rollback: - needsRollback = true - } - } catch { - firstError = error - needsRollback = true - } - - if needsRollback { - do { - if topLevelSavepoint { - try rollback() - } else { - // Rollback, and release the savepoint. - // Rollback alone is not enough to clear the savepoint from - // the SQLite savepoint stack. - try execute(sql: "ROLLBACK TRANSACTION TO SAVEPOINT grdb") - try execute(sql: "RELEASE SAVEPOINT grdb") - } - } catch { - if firstError == nil { - firstError = error - } - } - } - - if let firstError = firstError { - throw firstError - } - } - - /// Begins a database transaction. - /// - /// - parameter kind: The transaction type (default nil). If nil, the - /// transaction type is configuration.defaultTransactionKind, which itself - /// defaults to .deferred. See https://www.sqlite.org/lang_transaction.html - /// for more information. - /// - throws: The error thrown by the block. - public func beginTransaction(_ kind: TransactionKind? = nil) throws { - let kind = kind ?? configuration.defaultTransactionKind - try execute(sql: "BEGIN \(kind.rawValue) TRANSACTION") - assert(isInsideTransaction) - } - - /// Begins a database transaction and take a snapshot of the last committed - /// database state. - func beginSnapshotIsolation() throws { - // https://www.sqlite.org/isolation.html - // - // > In WAL mode, SQLite exhibits "snapshot isolation". When a read - // > transaction starts, that reader continues to see an unchanging - // > "snapshot" of the database file as it existed at the moment in time - // > when the read transaction started. Any write transactions that - // > commit while the read transaction is active are still invisible to - // > the read transaction, because the reader is seeing a snapshot of - // > database file from a prior moment in time. - // - // That's exactly what we need. But what does "when read transaction - // starts" mean? - // - // http://www.sqlite.org/lang_transaction.html - // - // > Deferred [transaction] means that no locks are acquired on the - // > database until the database is first accessed. [...] Locks are not - // > acquired until the first read or write operation. [...] Because the - // > acquisition of locks is deferred until they are needed, it is - // > possible that another thread or process could create a separate - // > transaction and write to the database after the BEGIN on the - // > current thread has executed. - // - // Now that's precise enough: SQLite defers "snapshot isolation" until - // the first SELECT: - // - // Reader Writer - // BEGIN DEFERRED TRANSACTION - // UPDATE ... (1) - // Here the change (1) is visible - // SELECT ... - // UPDATE ... (2) - // Here the change (2) is not visible - // - // We thus have to perform a select that establishes the - // snapshot isolation before we release the writer queue: - // - // Reader Writer - // BEGIN DEFERRED TRANSACTION - // SELECT anything - // UPDATE ... - // Here the change is not visible by GRDB user - try beginTransaction(.deferred) - try internalCachedSelectStatement(sql: "SELECT rootpage FROM sqlite_master LIMIT 1").makeCursor().next() - } - - /// Rollbacks a database transaction. - public func rollback() throws { - // The SQLite documentation contains two related but distinct techniques - // to handle rollbacks and errors: - // - // https://www.sqlite.org/lang_transaction.html#immediate - // - // > Response To Errors Within A Transaction - // > - // > If certain kinds of errors occur within a transaction, the - // > transaction may or may not be rolled back automatically. - // > The errors that can cause an automatic rollback include: - // > - // > - SQLITE_FULL: database or disk full - // > - SQLITE_IOERR: disk I/O error - // > - SQLITE_BUSY: database in use by another process - // > - SQLITE_NOMEM: out or memory - // > - // > [...] It is recommended that applications respond to the - // > errors listed above by explicitly issuing a ROLLBACK - // > command. If the transaction has already been rolled back - // > automatically by the error response, then the ROLLBACK - // > command will fail with an error, but no harm is caused - // > by this. - // - // https://sqlite.org/c3ref/get_autocommit.html - // - // > The sqlite3_get_autocommit() interface returns non-zero or zero if - // > the given database connection is or is not in autocommit mode, - // > respectively. - // > - // > [...] If certain kinds of errors occur on a statement within a - // > multi-statement transaction (errors including SQLITE_FULL, - // > SQLITE_IOERR, SQLITE_NOMEM, SQLITE_BUSY, and SQLITE_INTERRUPT) then - // > the transaction might be rolled back automatically. The only way to - // > find out whether SQLite automatically rolled back the transaction - // > after an error is to use this function. - // - // The second technique is more robust, because we don't have to guess - // which rollback errors should be ignored, and which rollback errors - // should be exposed to the library user. - SchedulingWatchdog.preconditionValidQueue(self) // guard sqlite3_get_autocommit - if sqlite3_get_autocommit(sqliteConnection) == 0 { - try execute(sql: "ROLLBACK TRANSACTION") - } - assert(!isInsideTransaction) - } - - /// Commits a database transaction. - public func commit() throws { - try execute(sql: "COMMIT TRANSACTION") - assert(!isInsideTransaction) - } -} - -extension Database { - - // MARK: - Memory Management - - func releaseMemory() { - sqlite3_db_release_memory(sqliteConnection) - schemaCache.clear() - internalStatementCache.clear() - publicStatementCache.clear() - } -} - -extension Database { - - // MARK: - Backup - - static func backup(from dbFrom: Database, to dbDest: Database, afterBackupInit: (() -> ())? = nil, afterBackupStep: (() -> ())? = nil) throws { - guard let backup = sqlite3_backup_init(dbDest.sqliteConnection, "main", dbFrom.sqliteConnection, "main") else { - throw DatabaseError(resultCode: dbDest.lastErrorCode, message: dbDest.lastErrorMessage) - } - guard Int(bitPattern: backup) != Int(SQLITE_ERROR) else { - throw DatabaseError(resultCode: .SQLITE_ERROR) - } - - afterBackupInit?() - - do { - backupLoop: while true { - switch sqlite3_backup_step(backup, -1) { - case SQLITE_DONE: - afterBackupStep?() - break backupLoop - case SQLITE_OK: - afterBackupStep?() - case let code: - throw DatabaseError(resultCode: code, message: dbDest.lastErrorMessage) - } - } - } catch { - sqlite3_backup_finish(backup) - throw error - } - - switch sqlite3_backup_finish(backup) { - case SQLITE_OK: - break - case let code: - throw DatabaseError(resultCode: code, message: dbDest.lastErrorMessage) - } - - // The schema of the destination database has changed: - dbDest.clearSchemaCache() - } -} - -#if SQLITE_HAS_CODEC - extension Database { - - // MARK: - Encryption - - func change(passphrase: String) throws { - // FIXME: sqlite3_rekey is discouraged. - // - // https://github.com/ccgus/fmdb/issues/547#issuecomment-259219320 - // - // > We (Zetetic) have been discouraging the use of sqlite3_rekey in - // > favor of attaching a new database with the desired encryption - // > options and using sqlcipher_export() to migrate the contents and - // > schema of the original db into the new one: - // > https://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-a-database-errors/ - let data = passphrase.data(using: .utf8)! - #if swift(>=5.0) - let code = data.withUnsafeBytes { - sqlite3_rekey(sqliteConnection, $0.baseAddress, Int32($0.count)) - } - #else - let code = data.withUnsafeBytes { - sqlite3_rekey(sqliteConnection, $0, Int32(data.count)) - } - #endif - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: lastErrorMessage) - } - } - } -#endif - -extension Database { - - /// See BusyMode and https://www.sqlite.org/c3ref/busy_handler.html - public typealias BusyCallback = (_ numberOfTries: Int) -> Bool - - /// When there are several connections to a database, a connection may try - /// to access the database while it is locked by another connection. - /// - /// The BusyMode enum describes the behavior of GRDB when such a situation - /// occurs: - /// - /// - .immediateError: The SQLITE_BUSY error is immediately returned to the - /// connection that tries to access the locked database. - /// - /// - .timeout: The SQLITE_BUSY error will be returned only if the database - /// remains locked for more than the specified duration. - /// - /// - .callback: Perform your custom lock handling. - /// - /// To set the busy mode of a database, use Configuration: - /// - /// // Wait 1 second before failing with SQLITE_BUSY - /// let configuration = Configuration(busyMode: .timeout(1)) - /// let dbQueue = DatabaseQueue(path: "...", configuration: configuration) - /// - /// Relevant SQLite documentation: - /// - /// - https://www.sqlite.org/c3ref/busy_timeout.html - /// - https://www.sqlite.org/c3ref/busy_handler.html - /// - https://www.sqlite.org/lang_transaction.html - /// - https://www.sqlite.org/wal.html - public enum BusyMode { - /// The SQLITE_BUSY error is immediately returned to the connection that - /// tries to access the locked database. - case immediateError - - /// The SQLITE_BUSY error will be returned only if the database remains - /// locked for more than the specified duration (in seconds). - case timeout(TimeInterval) - - /// A custom callback that is called when a database is locked. - /// See https://www.sqlite.org/c3ref/busy_handler.html - case callback(BusyCallback) - } - - /// The available [checkpoint modes](https://www.sqlite.org/c3ref/wal_checkpoint_v2.html). - public enum CheckpointMode: Int32 { - case passive = 0 // SQLITE_CHECKPOINT_PASSIVE - case full = 1 // SQLITE_CHECKPOINT_FULL - case restart = 2 // SQLITE_CHECKPOINT_RESTART - case truncate = 3 // SQLITE_CHECKPOINT_TRUNCATE - } - - /// A built-in SQLite collation. - /// - /// See https://www.sqlite.org/datatype3.html#collation - public struct CollationName : RawRepresentable, Hashable { - /// :nodoc: - public let rawValue: String - - /// :nodoc: - public init(rawValue: String) { - self.rawValue = rawValue - } - - public init(_ rawValue: String) { - self.rawValue = rawValue - } - - /// The `BINARY` built-in SQL collation - public static let binary = CollationName("BINARY") - - /// The `NOCASE` built-in SQL collation - public static let nocase = CollationName("NOCASE") - - /// The `RTRIM` built-in SQL collation - public static let rtrim = CollationName("RTRIM") - } - - /// An SQL column type. - /// - /// try db.create(table: "player") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("title", .text) - /// } - /// - /// See https://www.sqlite.org/datatype3.html - public struct ColumnType : RawRepresentable, Hashable { - /// :nodoc: - public let rawValue: String - - /// :nodoc: - public init(rawValue: String) { - self.rawValue = rawValue - } - - public init(_ rawValue: String) { - self.rawValue = rawValue - } - - /// The `TEXT` SQL column type - public static let text = ColumnType("TEXT") - - /// The `INTEGER` SQL column type - public static let integer = ColumnType("INTEGER") - - /// The `DOUBLE` SQL column type - public static let double = ColumnType("DOUBLE") - - /// The `NUMERIC` SQL column type - public static let numeric = ColumnType("NUMERIC") - - /// The `BOOLEAN` SQL column type - public static let boolean = ColumnType("BOOLEAN") - - /// The `BLOB` SQL column type - public static let blob = ColumnType("BLOB") - - /// The `DATE` SQL column type - public static let date = ColumnType("DATE") - - /// The `DATETIME` SQL column type - public static let datetime = ColumnType("DATETIME") - } - - /// An SQLite conflict resolution. - /// - /// See https://www.sqlite.org/lang_conflict.html. - public enum ConflictResolution : String { - case rollback = "ROLLBACK" - case abort = "ABORT" - case fail = "FAIL" - case ignore = "IGNORE" - case replace = "REPLACE" - } - - /// A foreign key action. - /// - /// See https://www.sqlite.org/foreignkeys.html - public enum ForeignKeyAction : String { - case cascade = "CASCADE" - case restrict = "RESTRICT" - case setNull = "SET NULL" - case setDefault = "SET DEFAULT" - } - - /// log function that takes an error message. - public typealias LogErrorFunction = (_ resultCode: ResultCode, _ message: String) -> Void - - /// The end of a transaction: Commit, or Rollback - public enum TransactionCompletion { - case commit - case rollback - } - - /// An SQLite transaction kind. See https://www.sqlite.org/lang_transaction.html - public enum TransactionKind : String { - case deferred = "DEFERRED" - case immediate = "IMMEDIATE" - case exclusive = "EXCLUSIVE" - } -} - -extension Database { - /// An SQLite threading mode. See https://www.sqlite.org/threadsafe.html. - enum ThreadingMode { - case `default` - case multiThread - case serialized - - var SQLiteOpenFlags: Int32 { - switch self { - case .`default`: - return 0 - case .multiThread: - return SQLITE_OPEN_NOMUTEX - case .serialized: - return SQLITE_OPEN_FULLMUTEX - } - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseCollation.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseCollation.swift deleted file mode 100755 index 530992c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseCollation.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// A Collation is a string comparison function used by SQLite. -public final class DatabaseCollation { - public let name: String - let function: (Int32, UnsafeRawPointer?, Int32, UnsafeRawPointer?) -> ComparisonResult - - /// Creates a collation. - /// - /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in - /// return (string1 as NSString).localizedStandardCompare(string2) - /// } - /// db.add(collation: collation) - /// try db.execute(sql: "CREATE TABLE file (name TEXT COLLATE localized_standard") - /// - /// - parameters: - /// - name: The function name. - /// - function: A function that compares two strings. - public init(_ name: String, function: @escaping (String, String) -> ComparisonResult) { - self.name = name - self.function = { (length1, buffer1, length2, buffer2) in - // Buffers are not C strings: they do not end with \0. - let string1 = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer1.unsafelyUnwrapped), length: Int(length1), encoding: .utf8, freeWhenDone: false)! - let string2 = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer2.unsafelyUnwrapped), length: Int(length2), encoding: .utf8, freeWhenDone: false)! - return function(string1, string2) - } - } -} - -extension DatabaseCollation: Hashable { - // Collation equality is based on the sqlite3_strnicmp SQLite function. - // (see https://www.sqlite.org/c3ref/create_collation.html). Computing - // a hash value that honors the Swift Hashable contract (value equality - // implies hash equality) is thus non trivial. But it's not that - // important, since this hashValue is only used when one adds - // or removes a collation from a database connection. - /// :nodoc: - public func hash(into hasher: inout Hasher) { - hasher.combine(0) - } - - /// Two collations are equal if they share the same name (case insensitive) - /// :nodoc: - public static func == (lhs: DatabaseCollation, rhs: DatabaseCollation) -> Bool { - // See https://www.sqlite.org/c3ref/create_collation.html - return sqlite3_stricmp(lhs.name, lhs.name) == 0 - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseError.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseError.swift deleted file mode 100755 index d912174..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseError.swift +++ /dev/null @@ -1,270 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -public struct ResultCode : RawRepresentable, Equatable, CustomStringConvertible { - public let rawValue: Int32 - - public init(rawValue: Int32) { - self.rawValue = rawValue - } - - /// A result code limited to the least significant 8 bits of the receiver. - /// See https://www.sqlite.org/rescode.html for more information. - /// - /// let resultCode = .SQLITE_CONSTRAINT_FOREIGNKEY - /// resultCode.primaryResultCode == .SQLITE_CONSTRAINT // true - public var primaryResultCode: ResultCode { - return ResultCode(rawValue: rawValue & 0xFF) - } - - var isPrimary: Bool { - return self == primaryResultCode - } - - /// Returns true if the code on the left matches the code on the right. - /// - /// Primary result codes match themselves and their extended result codes, - /// while extended result codes match only themselves: - /// - /// switch error.extendedResultCode { - /// case .SQLITE_CONSTRAINT_FOREIGNKEY: // foreign key constraint error - /// case .SQLITE_CONSTRAINT: // any other constraint error - /// default: // any other database error - /// } - public static func ~= (pattern: ResultCode, code: ResultCode) -> Bool { - if pattern.isPrimary { - return pattern == code.primaryResultCode - } else { - return pattern == code - } - } - - // Primary Result codes - // https://www.sqlite.org/rescode.html#primary_result_code_list - - public static let SQLITE_OK = ResultCode(rawValue: 0) // Successful result - public static let SQLITE_ERROR = ResultCode(rawValue: 1) // SQL error or missing database - public static let SQLITE_INTERNAL = ResultCode(rawValue: 2) // Internal logic error in SQLite - public static let SQLITE_PERM = ResultCode(rawValue: 3) // Access permission denied - public static let SQLITE_ABORT = ResultCode(rawValue: 4) // Callback routine requested an abort - public static let SQLITE_BUSY = ResultCode(rawValue: 5) // The database file is locked - public static let SQLITE_LOCKED = ResultCode(rawValue: 6) // A table in the database is locked - public static let SQLITE_NOMEM = ResultCode(rawValue: 7) // A malloc() failed - public static let SQLITE_READONLY = ResultCode(rawValue: 8) // Attempt to write a readonly database - public static let SQLITE_INTERRUPT = ResultCode(rawValue: 9) // Operation terminated by sqlite3_interrupt() - public static let SQLITE_IOERR = ResultCode(rawValue: 10) // Some kind of disk I/O error occurred - public static let SQLITE_CORRUPT = ResultCode(rawValue: 11) // The database disk image is malformed - public static let SQLITE_NOTFOUND = ResultCode(rawValue: 12) // Unknown opcode in sqlite3_file_control() - public static let SQLITE_FULL = ResultCode(rawValue: 13) // Insertion failed because database is full - public static let SQLITE_CANTOPEN = ResultCode(rawValue: 14) // Unable to open the database file - public static let SQLITE_PROTOCOL = ResultCode(rawValue: 15) // Database lock protocol error - public static let SQLITE_EMPTY = ResultCode(rawValue: 16) // Database is empty - public static let SQLITE_SCHEMA = ResultCode(rawValue: 17) // The database schema changed - public static let SQLITE_TOOBIG = ResultCode(rawValue: 18) // String or BLOB exceeds size limit - public static let SQLITE_CONSTRAINT = ResultCode(rawValue: 19) // Abort due to constraint violation - public static let SQLITE_MISMATCH = ResultCode(rawValue: 20) // Data type mismatch - public static let SQLITE_MISUSE = ResultCode(rawValue: 21) // Library used incorrectly - public static let SQLITE_NOLFS = ResultCode(rawValue: 22) // Uses OS features not supported on host - public static let SQLITE_AUTH = ResultCode(rawValue: 23) // Authorization denied - public static let SQLITE_FORMAT = ResultCode(rawValue: 24) // Auxiliary database format error - public static let SQLITE_RANGE = ResultCode(rawValue: 25) // 2nd parameter to sqlite3_bind out of range - public static let SQLITE_NOTADB = ResultCode(rawValue: 26) // File opened that is not a database file - public static let SQLITE_NOTICE = ResultCode(rawValue: 27) // Notifications from sqlite3_log() - public static let SQLITE_WARNING = ResultCode(rawValue: 28) // Warnings from sqlite3_log() - public static let SQLITE_ROW = ResultCode(rawValue: 100) // sqlite3_step() has another row ready - public static let SQLITE_DONE = ResultCode(rawValue: 101) // sqlite3_step() has finished executing - - // Extended Result Code - // https://www.sqlite.org/rescode.html#extended_result_code_list - - public static let SQLITE_IOERR_READ = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (1<<8))) - public static let SQLITE_IOERR_SHORT_READ = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (2<<8))) - public static let SQLITE_IOERR_WRITE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (3<<8))) - public static let SQLITE_IOERR_FSYNC = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (4<<8))) - public static let SQLITE_IOERR_DIR_FSYNC = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (5<<8))) - public static let SQLITE_IOERR_TRUNCATE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (6<<8))) - public static let SQLITE_IOERR_FSTAT = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (7<<8))) - public static let SQLITE_IOERR_UNLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (8<<8))) - public static let SQLITE_IOERR_RDLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (9<<8))) - public static let SQLITE_IOERR_DELETE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (10<<8))) - public static let SQLITE_IOERR_BLOCKED = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (11<<8))) - public static let SQLITE_IOERR_NOMEM = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (12<<8))) - public static let SQLITE_IOERR_ACCESS = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (13<<8))) - public static let SQLITE_IOERR_CHECKRESERVEDLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (14<<8))) - public static let SQLITE_IOERR_LOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (15<<8))) - public static let SQLITE_IOERR_CLOSE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (16<<8))) - public static let SQLITE_IOERR_DIR_CLOSE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (17<<8))) - public static let SQLITE_IOERR_SHMOPEN = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (18<<8))) - public static let SQLITE_IOERR_SHMSIZE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (19<<8))) - public static let SQLITE_IOERR_SHMLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (20<<8))) - public static let SQLITE_IOERR_SHMMAP = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (21<<8))) - public static let SQLITE_IOERR_SEEK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (22<<8))) - public static let SQLITE_IOERR_DELETE_NOENT = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (23<<8))) - public static let SQLITE_IOERR_MMAP = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (24<<8))) - public static let SQLITE_IOERR_GETTEMPPATH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (25<<8))) - public static let SQLITE_IOERR_CONVPATH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (26<<8))) - public static let SQLITE_IOERR_VNODE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (27<<8))) - public static let SQLITE_IOERR_AUTH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (28<<8))) - public static let SQLITE_LOCKED_SHAREDCACHE = ResultCode(rawValue: (SQLITE_LOCKED.rawValue | (1<<8))) - public static let SQLITE_BUSY_RECOVERY = ResultCode(rawValue: (SQLITE_BUSY.rawValue | (1<<8))) - public static let SQLITE_BUSY_SNAPSHOT = ResultCode(rawValue: (SQLITE_BUSY.rawValue | (2<<8))) - public static let SQLITE_CANTOPEN_NOTEMPDIR = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (1<<8))) - public static let SQLITE_CANTOPEN_ISDIR = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (2<<8))) - public static let SQLITE_CANTOPEN_FULLPATH = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (3<<8))) - public static let SQLITE_CANTOPEN_CONVPATH = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (4<<8))) - public static let SQLITE_CORRUPT_VTAB = ResultCode(rawValue: (SQLITE_CORRUPT.rawValue | (1<<8))) - public static let SQLITE_READONLY_RECOVERY = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (1<<8))) - public static let SQLITE_READONLY_CANTLOCK = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (2<<8))) - public static let SQLITE_READONLY_ROLLBACK = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (3<<8))) - public static let SQLITE_READONLY_DBMOVED = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (4<<8))) - public static let SQLITE_ABORT_ROLLBACK = ResultCode(rawValue: (SQLITE_ABORT.rawValue | (2<<8))) - public static let SQLITE_CONSTRAINT_CHECK = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (1<<8))) - public static let SQLITE_CONSTRAINT_COMMITHOOK = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (2<<8))) - public static let SQLITE_CONSTRAINT_FOREIGNKEY = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (3<<8))) - public static let SQLITE_CONSTRAINT_FUNCTION = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (4<<8))) - public static let SQLITE_CONSTRAINT_NOTNULL = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (5<<8))) - public static let SQLITE_CONSTRAINT_PRIMARYKEY = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (6<<8))) - public static let SQLITE_CONSTRAINT_TRIGGER = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (7<<8))) - public static let SQLITE_CONSTRAINT_UNIQUE = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (8<<8))) - public static let SQLITE_CONSTRAINT_VTAB = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (9<<8))) - public static let SQLITE_CONSTRAINT_ROWID = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (10<<8))) - public static let SQLITE_NOTICE_RECOVER_WAL = ResultCode(rawValue: (SQLITE_NOTICE.rawValue | (1<<8))) - public static let SQLITE_NOTICE_RECOVER_ROLLBACK = ResultCode(rawValue: (SQLITE_NOTICE.rawValue | (2<<8))) - public static let SQLITE_WARNING_AUTOINDEX = ResultCode(rawValue: (SQLITE_WARNING.rawValue | (1<<8))) - public static let SQLITE_AUTH_USER = ResultCode(rawValue: (SQLITE_AUTH.rawValue | (1<<8))) - public static let SQLITE_OK_LOAD_PERMANENTLY = ResultCode(rawValue: (SQLITE_OK.rawValue | (1<<8))) -} - -// CustomStringConvertible -extension ResultCode { - var errorString: String? { - // sqlite3_errstr was added in SQLite 3.7.15 http://www.sqlite.org/changes.html#version_3_7_15 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - #if GRDBCUSTOMSQLITE || GRDBCIPHER - return String(cString: sqlite3_errstr(rawValue)) - #else - if #available(OSX 10.10, OSXApplicationExtension 10.10, *) { - return String(cString: sqlite3_errstr(rawValue)) - } else { - return nil - } - #endif - } - - /// :nodoc: - public var description: String { - if let errorString = errorString { - return "\(rawValue) (\(errorString))" - } else { - return "\(rawValue)" - } - } -} - -/// DatabaseError wraps an SQLite error. -public struct DatabaseError : Error, CustomStringConvertible, CustomNSError { - - /// The SQLite error code (see - /// https://www.sqlite.org/rescode.html#primary_result_code_list). - /// - /// do { - /// ... - /// } catch let error as DatabaseError where error.resultCode == .SQL_CONSTRAINT { - /// // A constraint error - /// } - /// - /// This property returns a "primary result code", that is to say the least - /// significant 8 bits of any SQLite result code. See - /// https://www.sqlite.org/rescode.html for more information. - /// - /// See also `extendedResultCode`. - public var resultCode: ResultCode { - return extendedResultCode.primaryResultCode - } - - /// The SQLite extended error code (see - /// https://www.sqlite.org/rescode.html#extended_result_code_list). - /// - /// do { - /// ... - /// } catch let error as DatabaseError where error.extendedResultCode == .SQLITE_CONSTRAINT_FOREIGNKEY { - /// // A foreign key constraint error - /// } - /// - /// See also `resultCode`. - public let extendedResultCode: ResultCode - - /// The SQLite error message. - public let message: String? - - /// The SQL query that yielded the error (if relevant). - public let sql: String? - - /// Creates a Database Error - public init(resultCode: ResultCode = .SQLITE_ERROR, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) { - self.extendedResultCode = resultCode - self.message = message ?? resultCode.errorString - self.sql = sql - self.arguments = arguments - } - - /// Creates a Database Error with a raw Int32 result code. - /// - /// This initializer is not public because library user is not supposed to - /// be exposed to raw result codes. - init(resultCode: Int32, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) { - self.init(resultCode: ResultCode(rawValue: resultCode), message: message, sql: sql, arguments: arguments) - } - - // MARK: Not public - - /// The query arguments that yielded the error (if relevant). - /// Not public because the StatementArguments class has no public method. - let arguments: StatementArguments? -} - -// CustomStringConvertible -extension DatabaseError { - /// :nodoc: - public var description: String { - var description = "SQLite error \(resultCode.rawValue)" - if let sql = sql { - description += " with statement `\(sql)`" - } - if let arguments = arguments, !arguments.isEmpty { - description += " arguments \(arguments)" - } - if let message = message { - description += ": \(message)" - } - return description - } -} - -// CustomNSError -extension DatabaseError { - - /// NSError bridging: the domain of the error. - /// :nodoc: - public static var errorDomain: String { - return "GRDB.DatabaseError" - } - - /// NSError bridging: the error code within the given domain. - /// :nodoc: - public var errorCode: Int { - return Int(extendedResultCode.rawValue) - } - - /// NSError bridging: the user-info dictionary. - /// :nodoc: - public var errorUserInfo: [String : Any] { - return [NSLocalizedDescriptionKey: description] - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseFunction.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseFunction.swift deleted file mode 100755 index 0a9f4e0..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseFunction.swift +++ /dev/null @@ -1,383 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// An SQL function or aggregate. -public final class DatabaseFunction: Hashable { - // SQLite identifies functions by (name + argument count) - private struct Identity: Hashable { - let name: String - let nArg: Int32 // -1 for variadic functions - } - - public var name: String { return identity.name } - private let identity: Identity - let pure: Bool - private let kind: Kind - private var eTextRep: Int32 { return (SQLITE_UTF8 | (pure ? SQLITE_DETERMINISTIC : 0)) } - - /// Returns an SQL function. - /// - /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in - /// guard let int = Int.fromDatabaseValue(dbValues[0]) else { - /// return nil - /// } - /// return int + 1 - /// } - /// db.add(function: fn) - /// try Int.fetchOne(db, sql: "SELECT succ(1)")! // 2 - /// - /// - parameters: - /// - name: The function name. - /// - argumentCount: The number of arguments of the function. If - /// omitted, or nil, the function accepts any number of arguments. - /// - pure: Whether the function is "pure", which means that its results - /// only depends on its inputs. When a function is pure, SQLite has - /// the opportunity to perform additional optimizations. Default value - /// is false. - /// - function: A function that takes an array of DatabaseValue - /// arguments, and returns an optional DatabaseValueConvertible such - /// as Int, String, NSDate, etc. The array is guaranteed to have - /// exactly *argumentCount* elements, provided *argumentCount* is - /// not nil. - public init(_ name: String, argumentCount: Int32? = nil, pure: Bool = false, function: @escaping ([DatabaseValue]) throws -> DatabaseValueConvertible?) { - self.identity = Identity(name: name, nArg: argumentCount ?? -1) - self.pure = pure - self.kind = .function{ (argc, argv) in - let arguments = (0.. DatabaseValueConvertible? { - /// return sum - /// } - /// } - /// - /// let dbQueue = DatabaseQueue() - /// let fn = DatabaseFunction("mysum", argumentCount: 1, aggregate: MySum.self) - /// dbQueue.add(function: fn) - /// try dbQueue.write { db in - /// try db.execute(sql: "CREATE TABLE test(i)") - /// try db.execute(sql: "INSERT INTO test(i) VALUES (1)") - /// try db.execute(sql: "INSERT INTO test(i) VALUES (2)") - /// try Int.fetchOne(db, sql: "SELECT mysum(i) FROM test")! // 3 - /// } - /// - /// - parameters: - /// - name: The function name. - /// - argumentCount: The number of arguments of the aggregate. If - /// omitted, or nil, the aggregate accepts any number of arguments. - /// - pure: Whether the aggregate is "pure", which means that its - /// results only depends on its inputs. When an aggregate is pure, - /// SQLite has the opportunity to perform additional optimizations. - /// Default value is false. - /// - aggregate: A type that implements the DatabaseAggregate protocol. - /// For each step of the aggregation, its `step` method is called with - /// an array of DatabaseValue arguments. The array is guaranteed to - /// have exactly *argumentCount* elements, provided *argumentCount* is - /// not nil. - public init(_ name: String, argumentCount: Int32? = nil, pure: Bool = false, aggregate: Aggregate.Type) { - self.identity = Identity(name: name, nArg: argumentCount ?? -1) - self.pure = pure - self.kind = .aggregate { return Aggregate() } - } - - /// Calls sqlite3_create_function_v2 - /// See https://sqlite.org/c3ref/create_function.html - func install(in db: Database) { - // Retain the function definition - let definition = kind.definition - let definitionP = Unmanaged.passRetained(definition).toOpaque() - - let code = sqlite3_create_function_v2( - db.sqliteConnection, - identity.name, - identity.nArg, - eTextRep, - definitionP, - kind.xFunc, - kind.xStep, - kind.xFinal, - { definitionP in - // Release the function definition - Unmanaged.fromOpaque(definitionP!).release() - }) - - guard code == SQLITE_OK else { - // Assume a GRDB bug: there is no point throwing any error. - fatalError(DatabaseError(resultCode: code, message: db.lastErrorMessage).description) - } - } - - /// Calls sqlite3_create_function_v2 - /// See https://sqlite.org/c3ref/create_function.html - func uninstall(in db: Database) { - let code = sqlite3_create_function_v2( - db.sqliteConnection, - identity.name, - identity.nArg, - eTextRep, - nil, nil, nil, nil, nil) - - guard code == SQLITE_OK else { - // Assume a GRDB bug: there is no point throwing any error. - fatalError(DatabaseError(resultCode: code, message: db.lastErrorMessage).description) - } - } - - /// The way to compute the result of a function. - /// Feeds the `pApp` parameter of sqlite3_create_function_v2 - /// http://sqlite.org/capi3ref.html#sqlite3_create_function - private class FunctionDefinition { - let compute: (Int32, UnsafeMutablePointer?) throws -> DatabaseValueConvertible? - init(compute: @escaping (Int32, UnsafeMutablePointer?) throws -> DatabaseValueConvertible?) { - self.compute = compute - } - } - - /// The way to start an aggregate. - /// Feeds the `pApp` parameter of sqlite3_create_function_v2 - /// http://sqlite.org/capi3ref.html#sqlite3_create_function - private class AggregateDefinition { - let makeAggregate: () -> DatabaseAggregate - init(makeAggregate: @escaping () -> DatabaseAggregate) { - self.makeAggregate = makeAggregate - } - } - - /// The current state of an aggregate, storable in SQLite - private class AggregateContext { - var aggregate: DatabaseAggregate - var hasErrored = false - init(aggregate: DatabaseAggregate) { - self.aggregate = aggregate - } - } - - /// A function kind: an "SQL function" or an "aggregate". - /// See http://sqlite.org/capi3ref.html#sqlite3_create_function - private enum Kind { - /// A regular function: SELECT f(1) - case function((Int32, UnsafeMutablePointer?) throws -> DatabaseValueConvertible?) - - /// An aggregate: SELECT f(foo) FROM bar GROUP BY baz - case aggregate(() -> DatabaseAggregate) - - /// Feeds the `pApp` parameter of sqlite3_create_function_v2 - /// http://sqlite.org/capi3ref.html#sqlite3_create_function - var definition: AnyObject { - switch self { - case .function(let compute): - return FunctionDefinition(compute: compute) - case .aggregate(let makeAggregate): - return AggregateDefinition(makeAggregate: makeAggregate) - } - } - - /// Feeds the `xFunc` parameter of sqlite3_create_function_v2 - /// http://sqlite.org/capi3ref.html#sqlite3_create_function - var xFunc: (@convention(c) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void)? { - guard case .function = self else { return nil } - return { (sqliteContext, argc, argv) in - let definition = Unmanaged.fromOpaque(sqlite3_user_data(sqliteContext)).takeUnretainedValue() - do { - try DatabaseFunction.report( - result: definition.compute(argc, argv), - in: sqliteContext) - } catch { - DatabaseFunction.report(error: error, in: sqliteContext) - } - } - } - - /// Feeds the `xStep` parameter of sqlite3_create_function_v2 - /// http://sqlite.org/capi3ref.html#sqlite3_create_function - var xStep: (@convention(c) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void)? { - guard case .aggregate = self else { return nil } - return { (sqliteContext, argc, argv) in - let aggregateContextU = DatabaseFunction.unmanagedAggregateContext(sqliteContext) - let aggregateContext = aggregateContextU.takeUnretainedValue() - assert(!aggregateContext.hasErrored) - do { - let arguments = (0.. Void)? { - guard case .aggregate = self else { return nil } - return { (sqliteContext) in - let aggregateContextU = DatabaseFunction.unmanagedAggregateContext(sqliteContext) - let aggregateContext = aggregateContextU.takeUnretainedValue() - aggregateContextU.release() - - guard !aggregateContext.hasErrored else { - return - } - - do { - try DatabaseFunction.report( - result: aggregateContext.aggregate.finalize(), - in: sqliteContext) - } catch { - DatabaseFunction.report(error: error, in: sqliteContext) - } - } - } - } - - /// Helper function that extracts the current state of an aggregate from an - /// sqlite function execution context. - /// - /// The result must be released when the aggregate concludes. - /// - /// See https://sqlite.org/c3ref/context.html - /// See https://sqlite.org/c3ref/aggregate_context.html - private static func unmanagedAggregateContext(_ sqliteContext: OpaquePointer?) -> Unmanaged { - // The current aggregate buffer - let stride = MemoryLayout>.stride - let aggregateContextBufferP = UnsafeMutableRawBufferPointer(start: sqlite3_aggregate_context(sqliteContext, Int32(stride))!, count: stride) - - if aggregateContextBufferP.contains(where: { $0 != 0 }) { // TODO: This testt looks weird. Review. - // Buffer contains non-null pointer: load aggregate context - let aggregateContextP = aggregateContextBufferP.baseAddress!.assumingMemoryBound(to: Unmanaged.self) - return aggregateContextP.pointee - } else { - // Buffer contains null pointer: create aggregate context... - let aggregate = Unmanaged.fromOpaque(sqlite3_user_data(sqliteContext)) - .takeUnretainedValue() - .makeAggregate() - let aggregateContext = AggregateContext(aggregate: aggregate) - - // retain and store in SQLite's buffer - let aggregateContextU = Unmanaged.passRetained(aggregateContext) - let aggregateContextP = aggregateContextU.toOpaque() - withUnsafeBytes(of: aggregateContextP) { - aggregateContextBufferP.copyMemory(from: $0) - } - return aggregateContextU - } - } - - private static func report(result: DatabaseValueConvertible?, in sqliteContext: OpaquePointer?) { - switch result?.databaseValue.storage ?? .null { - case .null: - sqlite3_result_null(sqliteContext) - case .int64(let int64): - sqlite3_result_int64(sqliteContext, int64) - case .double(let double): - sqlite3_result_double(sqliteContext, double) - case .string(let string): - sqlite3_result_text(sqliteContext, string, -1, SQLITE_TRANSIENT) - case .blob(let data): - #if swift(>=5.0) - data.withUnsafeBytes { - sqlite3_result_blob(sqliteContext, $0.baseAddress, Int32($0.count), SQLITE_TRANSIENT) - } - #else - data.withUnsafeBytes { - sqlite3_result_blob(sqliteContext, $0, Int32(data.count), SQLITE_TRANSIENT) - } - #endif - } - } - - private static func report(error: Error, in sqliteContext: OpaquePointer?) { - if let error = error as? DatabaseError { - if let message = error.message { - sqlite3_result_error(sqliteContext, message, -1) - } - sqlite3_result_error_code(sqliteContext, error.extendedResultCode.rawValue) - } else { - sqlite3_result_error(sqliteContext, "\(error)", -1) - } - } -} - -extension DatabaseFunction { - /// :nodoc: - public func hash(into hasher: inout Hasher) { - hasher.combine(identity) - } - - /// Two functions are equal if they share the same name and arity. - /// :nodoc: - public static func == (lhs: DatabaseFunction, rhs: DatabaseFunction) -> Bool { - return lhs.identity == rhs.identity - } -} - -/// The protocol for custom SQLite aggregates. -/// -/// For example: -/// -/// struct MySum : DatabaseAggregate { -/// var sum: Int = 0 -/// -/// mutating func step(_ dbValues: [DatabaseValue]) { -/// if let int = Int.fromDatabaseValue(dbValues[0]) { -/// sum += int -/// } -/// } -/// -/// func finalize() -> DatabaseValueConvertible? { -/// return sum -/// } -/// } -/// -/// let dbQueue = DatabaseQueue() -/// let fn = DatabaseFunction("mysum", argumentCount: 1, aggregate: MySum.self) -/// dbQueue.add(function: fn) -/// try dbQueue.write { db in -/// try db.execute(sql: "CREATE TABLE test(i)") -/// try db.execute(sql: "INSERT INTO test(i) VALUES (1)") -/// try db.execute(sql: "INSERT INTO test(i) VALUES (2)") -/// try Int.fetchOne(db, sql: "SELECT mysum(i) FROM test")! // 3 -/// } -public protocol DatabaseAggregate { - /// Creates an aggregate. - init() - - /// This method is called at each step of the aggregation. - /// - /// The dbValues argument contains as many values as given to the SQL - /// aggregate function. - /// - /// -- One value - /// SELECT maxLength(name) FROM player - /// - /// -- Two values - /// SELECT maxFullNameLength(firstName, lastName) FROM player - /// - /// This method is never called after the finalize() method has been called. - mutating func step(_ dbValues: [DatabaseValue]) throws - - /// Returns the final result - func finalize() throws -> DatabaseValueConvertible? -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabasePool.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabasePool.swift deleted file mode 100755 index 7a8bc43..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabasePool.swift +++ /dev/null @@ -1,692 +0,0 @@ -import Foundation -import Dispatch -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif -#if os(iOS) - import UIKit -#endif - -/// A DatabasePool grants concurrent accesses to an SQLite database. -public final class DatabasePool: DatabaseWriter { - private let writer: SerializedDatabase - private var readerConfig: Configuration - private var readerPool: Pool! - - private var functions = Set() - private var collations = Set() - private var tokenizerRegistrations: [(Database) -> Void] = [] - - var snapshotCount = ReadWriteBox(0) - - #if os(iOS) - private weak var application: UIApplication? - #endif - - // MARK: - Database Information - - /// The path to the database. - public var path: String { - return writer.path - } - - // MARK: - Initializer - - /// Opens the SQLite database at path *path*. - /// - /// let dbPool = try DatabasePool(path: "/path/to/database.sqlite") - /// - /// Database connections get closed when the database pool gets deallocated. - /// - /// - parameters: - /// - path: The path to the database file. - /// - configuration: A configuration. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public init(path: String, configuration: Configuration = Configuration()) throws { - GRDBPrecondition(configuration.maximumReaderCount > 0, "configuration.maximumReaderCount must be at least 1") - - // Writer - writer = try SerializedDatabase( - path: path, - configuration: configuration, - schemaCache: SimpleDatabaseSchemaCache(), - defaultLabel: "GRDB.DatabasePool", - purpose: "writer") - - // Activate WAL Mode unless readonly - if !configuration.readonly { - try writer.sync { db in - let journalMode = try String.fetchOne(db, sql: "PRAGMA journal_mode = WAL") - guard journalMode == "wal" else { - throw DatabaseError(message: "could not activate WAL Mode at path: \(path)") - } - - // https://www.sqlite.org/pragma.html#pragma_synchronous - // > Many applications choose NORMAL when in WAL mode - try db.execute(sql: "PRAGMA synchronous = NORMAL") - - if !FileManager.default.fileExists(atPath: path + "-wal") { - // Create the -wal file if it does not exist yet. This - // avoids an SQLITE_CANTOPEN (14) error whenever a user - // opens a pool to an existing non-WAL database, and - // attempts to read from it. - // See https://github.com/groue/GRDB.swift/issues/102 - try db.execute(sql: "CREATE TABLE grdb_issue_102 (id INTEGER PRIMARY KEY); DROP TABLE grdb_issue_102;") - } - } - } - - // Readers - readerConfig = configuration - readerConfig.readonly = true - readerConfig.defaultTransactionKind = .deferred // Make it the default for readers. Other transaction kinds are forbidden by SQLite in read-only connections. - readerConfig.allowsUnsafeTransactions = false // Because there's no guarantee that one can get the same reader in order to close its opened transaction. - var readerCount = 0 - readerPool = Pool(maximumCount: configuration.maximumReaderCount, makeElement: { [unowned self] in - readerCount += 1 // protected by pool's ReadWriteBox (undocumented behavior and protection) - let reader = try SerializedDatabase( - path: path, - configuration: self.readerConfig, - schemaCache: SimpleDatabaseSchemaCache(), - defaultLabel: "GRDB.DatabasePool", - purpose: "reader.\(readerCount)") - reader.sync { self.setupDatabase($0) } - return reader - }) - } - - #if os(iOS) - deinit { - // Undo job done in setupMemoryManagement() - // - // https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11Error - // Explicit unregistration is required before iOS 9 and OS X 10.11. - NotificationCenter.default.removeObserver(self) - } - #endif - - private func setupDatabase(_ db: Database) { - for function in functions { - db.add(function: function) - } - for collation in collations { - db.add(collation: collation) - } - for registration in tokenizerRegistrations { - registration(db) - } - } - - /// Blocks the current thread until all database connections have - /// executed the *body* block. - fileprivate func forEachConnection(_ body: (Database) -> Void) { - writer.sync(body) - readerPool.forEach { $0.sync(body) } - } -} - -extension DatabasePool { - - // MARK: - WAL Checkpoints - - /// Runs a WAL checkpoint - /// - /// See https://www.sqlite.org/wal.html and - /// https://www.sqlite.org/c3ref/wal_checkpoint_v2.html) for - /// more information. - /// - /// - parameter kind: The checkpoint mode (default passive) - public func checkpoint(_ kind: Database.CheckpointMode = .passive) throws { - try writer.sync { db in - // TODO: read https://www.sqlite.org/c3ref/wal_checkpoint_v2.html and - // check whether we need a busy handler on writer and/or readers - // when kind is not .Passive. - let code = sqlite3_wal_checkpoint_v2(db.sqliteConnection, nil, kind.rawValue, nil, nil) - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: db.lastErrorMessage, sql: nil) - } - } - } -} - -extension DatabasePool { - - // MARK: - Memory management - - /// Free as much memory as possible. - /// - /// This method blocks the current thread until all database accesses are completed. - /// - /// See also setupMemoryManagement(application:) - public func releaseMemory() { - forEachConnection { $0.releaseMemory() } - readerPool.clear() - } - - - #if os(iOS) - /// Listens to UIApplicationDidEnterBackgroundNotification and - /// UIApplicationDidReceiveMemoryWarningNotification in order to release - /// as much memory as possible. - /// - /// - param application: The UIApplication that will start a background - /// task to let the database pool release its memory when the application - /// enters background. - public func setupMemoryManagement(in application: UIApplication) { - self.application = application - let center = NotificationCenter.default - center.addObserver( - self, - selector: #selector(DatabasePool.applicationDidReceiveMemoryWarning(_:)), - name: UIApplication.didReceiveMemoryWarningNotification, - object: nil) - center.addObserver( - self, - selector: #selector(DatabasePool.applicationDidEnterBackground(_:)), - name: UIApplication.didEnterBackgroundNotification, - object: nil) - } - - @objc private func applicationDidEnterBackground(_ notification: NSNotification) { - guard let application = application else { - return - } - - let task: UIBackgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: nil) - if task == .invalid { - // Perform releaseMemory() synchronously. - releaseMemory() - } else { - // Perform releaseMemory() asynchronously. - DispatchQueue.global().async { - self.releaseMemory() - application.endBackgroundTask(task) - } - } - } - - @objc private func applicationDidReceiveMemoryWarning(_ notification: NSNotification) { - DispatchQueue.global().async { - self.releaseMemory() - } - } - #endif -} - -#if SQLITE_HAS_CODEC - extension DatabasePool { - - // MARK: - Encryption - - /// Changes the passphrase of an encrypted database - public func change(passphrase: String) throws { - try readerPool.clear(andThen: { - try writer.sync { try $0.change(passphrase: passphrase) } - readerConfig.passphrase = passphrase - }) - } - } -#endif - -extension DatabasePool : DatabaseReader { - - // MARK: - Reading from Database - - /// Synchronously executes a read-only block in a protected dispatch queue, - /// and returns its result. The block is wrapped in a deferred transaction. - /// - /// let players = try dbPool.read { db in - /// try Player.fetchAll(...) - /// } - /// - /// The block is completely isolated. Eventual concurrent database updates - /// are *not visible* inside the block: - /// - /// try dbPool.read { db in - /// // Those two values are guaranteed to be equal, even if the - /// // `wine` table is modified between the two requests: - /// let count1 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// let count2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// try dbPool.read { db in - /// // Now this value may be different: - /// let count = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block, or any DatabaseError that would - /// happen while establishing the read access to the database. - public func read(_ block: (Database) throws -> T) throws -> T { - GRDBPrecondition(currentReader == nil, "Database methods are not reentrant.") - return try readerPool.get { reader in - try reader.sync { db in - var result: T? = nil - // The block isolation comes from the DEFERRED transaction. - // See DatabasePoolTests.testReadMethodIsolationOfBlock(). - try db.inTransaction(.deferred) { - // Reset the schema cache before running user code in snapshot isolation - db.schemaCache = SimpleDatabaseSchemaCache() - result = try block(db) - return .commit - } - return result! - } - } - } - - /// Synchronously executes a read-only block in a protected dispatch queue, - /// and returns its result. - /// - /// The block argument is not isolated: eventual concurrent database updates - /// are visible inside the block: - /// - /// try dbPool.unsafeRead { db in - /// // Those two values may be different because some other thread - /// // may have inserted or deleted a wine between the two requests: - /// let count1 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// let count2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// Cursor iteration is safe, though: - /// - /// try dbPool.unsafeRead { db in - /// // No concurrent update can mess with this iteration: - /// let rows = try Row.fetchCursor(db, sql: "SELECT ...") - /// while let row = try rows.next() { ... } - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block, or any DatabaseError that would - /// happen while establishing the read access to the database. - public func unsafeRead(_ block: (Database) throws -> T) throws -> T { - GRDBPrecondition(currentReader == nil, "Database methods are not reentrant.") - return try readerPool.get { reader in - try reader.sync { db in - // No schema cache when snapshot isolation is not established - db.schemaCache = EmptyDatabaseSchemaCache() - return try block(db) - } - } - } - - /// Synchronously executes a read-only block in a protected dispatch queue, - /// and returns its result. - /// - /// The block argument is not isolated: eventual concurrent database updates - /// are visible inside the block: - /// - /// try dbPool.unsafeReentrantRead { db in - /// // Those two values may be different because some other thread - /// // may have inserted or deleted a wine between the two requests: - /// let count1 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// let count2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// Cursor iteration is safe, though: - /// - /// try dbPool.unsafeReentrantRead { db in - /// // No concurrent update can mess with this iteration: - /// let rows = try Row.fetchCursor(db, sql: "SELECT ...") - /// while let row = try rows.next() { ... } - /// } - /// - /// This method is reentrant. It is unsafe because it fosters dangerous - /// concurrency practices. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block, or any DatabaseError that would - /// happen while establishing the read access to the database. - public func unsafeReentrantRead(_ block: (Database) throws -> T) throws -> T { - if let reader = currentReader { - return try reader.reentrantSync(block) - } else { - return try readerPool.get { reader in - try reader.sync { db in - // No schema cache when snapshot isolation is not established - db.schemaCache = EmptyDatabaseSchemaCache() - return try block(db) - } - } - } - } - - public func concurrentRead(_ block: @escaping (Database) throws -> T) -> DatabaseFuture { - // Check that we're on the writer queue... - writer.execute { db in - // ... and that no transaction is opened. - GRDBPrecondition(!db.isInsideTransaction, """ - concurrentRead must not be called from inside a transaction. \ - If this error is raised from a DatabasePool.write block, use \ - DatabasePool.writeWithoutTransaction instead (and use \ - transactions when needed). - """) - } - - // The semaphore that blocks the writing dispatch queue until snapshot - // isolation has been established: - let isolationSemaphore = DispatchSemaphore(value: 0) - - // The semaphore that blocks until futureResult is defined: - let futureSemaphore = DispatchSemaphore(value: 0) - var futureResult: Result? = nil - - do { - let (reader, releaseReader) = try readerPool.get() - reader.async { db in - defer { - try? db.commit() // Ignore commit error - releaseReader() - } - do { - try db.beginSnapshotIsolation() - } catch { - futureResult = .failure(error) - isolationSemaphore.signal() - futureSemaphore.signal() - return - } - - // Release the writer queue - isolationSemaphore.signal() - - // Reset the schema cache before running user code in snapshot isolation - db.schemaCache = SimpleDatabaseSchemaCache() - - // Fetch and release the future - futureResult = Result { try block(db) } - futureSemaphore.signal() - } - } catch { - return DatabaseFuture { throw error } - } - - // Block the writer queue until snapshot isolation success or error - _ = isolationSemaphore.wait(timeout: .distantFuture) - - return DatabaseFuture { - // Block the future until results are fetched - _ = futureSemaphore.wait(timeout: .distantFuture) - return try futureResult!.get() - } - } - - /// Returns a reader that can be used from the current dispatch queue, - /// if any. - private var currentReader: SerializedDatabase? { - var readers: [SerializedDatabase] = [] - readerPool.forEach { reader in - // We can't check for reader.onValidQueue here because - // Pool.forEach() runs its closure argument in some arbitrary - // dispatch queue. We thus extract the reader so that we can query - // it below. - readers.append(reader) - } - - // Now the readers array contains some readers. The pool readers may - // already be different, because some other thread may have started - // a new read, for example. - // - // This doesn't matter: the reader we are looking for is already on - // its own dispatch queue. If it exists, is still in use, thus still - // in the pool, and thus still relevant for our check: - return readers.first { $0.onValidQueue } - } - - // MARK: - Writing in Database - - /// Synchronously executes an update block in a protected dispatch queue, - /// wrapped inside a transaction, and returns the result of the block. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// try dbPool.write { db in - /// try db.execute(...) - /// } - /// - /// Eventual concurrent reads are guaranteed not to see any changes - /// performed in the block until they are all saved in the database. - /// - /// This method is *not* reentrant. - /// - /// - parameters block: A block that executes SQL statements. - /// - throws: The error thrown by the block, or by the wrapping transaction. - public func write(_ block: (Database) throws -> T) throws -> T { - return try writer.sync { db in - var result: T? = nil - try db.inTransaction { - result = try block(db) - return .commit - } - return result! - } - } - - /// Synchronously executes a block that takes a database connection, and - /// returns its result. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// Eventual concurrent reads may see changes performed in the block before - /// the block completes. - /// - /// The block is guaranteed to be executed outside of a transaction. - /// - /// This method is *not* reentrant. - /// - /// - parameters block: A block that executes SQL statements and return - /// either .commit or .rollback. - /// - throws: The error thrown by the block. - public func writeWithoutTransaction(_ block: (Database) throws -> T) rethrows -> T { - return try writer.sync(block) - } - - /// Synchronously executes a block in a protected dispatch queue, wrapped - /// inside a transaction. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// If the block throws an error, the transaction is rollbacked and the - /// error is rethrown. If the block returns .rollback, the transaction is - /// also rollbacked, but no error is thrown. - /// - /// try dbPool.writeInTransaction { db in - /// db.execute(...) - /// return .commit - /// } - /// - /// Eventual concurrent reads are guaranteed not to see any changes - /// performed in the block until they are all saved in the database. - /// - /// This method is *not* reentrant. - /// - /// - parameters: - /// - kind: The transaction type (default nil). If nil, the transaction - /// type is configuration.defaultTransactionKind, which itself - /// defaults to .deferred. See https://www.sqlite.org/lang_transaction.html - /// for more information. - /// - block: A block that executes SQL statements and return either - /// .commit or .rollback. - /// - throws: The error thrown by the block, or any error establishing the - /// transaction. - public func writeInTransaction(_ kind: Database.TransactionKind? = nil, _ block: (Database) throws -> Database.TransactionCompletion) throws { - try writer.sync { db in - try db.inTransaction(kind) { - try block(db) - } - } - } - - /// Synchronously executes an update block in a protected dispatch queue, - /// and returns its result. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// try dbPool.unsafeReentrantWrite { db in - /// try db.execute(...) - /// } - /// - /// Eventual concurrent reads may see changes performed in the block before - /// the block completes. - /// - /// This method is reentrant. It is unsafe because it fosters dangerous - /// concurrency practices. - public func unsafeReentrantWrite(_ block: (Database) throws -> T) rethrows -> T { - return try writer.reentrantSync(block) - } - - // MARK: - Functions - - /// Add or redefine an SQL function. - /// - /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in - /// guard let int = Int.fromDatabaseValue(dbValues[0]) else { - /// return nil - /// } - /// return int + 1 - /// } - /// dbPool.add(function: fn) - /// try dbPool.read { db in - /// try Int.fetchOne(db, sql: "SELECT succ(1)") // 2 - /// } - public func add(function: DatabaseFunction) { - functions.update(with: function) - forEachConnection { $0.add(function: function) } - } - - /// Remove an SQL function. - public func remove(function: DatabaseFunction) { - functions.remove(function) - forEachConnection { $0.remove(function: function) } - } - - // MARK: - Collations - - /// Add or redefine a collation. - /// - /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in - /// return (string1 as NSString).localizedStandardCompare(string2) - /// } - /// dbPool.add(collation: collation) - /// try dbPool.write { db in - /// try db.execute(sql: "CREATE TABLE file (name TEXT COLLATE LOCALIZED_STANDARD") - /// } - public func add(collation: DatabaseCollation) { - collations.update(with: collation) - forEachConnection { $0.add(collation: collation) } - } - - /// Remove a collation. - public func remove(collation: DatabaseCollation) { - collations.remove(collation) - forEachConnection { $0.remove(collation: collation) } - } - - // MARK: - Custom FTS5 Tokenizers - - #if SQLITE_ENABLE_FTS5 - /// Add a custom FTS5 tokenizer. - /// - /// class MyTokenizer : FTS5CustomTokenizer { ... } - /// dbPool.add(tokenizer: MyTokenizer.self) - public func add(tokenizer: Tokenizer.Type) { - func registerTokenizer(db: Database) { - db.add(tokenizer: Tokenizer.self) - } - tokenizerRegistrations.append(registerTokenizer) - forEachConnection(registerTokenizer) - } - #endif -} - -extension DatabasePool { - - // MARK: - Snapshots - - /// Creates a database snapshot. - /// - /// The snapshot sees an unchanging database content, as it existed at the - /// moment it was created. - /// - /// When you want to control the latest committed changes seen by a - /// snapshot, create it from the pool's writer protected dispatch queue: - /// - /// let snapshot1 = try dbPool.write { db -> DatabaseSnapshot in - /// try Player.deleteAll() - /// return try dbPool.makeSnapshot() - /// } - /// // <- Other threads may modify the database here - /// let snapshot2 = try dbPool.makeSnapshot() - /// - /// try snapshot1.read { db in - /// // Guaranteed to be zero - /// try Player.fetchCount(db) - /// } - /// - /// try snapshot2.read { db in - /// // Could be anything - /// try Player.fetchCount(db) - /// } - /// - /// It is forbidden to create a snapshot from the writer protected dispatch - /// queue when a transaction is opened, though, because it is likely a - /// programmer error: - /// - /// try dbPool.write { db in - /// try db.inTransaction { - /// try Player.deleteAll() - /// // fatal error: makeSnapshot() must not be called from inside a transaction - /// let snapshot = try dbPool.makeSnapshot() - /// return .commit - /// } - /// } - /// - /// To avoid this fatal error, create the snapshot *before* or *after* the - /// transaction: - /// - /// try dbPool.writeWithoutTransaction { db in - /// // OK - /// let snapshot = try dbPool.makeSnapshot() - /// - /// try db.inTransaction { - /// try Player.deleteAll() - /// return .commit - /// } - /// - /// // OK - /// let snapshot = try dbPool.makeSnapshot() - /// } - /// - /// You can create as many snapshots as you need, regardless of the maximum - /// number of reader connections in the pool. - /// - /// For more information, read about "snapshot isolation" at https://sqlite.org/isolation.html - public func makeSnapshot() throws -> DatabaseSnapshot { - // Sanity check - if writer.onValidQueue { - writer.execute { db in - GRDBPrecondition(!db.isInsideTransaction, "makeSnapshot() must not be called from inside a transaction.") - } - } - - let snapshot = try DatabaseSnapshot( - path: path, - configuration: writer.configuration, - defaultLabel: "GRDB.DatabasePool", - purpose: "snapshot.\(snapshotCount.increment())") - snapshot.read { setupDatabase($0) } - return snapshot - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseQueue.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseQueue.swift deleted file mode 100755 index 7d87f69..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseQueue.swift +++ /dev/null @@ -1,365 +0,0 @@ -import Foundation - -#if os(iOS) -import UIKit -#endif - -/// A DatabaseQueue serializes access to an SQLite database. -public final class DatabaseQueue: DatabaseWriter { - private var writer: SerializedDatabase - #if os(iOS) - private weak var application: UIApplication? - #endif - - // MARK: - Configuration - - /// The database configuration - public var configuration: Configuration { - return writer.configuration - } - - /// The path to the database file; it is ":memory:" for in-memory databases. - public var path: String { - return writer.path - } - - // MARK: - Initializers - - /// Opens the SQLite database at path *path*. - /// - /// let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite") - /// - /// Database connections get closed when the database queue gets deallocated. - /// - /// - parameters: - /// - path: The path to the database file. - /// - configuration: A configuration. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public init(path: String, configuration: Configuration = Configuration()) throws { - writer = try SerializedDatabase( - path: path, - configuration: configuration, - schemaCache: SimpleDatabaseSchemaCache(), - defaultLabel: "GRDB.DatabaseQueue") - } - - /// Opens an in-memory SQLite database. - /// - /// let dbQueue = DatabaseQueue() - /// - /// Database memory is released when the database queue gets deallocated. - /// - /// - parameter configuration: A configuration. - public init(configuration: Configuration = Configuration()) { - // Assume SQLite always succeeds creating an in-memory database - writer = try! SerializedDatabase( - path: ":memory:", - configuration: configuration, - schemaCache: SimpleDatabaseSchemaCache(), - defaultLabel: "GRDB.DatabaseQueue") - } - - #if os(iOS) - deinit { - // Undo job done in setupMemoryManagement() - // - // https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11Error - // Explicit unregistration is required before iOS 9 and OS X 10.11. - NotificationCenter.default.removeObserver(self) - } - #endif -} - -extension DatabaseQueue { - - // MARK: - Memory management - - /// Free as much memory as possible. - /// - /// This method blocks the current thread until all database accesses are completed. - /// - /// See also setupMemoryManagement(application:) - public func releaseMemory() { - writer.sync { $0.releaseMemory() } - } - - #if os(iOS) - /// Listens to UIApplicationDidEnterBackgroundNotification and - /// UIApplicationDidReceiveMemoryWarningNotification in order to release - /// as much memory as possible. - /// - /// - param application: The UIApplication that will start a background - /// task to let the database queue release its memory when the application - /// enters background. - public func setupMemoryManagement(in application: UIApplication) { - self.application = application - let center = NotificationCenter.default - center.addObserver( - self, - selector: #selector(DatabaseQueue.applicationDidReceiveMemoryWarning(_:)), - name: UIApplication.didReceiveMemoryWarningNotification, - object: nil) - center.addObserver( - self, - selector: #selector(DatabaseQueue.applicationDidEnterBackground(_:)), - name: UIApplication.didEnterBackgroundNotification, - object: nil) - } - - @objc private func applicationDidEnterBackground(_ notification: NSNotification) { - guard let application = application else { - return - } - - let task: UIBackgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: nil) - if task == .invalid { - // Perform releaseMemory() synchronously. - releaseMemory() - } else { - // Perform releaseMemory() asynchronously. - DispatchQueue.global().async { - self.releaseMemory() - application.endBackgroundTask(task) - } - } - } - - @objc private func applicationDidReceiveMemoryWarning(_ notification: NSNotification) { - DispatchQueue.global().async { - self.releaseMemory() - } - } - #endif -} - -#if SQLITE_HAS_CODEC - extension DatabaseQueue { - - // MARK: - Encryption - - /// Changes the passphrase of an encrypted database - public func change(passphrase: String) throws { - try writer.sync { try $0.change(passphrase: passphrase) } - } - } -#endif - -extension DatabaseQueue { - - // MARK: - Reading from Database - - /// Synchronously executes a read-only block in a protected dispatch queue, - /// and returns its result. - /// - /// let players = try dbQueue.read { db in - /// try Player.fetchAll(db) - /// } - /// - /// This method is *not* reentrant. - /// - /// Starting SQLite 3.8.0 (iOS 8.2+, OSX 10.10+, custom SQLite builds and - /// SQLCipher), attempts to write in the database from this meethod throw a - /// DatabaseError of resultCode `SQLITE_READONLY`. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - public func read(_ block: (Database) throws -> T) rethrows -> T { - return try writer.sync { db in - try db.readOnly { try block(db) } - } - } - - /// Synchronously executes a block in a protected dispatch queue, and - /// returns its result. - /// - /// let players = try dbQueue.unsafeRead { db in - /// try Player.fetchAll(db) - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - /// - /// :nodoc: - public func unsafeRead(_ block: (Database) throws -> T) rethrows -> T { - return try writer.sync(block) - } - - /// Synchronously executes a block in a protected dispatch queue, and - /// returns its result. - /// - /// let players = try dbQueue.unsafeReentrantRead { db in - /// try Player.fetchAll(db) - /// } - /// - /// This method is reentrant. It is unsafe because it fosters dangerous - /// concurrency practices. - /// - /// :nodoc: - public func unsafeReentrantRead(_ block: (Database) throws -> T) rethrows -> T { - return try writer.reentrantSync(block) - } - - public func concurrentRead(_ block: @escaping (Database) throws -> T) -> DatabaseFuture { - // DatabaseQueue can't perform parallel reads. - // Perform a blocking read instead. - return DatabaseFuture(Result { - // Check that we're on the writer queue... - try writer.execute { db in - // ... and that no transaction is opened. - GRDBPrecondition(!db.isInsideTransaction, "concurrentRead must not be called from inside a transaction.") - return try db.readOnly { - try block(db) - } - } - }) - } - - // MARK: - Writing in Database - - /// Synchronously executes a block in a protected dispatch queue, wrapped - /// inside a transaction. - /// - /// If the block throws an error, the transaction is rollbacked and the - /// error is rethrown. - /// - /// try dbQueue.write { db in - /// try Player(...).insert(db) - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameter block: A block that executes SQL statements. - /// - throws: An eventual database error, or the error thrown by the block. - public func write(_ block: (Database) throws -> T) throws -> T { - return try writer.sync { db in - var result: T? = nil - try db.inTransaction { - result = try block(db) - return .commit - } - return result! - } - } - - /// Synchronously executes a block in a protected dispatch queue, wrapped - /// inside a transaction. - /// - /// If the block throws an error, the transaction is rollbacked and the - /// error is rethrown. If the block returns .rollback, the transaction is - /// also rollbacked, but no error is thrown. - /// - /// try dbQueue.inTransaction { db in - /// try Player(...).insert(db) - /// return .commit - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameters: - /// - kind: The transaction type (default nil). If nil, the transaction - /// type is configuration.defaultTransactionKind, which itself - /// defaults to .deferred. See https://www.sqlite.org/lang_transaction.html - /// for more information. - /// - block: A block that executes SQL statements and return either - /// .commit or .rollback. - /// - throws: The error thrown by the block. - public func inTransaction(_ kind: Database.TransactionKind? = nil, _ block: (Database) throws -> Database.TransactionCompletion) throws { - try writer.sync { db in - try db.inTransaction(kind) { - try block(db) - } - } - } - - /// Synchronously executes a block in a protected dispatch queue, and - /// returns its result. - /// - /// let players = try dbQueue.writeWithoutTransaction { db in - /// try Player(...).insert(db) - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - /// - /// :nodoc: - public func writeWithoutTransaction(_ block: (Database) throws -> T) rethrows -> T { - return try writer.sync(block) - } - - /// Synchronously executes a block in a protected dispatch queue, and - /// returns its result. - /// - /// // INSERT INTO player ... - /// let players = try dbQueue.inDatabase { db in - /// try Player(...).insert(db) - /// } - /// - /// This method is *not* reentrant. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - public func inDatabase(_ block: (Database) throws -> T) rethrows -> T { - return try writer.sync(block) - } - - /// Synchronously executes a block in a protected dispatch queue, and - /// returns its result. - /// - /// // INSERT INTO player ... - /// try dbQueue.unsafeReentrantWrite { db in - /// try Player(...).insert(db) - /// } - /// - /// This method is reentrant. It is unsafe because it fosters dangerous - /// concurrency practices. - public func unsafeReentrantWrite(_ block: (Database) throws -> T) rethrows -> T { - return try writer.reentrantSync(block) - } - - // MARK: - Functions - - /// Add or redefine an SQL function. - /// - /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in - /// guard let int = Int.fromDatabaseValue(dbValues[0]) else { - /// return nil - /// } - /// return int + 1 - /// } - /// dbQueue.add(function: fn) - /// try dbQueue.read { db in - /// try Int.fetchOne(db, sql: "SELECT succ(1)") // 2 - /// } - public func add(function: DatabaseFunction) { - writer.sync { $0.add(function: function) } - } - - /// Remove an SQL function. - public func remove(function: DatabaseFunction) { - writer.sync { $0.remove(function: function) } - } - - // MARK: - Collations - - /// Add or redefine a collation. - /// - /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in - /// return (string1 as NSString).localizedStandardCompare(string2) - /// } - /// dbQueue.add(collation: collation) - /// try dbQueue.write { db in - /// try db.execute(sql: "CREATE TABLE file (name TEXT COLLATE LOCALIZED_STANDARD") - /// } - public func add(collation: DatabaseCollation) { - writer.sync { $0.add(collation: collation) } - } - - /// Remove a collation. - public func remove(collation: DatabaseCollation) { - writer.sync { $0.remove(collation: collation) } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseReader.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseReader.swift deleted file mode 100755 index 3f0a79b..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseReader.swift +++ /dev/null @@ -1,271 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// The protocol for all types that can fetch values from a database. -/// -/// It is adopted by DatabaseQueue and DatabasePool. -/// -/// The protocol comes with isolation guarantees that describe the behavior of -/// adopting types in a multithreaded application. -/// -/// Types that adopt the protocol can provide in practice stronger guarantees. -/// For example, DatabaseQueue provides a stronger isolation level -/// than DatabasePool. -/// -/// **Warning**: Isolation guarantees stand as long as there is no external -/// connection to the database. Should you have to cope with external -/// connections, protect yourself with transactions, and be ready to setup a -/// [busy handler](https://www.sqlite.org/c3ref/busy_handler.html). -public protocol DatabaseReader : class { - - // MARK: - Read From Database - - /// Synchronously executes a read-only block that takes a database - /// connection, and returns its result. - /// - /// Guarantee 1: the block argument is isolated. Eventual concurrent - /// database updates are not visible inside the block: - /// - /// try reader.read { db in - /// // Those two values are guaranteed to be equal, even if the - /// // `wine` table is modified between the two requests: - /// let count1 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// let count2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// try reader.read { db in - /// // Now this value may be different: - /// let count = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// Guarantee 2: Starting iOS 8.2, OSX 10.10, and with custom SQLite builds - /// and SQLCipher, attempts to write in the database throw a DatabaseError - /// whose resultCode is `SQLITE_READONLY`. - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block, or any DatabaseError that would - /// happen while establishing the read access to the database. - func read(_ block: (Database) throws -> T) throws -> T - - /// Synchronously executes a read-only block that takes a database - /// connection, and returns its result. - /// - /// The two guarantees of the safe `read` method are lifted: - /// - /// The block argument is not isolated: eventual concurrent database updates - /// are visible inside the block: - /// - /// try reader.unsafeRead { db in - /// // Those two values may be different because some other thread - /// // may have inserted or deleted a wine between the two requests: - /// let count1 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// let count2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// Cursor iterations are isolated, though: - /// - /// try reader.unsafeRead { db in - /// // No concurrent update can mess with this iteration: - /// let rows = try Row.fetchCursor(db, sql: "SELECT ...") - /// while let row = try rows.next() { ... } - /// } - /// - /// The block argument is not prevented from writing (DatabaseQueue, in - /// particular, will accept database modifications in `unsafeRead`). - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block, or any DatabaseError that would - /// happen while establishing the read access to the database. - func unsafeRead(_ block: (Database) throws -> T) throws -> T - - /// Synchronously executes a block that takes a database connection, and - /// returns its result. - /// - /// The two guarantees of the safe `read` method are lifted: - /// - /// The block argument is not isolated: eventual concurrent database updates - /// are visible inside the block: - /// - /// try reader.unsafeReentrantRead { db in - /// // Those two values may be different because some other thread - /// // may have inserted or deleted a wine between the two requests: - /// let count1 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// let count2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wine")! - /// } - /// - /// Cursor iterations are isolated, though: - /// - /// try reader.unsafeReentrantRead { db in - /// // No concurrent update can mess with this iteration: - /// let rows = try Row.fetchCursor(db, sql: "SELECT ...") - /// while let row = try rows.next() { ... } - /// } - /// - /// The block argument is not prevented from writing (DatabaseQueue, in - /// particular, will accept database modifications in `unsafeReentrantRead`). - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block, or any DatabaseError that would - /// happen while establishing the read access to the database. - /// - /// This method is reentrant. It should be avoided because it fosters - /// dangerous concurrency practices. - func unsafeReentrantRead(_ block: (Database) throws -> T) throws -> T - - - // MARK: - Functions - - /// Add or redefine an SQL function. - /// - /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in - /// guard let int = Int.fromDatabaseValue(dbValues[0]) else { - /// return nil - /// } - /// return int + 1 - /// } - /// reader.add(function: fn) - /// try reader.read { db in - /// try Int.fetchOne(db, sql: "SELECT succ(1)")! // 2 - /// } - func add(function: DatabaseFunction) - - /// Remove an SQL function. - func remove(function: DatabaseFunction) - - - // MARK: - Collations - - /// Add or redefine a collation. - /// - /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in - /// return (string1 as NSString).localizedStandardCompare(string2) - /// } - /// reader.add(collation: collation) - /// try reader.execute(sql: "SELECT * FROM file ORDER BY name COLLATE localized_standard") - func add(collation: DatabaseCollation) - - /// Remove a collation. - func remove(collation: DatabaseCollation) - - // MARK: - Value Observation - - /// Starts a value observation. - /// - /// You should use the `ValueObservation.start(in:onError:onChange:)` - /// method instead. - /// - /// - parameter observation: the stared observation - /// - parameter onError: a closure that is provided by eventual errors that happen - /// during observation - /// - parameter onChange: a closure that is provided fresh values - /// - returns: a TransactionObserver - func add( - observation: ValueObservation, - onError: ((Error) -> Void)?, - onChange: @escaping (Reducer.Value) -> Void) - throws -> TransactionObserver - - /// Remove a transaction observer. - func remove(transactionObserver: TransactionObserver) -} - -extension DatabaseReader { - - // MARK: - Backup - - /// Copies the database contents into another database. - /// - /// The `backup` method blocks the current thread until the destination - /// database contains the same contents as the source database. - /// - /// When the source is a DatabasePool, concurrent writes can happen during - /// the backup. Those writes may, or may not, be reflected in the backup, - /// but they won't trigger any error. - public func backup(to writer: DatabaseWriter) throws { - try backup(to: writer, afterBackupInit: nil, afterBackupStep: nil) - } - - func backup(to writer: DatabaseWriter, afterBackupInit: (() -> ())?, afterBackupStep: (() -> ())?) throws { - try read { dbFrom in - try writer.writeWithoutTransaction { dbDest in - try Database.backup(from: dbFrom, to: dbDest, afterBackupInit: afterBackupInit, afterBackupStep: afterBackupStep) - } - } - } -} - -/// A type-erased DatabaseReader -/// -/// Instances of AnyDatabaseReader forward their methods to an arbitrary -/// underlying database reader. -public final class AnyDatabaseReader : DatabaseReader { - private let base: DatabaseReader - - /// Creates a database reader that wraps a base database reader. - public init(_ base: DatabaseReader) { - self.base = base - } - - // MARK: - Reading from Database - - /// :nodoc: - public func read(_ block: (Database) throws -> T) throws -> T { - return try base.read(block) - } - - /// :nodoc: - public func unsafeRead(_ block: (Database) throws -> T) throws -> T { - return try base.unsafeRead(block) - } - - /// :nodoc: - public func unsafeReentrantRead(_ block: (Database) throws -> T) throws -> T { - return try base.unsafeReentrantRead(block) - } - - // MARK: - Functions - - /// :nodoc: - public func add(function: DatabaseFunction) { - base.add(function: function) - } - - /// :nodoc: - public func remove(function: DatabaseFunction) { - base.remove(function: function) - } - - // MARK: - Collations - - /// :nodoc: - public func add(collation: DatabaseCollation) { - base.add(collation: collation) - } - - /// :nodoc: - public func remove(collation: DatabaseCollation) { - base.remove(collation: collation) - } - - // MARK: - Value Observation - - /// :nodoc: - public func add( - observation: ValueObservation, - onError: ((Error) -> Void)?, - onChange: @escaping (Reducer.Value) -> Void) - throws -> TransactionObserver - { - return try base.add(observation: observation, onError: onError, onChange: onChange) - } - - /// :nodoc: - public func remove(transactionObserver: TransactionObserver) { - base.remove(transactionObserver: transactionObserver) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseRegion.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseRegion.swift deleted file mode 100755 index 7df10aa..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseRegion.swift +++ /dev/null @@ -1,355 +0,0 @@ -/// DatabaseRegion defines a region in the database. DatabaseRegion is dedicated -/// to help transaction observers recognize impactful database changes in their -/// `observes(eventsOfKind:)` and `databaseDidChange(with:)` methods. -/// -/// A database region is the union of any number of "table regions", which can -/// cover a full table, or the combination of columns and rows: -/// -/// |Table1 | |Table2 | |Table3 | |Table4 | |Table5 | -/// |-------| |-------| |-------| |-------| |-------| -/// |x|x|x|x| |x| | | | |x|x|x|x| |x|x| |x| | | | | | -/// |x|x|x|x| |x| | | | | | | | | | | | | | | |x| | | -/// |x|x|x|x| |x| | | | | | | | | |x|x| |x| | | | | | -/// |x|x|x|x| |x| | | | | | | | | | | | | | | | | | | -/// -/// To create a database region, you use one of those methods: -/// -/// - `DatabaseRegion.fullDatabase`: the region that covers all database tables. -/// -/// - `DatabaseRegion()`: the empty region. -/// -/// - `DatabaseRegion(table:)`: the region that covers one database table. -/// -/// - `SelectStatement.databaseRegion`: -/// -/// let statement = try db.makeSelectStatement(sql: "SELECT name, score FROM player") -/// let region = statement.databaseRegion -/// -/// - `FetchRequest.databaseRegion(_:)` -/// -/// let request = Player.filter(key: 1) -/// let region = try request.databaseRegion(db) -public struct DatabaseRegion: CustomStringConvertible, Equatable { - private let tableRegions: [String: TableRegion]? - private init(tableRegions: [String: TableRegion]?) { - self.tableRegions = tableRegions - } - - /// Returns whether the region is empty. - public var isEmpty: Bool { - guard let tableRegions = tableRegions else { - // full database - return false - } - return tableRegions.isEmpty - } - - /// The region that covers the full database: all columns and all rows - /// from all tables. - public static let fullDatabase = DatabaseRegion(tableRegions: nil) - - /// Creates an empty database region. - public init() { - self.init(tableRegions: [:]) - } - - /// Creates a region that spans all rows and columns of a database table. - /// - /// - parameter table: A table name. - public init(table: String) { - self.init(tableRegions: [table: TableRegion(columns: nil, rowIds: nil)]) - } - - /// Full columns in a table: (some columns in a table) × (all rows) - init(table: String, columns: Set) { - self.init(tableRegions: [table: TableRegion(columns: columns, rowIds: nil)]) - } - - /// Full rows in a table: (all columns in a table) × (some rows) - init(table: String, rowIds: Set) { - self.init(tableRegions: [table: TableRegion(columns: nil, rowIds: rowIds)]) - } - - /// Returns the intersection of this region and the given one. - /// - /// This method is not public because there is no known public use case for - /// this intersection. It is currently only used as support for - /// the isModified(byEventsOfKind:) method. - func intersection(_ other: DatabaseRegion) -> DatabaseRegion { - guard let tableRegions = tableRegions else { return other } - guard let otherTableRegions = other.tableRegions else { return self } - - var tableRegionsIntersection: [String: TableRegion] = [:] - for (table, tableRegion) in tableRegions { - guard let otherTableRegion = otherTableRegions - .first(where: { (otherTable, _) in otherTable == table })? - .value else { continue } - let tableRegionIntersection = tableRegion.intersection(otherTableRegion) - guard !tableRegionIntersection.isEmpty else { continue } - tableRegionsIntersection[table] = tableRegionIntersection - } - - return DatabaseRegion(tableRegions: tableRegionsIntersection) - } - - /// Only keeps those rowIds in the given table - func tableIntersection(_ table: String, rowIds: Set) -> DatabaseRegion { - guard var tableRegions = tableRegions else { - return DatabaseRegion(table: table, rowIds: rowIds) - } - - guard let tableRegion = tableRegions[table] else { - return self - } - - let intersection = tableRegion.intersection(TableRegion(columns: nil, rowIds: rowIds)) - if intersection.isEmpty { - tableRegions.removeValue(forKey: table) - } else { - tableRegions[table] = intersection - } - return DatabaseRegion(tableRegions: tableRegions) - } - - /// Returns the union of this region and the given one. - public func union(_ other: DatabaseRegion) -> DatabaseRegion { - guard let tableRegions = tableRegions else { return .fullDatabase } - guard let otherTableRegions = other.tableRegions else { return .fullDatabase } - - var tableRegionsUnion: [String: TableRegion] = [:] - let tableNames = Set(tableRegions.keys).union(Set(otherTableRegions.keys)) - for table in tableNames { - let tableRegion = tableRegions[table] - let otherTableRegion = otherTableRegions[table] - let tableRegionUnion: TableRegion - switch (tableRegion, otherTableRegion) { - case (nil, nil): - preconditionFailure() - case (nil, let tableRegion?), (let tableRegion?, nil): - tableRegionUnion = tableRegion - case (let tableRegion?, let otherTableRegion?): - tableRegionUnion = tableRegion.union(otherTableRegion) - } - tableRegionsUnion[table] = tableRegionUnion - } - - return DatabaseRegion(tableRegions: tableRegionsUnion) - } - - /// Inserts the given region into this region - public mutating func formUnion(_ other: DatabaseRegion) { - self = union(other) - } - - func ignoring(_ tables: Set) -> DatabaseRegion { - guard tables.isEmpty == false else { return self } - guard let tableRegions = tableRegions else { return .fullDatabase } - let filteredRegions = tableRegions.filter { tables.contains($0.key) == false } - return DatabaseRegion(tableRegions: filteredRegions) - } -} - -extension DatabaseRegion { - - // MARK: - Database Events - - /// Returns whether the content in the region would be impacted if the - /// database were modified by an event of this kind. - public func isModified(byEventsOfKind eventKind: DatabaseEventKind) -> Bool { - return intersection(eventKind.modifiedRegion).isEmpty == false - } - - /// Returns whether the content in the region is impacted by this event. - /// - /// - precondition: event has been filtered by the same region - /// in the TransactionObserver.observes(eventsOfKind:) method, by calling - /// region.isModified(byEventsOfKind:) - public func isModified(by event: DatabaseEvent) -> Bool { - guard let tableRegions = tableRegions else { - // Full database: all changes are impactful - return true - } - - if tableRegions.count == 1 { - // Fast path when the region contains a single table. - // - // We can apply the precondition: due to the filtering of events - // performed in observes(eventsOfKind:), the event argument is - // guaranteed to be about the fetched table. We thus only have to - // check for rowIds. - assert(event.tableName == tableRegions[tableRegions.startIndex].key) // sanity check in debug mode - let tableRegion = tableRegions[tableRegions.startIndex].value - return tableRegion.contains(rowID: event.rowID) - } else { - // Slow path when several tables are observed. - guard let tableRegion = tableRegions[event.tableName] else { - // Shouldn't happen if the precondition is met. - fatalError("precondition failure: event was not filtered out in observes(eventsOfKind:) by region.isModified(byEventsOfKind:)") - } - return tableRegion.contains(rowID: event.rowID) - } - } -} - -// Equatable -extension DatabaseRegion { - /// :nodoc: - public static func == (lhs: DatabaseRegion, rhs: DatabaseRegion) -> Bool { - switch (lhs.tableRegions, rhs.tableRegions) { - case (nil, nil): - return true - case (let ltableRegions?, let rtableRegions?): - let ltableNames = Set(ltableRegions.keys) - let rtableNames = Set(rtableRegions.keys) - guard ltableNames == rtableNames else { - return false - } - for tableName in ltableNames { - if ltableRegions[tableName]! != rtableRegions[tableName]! { - return false - } - } - return true - default: - return false - } - } -} - -// CustomStringConvertible -extension DatabaseRegion { - /// :nodoc: - public var description: String { - guard let tableRegions = tableRegions else { - return "full database" - } - if tableRegions.isEmpty { - return "empty" - } - return tableRegions - .sorted(by: { (l, r) in l.key < r.key }) - .map { (table, tableRegion) in - var desc = table - if let columns = tableRegion.columns { - desc += "(" + columns.sorted().joined(separator: ",") + ")" - } else { - desc += "(*)" - } - if let rowIds = tableRegion.rowIds { - desc += "[" + rowIds.sorted().map { "\($0)" }.joined(separator: ",") + "]" - } - return desc - } - .joined(separator: ",") - } -} - -private struct TableRegion: Equatable { - var columns: Set? // nil means "all columns" - var rowIds: Set? // nil means "all rowids" - - var isEmpty: Bool { - if let columns = columns, columns.isEmpty { return true } - if let rowIds = rowIds, rowIds.isEmpty { return true } - return false - } - - func intersection(_ other: TableRegion) -> TableRegion { - let columnsIntersection: Set? - switch (self.columns, other.columns) { - case (nil, let columns), (let columns, nil): - columnsIntersection = columns - case (let columns?, let other?): - columnsIntersection = columns.intersection(other) - } - - let rowIdsIntersection: Set? - switch (self.rowIds, other.rowIds) { - case (nil, let rowIds), (let rowIds, nil): - rowIdsIntersection = rowIds - case (let rowIds?, let other?): - rowIdsIntersection = rowIds.intersection(other) - } - - return TableRegion(columns: columnsIntersection, rowIds: rowIdsIntersection) - } - - func union(_ other: TableRegion) -> TableRegion { - let columnsUnion: Set? - switch (self.columns, other.columns) { - case (nil, _), (_, nil): - columnsUnion = nil - case (let columns?, let other?): - columnsUnion = columns.union(other) - } - - let rowIdsUnion: Set? - switch (self.rowIds, other.rowIds) { - case (nil, _), (_, nil): - rowIdsUnion = nil - case (let rowIds?, let other?): - rowIdsUnion = rowIds.union(other) - } - - return TableRegion(columns: columnsUnion, rowIds: rowIdsUnion) - } - - func contains(rowID: Int64) -> Bool { - guard let rowIds = rowIds else { - return true - } - return rowIds.contains(rowID) - } -} - -// MARK: - DatabaseRegionConvertible - -public protocol DatabaseRegionConvertible { - /// Returns a database region. - /// - /// - parameter db: A database connection. - func databaseRegion(_ db: Database) throws -> DatabaseRegion -} - -extension DatabaseRegion: DatabaseRegionConvertible { - /// :nodoc: - public func databaseRegion(_ db: Database) throws -> DatabaseRegion { - return self - } -} - -/// A type-erased DatabaseRegionConvertible -public struct AnyDatabaseRegionConvertible: DatabaseRegionConvertible { - let _region: (Database) throws -> DatabaseRegion - - public init(_ region: @escaping (Database) throws -> DatabaseRegion) { - _region = region - } - - public init(_ region: DatabaseRegionConvertible) { - _region = { try region.databaseRegion($0) } - } - - /// :nodoc: - public func databaseRegion(_ db: Database) throws -> DatabaseRegion { - return try _region(db) - } -} - -// MARK: - Utils - -extension DatabaseRegion { - static func union(_ regions: DatabaseRegion...) -> DatabaseRegion { - return regions.reduce(into: DatabaseRegion()) { union, region in - union.formUnion(region) - } - } - - static func union(_ regions: [DatabaseRegionConvertible]) -> (Database) throws -> DatabaseRegion { - return { db in - try regions.reduce(into: DatabaseRegion()) { union, region in - try union.formUnion(region.databaseRegion(db)) - } - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseRegionObservation.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseRegionObservation.swift deleted file mode 100755 index a55fd2b..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseRegionObservation.swift +++ /dev/null @@ -1,133 +0,0 @@ -/// DatabaseRegionObservation tracks changes in the results of database -/// requests, and notifies each database transaction whenever the -/// database changes. -/// -/// For example: -/// -/// let observation = DatabaseRegionObservation(tracking: Player.all) -/// let observer = try observation.start(in: dbQueue) { db: Database in -/// print("Players have changed.") -/// } -public struct DatabaseRegionObservation { - /// The extent of the database observation. The default is - /// `.observerLifetime`: the observation lasts until the - /// observer returned by the `start(in:onChange:)` method - /// is deallocated. - public var extent = Database.TransactionObservationExtent.observerLifetime - - /// A closure that is evaluated when the observation starts, and returns - /// the observed database region. - var observedRegion: (Database) throws -> DatabaseRegion - - // Not public because we foster DatabaseRegionConvertible. - init(tracking region: @escaping (Database) throws -> DatabaseRegion) { - self.observedRegion = { db in - // Remove views from the observed region. - // - // We can do it because we are only interested in modifications in - // actual tables. And we want to do it because we have a fast path - // for simple regions that span a single table. - let views = try db.schema().names(ofType: .view) - return try region(db).ignoring(views) - } - } - - /// Creates a DatabaseRegionObservation which observes *regions*, and - /// notifies whenever one of the observed regions is modified by a - /// database transaction. - /// - /// For example, this sample code counts the number of a times the player - /// table is modified: - /// - /// let observation = DatabaseRegionObservation(tracking: Player.all()) - /// - /// var count = 0 - /// let observer = observation.start(in: dbQueue) { _ in - /// count += 1 - /// print("Players have been modified \(count) times.") - /// } - /// - /// The observation lasts until the observer returned by `start` is - /// deallocated. See the `extent` property for more information. - /// - /// - parameter regions: A list of observed regions. - public init(tracking regions: DatabaseRegionConvertible...) { - self.init(tracking: regions) - } - - /// Creates a DatabaseRegionObservation which observes *regions*, and - /// notifies whenever one of the observed regions is modified by a - /// database transaction. - /// - /// For example, this sample code counts the number of a times the player - /// table is modified: - /// - /// let observation = DatabaseRegionObservation(tracking: [Player.all()]) - /// - /// var count = 0 - /// let observer = observation.start(in: dbQueue) { _ in - /// count += 1 - /// print("Players have been modified \(count) times.") - /// } - /// - /// The observation lasts until the observer returned by `start` is - /// deallocated. See the `extent` property for more information. - /// - /// - parameter regions: A list of observed regions. - public init(tracking regions: [DatabaseRegionConvertible]) { - self.init(tracking: DatabaseRegion.union(regions)) - } -} - -extension DatabaseRegionObservation { - /// Starts the observation in the provided database writer (such as - /// a database queue or database pool), and returns a transaction observer. - /// - /// - parameter reader: A DatabaseWriter. - /// - parameter onChange: A closure that is provided a database connection - /// with write access each time the observed region has been modified. - /// - returns: a TransactionObserver - public func start(in dbWriter: DatabaseWriter, onChange: @escaping (Database) -> Void) throws -> TransactionObserver { - // Use unsafeReentrantWrite so that observation can start from any - // dispatch queue. - return try dbWriter.unsafeReentrantWrite { db -> TransactionObserver in - let region = try observedRegion(db) - let observer = DatabaseRegionObserver(region: region, onChange: onChange) - db.add(transactionObserver: observer, extent: extent) - return observer - } - } -} - -private class DatabaseRegionObserver: TransactionObserver { - let region: DatabaseRegion - let onChange: (Database) -> Void - var isChanged = false - - init(region: DatabaseRegion, onChange: @escaping (Database) -> Void) { - self.region = region - self.onChange = onChange - } - - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - return region.isModified(byEventsOfKind: eventKind) - } - - func databaseDidChange(with event: DatabaseEvent) { - if region.isModified(by: event) { - isChanged = true - stopObservingDatabaseChangesUntilNextTransaction() - } - } - - func databaseDidCommit(_ db: Database) { - guard isChanged else { return } - isChanged = false - - onChange(db) - } - - func databaseDidRollback(_ db: Database) { - isChanged = false - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseSchemaCache.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseSchemaCache.swift deleted file mode 100755 index 4f29cef..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseSchemaCache.swift +++ /dev/null @@ -1,89 +0,0 @@ -/// The protocol for database schema cache. -protocol DatabaseSchemaCache { - mutating func clear() - - var schemaInfo: SchemaInfo? { get set } - - func primaryKey(_ table: String) -> PrimaryKeyInfo? - mutating func set(primaryKey: PrimaryKeyInfo, forTable table: String) - - func columns(in table: String) -> [ColumnInfo]? - mutating func set(columns: [ColumnInfo], forTable table: String) - - func indexes(on table: String) -> [IndexInfo]? - mutating func set(indexes: [IndexInfo], forTable table: String) - - func foreignKeys(on table: String) -> [ForeignKeyInfo]? - mutating func set(foreignKeys: [ForeignKeyInfo], forTable table: String) -} - -/// A thread-unsafe database schema cache -struct SimpleDatabaseSchemaCache: DatabaseSchemaCache { - var schemaInfo: SchemaInfo? - private var primaryKeys: [String: PrimaryKeyInfo] = [:] - private var columns: [String: [ColumnInfo]] = [:] - private var indexes: [String: [IndexInfo]] = [:] - private var foreignKeys: [String: [ForeignKeyInfo]] = [:] - - mutating func clear() { - primaryKeys = [:] - columns = [:] - indexes = [:] - foreignKeys = [:] - schemaInfo = nil - } - - func primaryKey(_ table: String) -> PrimaryKeyInfo? { - return primaryKeys[table] - } - - mutating func set(primaryKey: PrimaryKeyInfo, forTable table: String) { - primaryKeys[table] = primaryKey - } - - func columns(in table: String) -> [ColumnInfo]? { - return columns[table] - } - - mutating func set(columns: [ColumnInfo], forTable table: String) { - self.columns[table] = columns - } - - func indexes(on table: String) -> [IndexInfo]? { - return indexes[table] - } - - mutating func set(indexes: [IndexInfo], forTable table: String) { - self.indexes[table] = indexes - } - - func foreignKeys(on table: String) -> [ForeignKeyInfo]? { - return foreignKeys[table] - } - - mutating func set(foreignKeys: [ForeignKeyInfo], forTable table: String) { - self.foreignKeys[table] = foreignKeys - } -} - -/// An always empty database schema cache -struct EmptyDatabaseSchemaCache: DatabaseSchemaCache { - func clear() { } - - var schemaInfo: SchemaInfo? { - get { return nil } - set { } - } - - func primaryKey(_ table: String) -> PrimaryKeyInfo? { return nil } - func set(primaryKey: PrimaryKeyInfo, forTable table: String) { } - - func columns(in table: String) -> [ColumnInfo]? { return nil } - func set(columns: [ColumnInfo], forTable table: String) { } - - func indexes(on table: String) -> [IndexInfo]? { return nil } - func set(indexes: [IndexInfo], forTable table: String) { } - - func foreignKeys(on table: String) -> [ForeignKeyInfo]? { return nil } - func set(foreignKeys: [ForeignKeyInfo], forTable table: String) { } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseSnapshot.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseSnapshot.swift deleted file mode 100755 index fc7b28a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseSnapshot.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Dispatch - -/// A DatabaseSnapshot sees an unchanging database content, as it existed at the -/// moment it was created. -/// -/// See DatabasePool.makeSnapshot() -/// -/// For more information, read about "snapshot isolation" at https://sqlite.org/isolation.html -public class DatabaseSnapshot : DatabaseReader { - private var serializedDatabase: SerializedDatabase - - /// The database configuration - var configuration: Configuration { - return serializedDatabase.configuration - } - - init(path: String, configuration: Configuration = Configuration(), defaultLabel: String, purpose: String) throws { - var configuration = configuration - configuration.readonly = true - configuration.allowsUnsafeTransactions = true // Snaphost keeps a long-lived transaction - - serializedDatabase = try SerializedDatabase( - path: path, - configuration: configuration, - schemaCache: SimpleDatabaseSchemaCache(), - defaultLabel: defaultLabel, - purpose: purpose) - - try serializedDatabase.sync { db in - // Assert WAL mode - let journalMode = try String.fetchOne(db, sql: "PRAGMA journal_mode") - guard journalMode == "wal" else { - throw DatabaseError(message: "WAL mode is not activated at path: \(path)") - } - try db.beginSnapshotIsolation() - } - } - - deinit { - // Leave snapshot isolation - serializedDatabase.sync { db in - try? db.commit() - } - } -} - -// DatabaseReader -extension DatabaseSnapshot { - - // MARK: - Reading from Database - - /// Synchronously executes a read-only block that takes a database - /// connection, and returns its result. - /// - /// let players = try snapshot.read { db in - /// try Player.fetchAll(...) - /// } - /// - /// - parameter block: A block that accesses the database. - /// - throws: The error thrown by the block. - public func read(_ block: (Database) throws -> T) rethrows -> T { - return try serializedDatabase.sync(block) - } - - /// Alias for `read`. See `DatabaseReader.unsafeRead`. - /// - /// :nodoc: - public func unsafeRead(_ block: (Database) throws -> T) rethrows -> T { - return try serializedDatabase.sync(block) - } - - /// Alias for `read`. See `DatabaseReader.unsafeReentrantRead`. - /// - /// :nodoc: - public func unsafeReentrantRead(_ block: (Database) throws -> T) throws -> T { - return try serializedDatabase.sync(block) - } - - // MARK: - Functions - - public func add(function: DatabaseFunction) { - serializedDatabase.sync { $0.add(function: function) } - } - - public func remove(function: DatabaseFunction) { - serializedDatabase.sync { $0.remove(function: function) } - } - - // MARK: - Collations - - public func add(collation: DatabaseCollation) { - serializedDatabase.sync { $0.add(collation: collation) } - } - - public func remove(collation: DatabaseCollation) { - serializedDatabase.sync { $0.remove(collation: collation) } - } - - // MARK: - Value Observation - - public func add( - observation: ValueObservation, - onError: ((Error) -> Void)?, - onChange: @escaping (Reducer.Value) -> Void) - throws -> TransactionObserver - { - // Deal with initial value - switch observation.scheduling { - case .mainQueue: - if let value = try unsafeReentrantRead(observation.initialValue) { - if DispatchQueue.isMain { - onChange(value) - } else { - DispatchQueue.main.async { - onChange(value) - } - } - } - case let .async(onQueue: queue, startImmediately: startImmediately): - if startImmediately { - if let value = try unsafeReentrantRead(observation.initialValue) { - queue.async { - onChange(value) - } - } - } - case let .unsafe(startImmediately: startImmediately): - if startImmediately { - if let value = try unsafeReentrantRead(observation.initialValue) { - onChange(value) - } - } - } - - // Return a dummy observer, because snapshots never change - return SnapshotValueObserver() - } - - public func remove(transactionObserver: TransactionObserver) { - // Can't remove an observer which could not be added :-) - } -} - -extension ValueObservation where Reducer: ValueReducer { - /// Helper method for DatabaseSnapshot.add(observation:onError:onChange:) - fileprivate func initialValue(_ db: Database) throws -> Reducer.Value? { - var reducer = try makeReducer(db) - let fetched = try reducer.fetch(db) - return reducer.value(fetched) - } -} - -/// An observer that does nothing, support for -/// `DatabaseSnapshot.add(observation:onError:onChange:)`. -private class SnapshotValueObserver: TransactionObserver { - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { return false } - func databaseDidChange(with event: DatabaseEvent) { } - func databaseDidCommit(_ db: Database) { } - func databaseDidRollback(_ db: Database) { } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValue.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValue.swift deleted file mode 100755 index d2b44f8..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValue.swift +++ /dev/null @@ -1,341 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -// MARK: - DatabaseValue - -/// DatabaseValue is the intermediate type between SQLite and your values. -/// -/// See https://www.sqlite.org/datatype3.html -public struct DatabaseValue: Hashable, CustomStringConvertible, DatabaseValueConvertible, SQLExpression { - /// The SQLite storage - public let storage: Storage - - /// The NULL DatabaseValue. - public static let null = DatabaseValue(storage: .null) - - /// An SQLite storage (NULL, INTEGER, REAL, TEXT, BLOB). - public enum Storage : Equatable { - /// The NULL storage class. - case null - - /// The INTEGER storage class, wrapping an Int64. - case int64(Int64) - - /// The REAL storage class, wrapping a Double. - case double(Double) - - /// The TEXT storage class, wrapping a String. - case string(String) - - /// The BLOB storage class, wrapping Data. - case blob(Data) - - /// Returns Int64, Double, String, Data or nil. - public var value: DatabaseValueConvertible? { - switch self { - case .null: - return nil - case .int64(let int64): - return int64 - case .double(let double): - return double - case .string(let string): - return string - case .blob(let data): - return data - } - } - - /// Return true if the storages are identical. - /// - /// Unlike DatabaseValue equality that considers the integer 1 to be - /// equal to the 1.0 double (as SQLite does), int64 and double storages - /// are never equal. - public static func == (_ lhs: Storage, _ rhs: Storage) -> Bool { - switch (lhs, rhs) { - case (.null, .null): return true - case (.int64(let lhs), .int64(let rhs)): return lhs == rhs - case (.double(let lhs), .double(let rhs)): return lhs == rhs - case (.string(let lhs), .string(let rhs)): return lhs == rhs - case (.blob(let lhs), .blob(let rhs)): return lhs == rhs - default: return false - } - } - } - - /// Creates a DatabaseValue from Any. - /// - /// The result is nil unless object adopts DatabaseValueConvertible. - public init?(value: Any) { - guard let convertible = value as? DatabaseValueConvertible else { - return nil - } - self = convertible.databaseValue - } - - // MARK: - Extracting Value - - /// Returns true if databaseValue is NULL. - public var isNull: Bool { - switch storage { - case .null: - return true - default: - return false - } - } - - // MARK: - Not Public - - init(storage: Storage) { - self.storage = storage - } - - // SQLite function argument - init(sqliteValue: SQLiteValue) { - switch sqlite3_value_type(sqliteValue) { - case SQLITE_NULL: - storage = .null - case SQLITE_INTEGER: - storage = .int64(sqlite3_value_int64(sqliteValue)) - case SQLITE_FLOAT: - storage = .double(sqlite3_value_double(sqliteValue)) - case SQLITE_TEXT: - storage = .string(String(cString: sqlite3_value_text(sqliteValue)!)) - case SQLITE_BLOB: - if let bytes = sqlite3_value_blob(sqliteValue) { - let count = Int(sqlite3_value_bytes(sqliteValue)) - storage = .blob(Data(bytes: bytes, count: count)) // copy bytes - } else { - storage = .blob(Data()) - } - case let type: - // Assume a GRDB bug: there is no point throwing any error. - fatalError("Unexpected SQLite value type: \(type)") - } - } - - /// Returns a DatabaseValue initialized from a raw SQLite statement pointer. - init(sqliteStatement: SQLiteStatement, index: Int32) { - switch sqlite3_column_type(sqliteStatement, index) { - case SQLITE_NULL: - storage = .null - case SQLITE_INTEGER: - storage = .int64(sqlite3_column_int64(sqliteStatement, index)) - case SQLITE_FLOAT: - storage = .double(sqlite3_column_double(sqliteStatement, index)) - case SQLITE_TEXT: - storage = .string(String(cString: sqlite3_column_text(sqliteStatement, index))) - case SQLITE_BLOB: - if let bytes = sqlite3_column_blob(sqliteStatement, index) { - let count = Int(sqlite3_column_bytes(sqliteStatement, index)) - storage = .blob(Data(bytes: bytes, count: count)) // copy bytes - } else { - storage = .blob(Data()) - } - case let type: - // Assume a GRDB bug: there is no point throwing any error. - fatalError("Unexpected SQLite column type: \(type)") - } - } -} - -// MARK: - Hashable & Equatable - -// Hashable -extension DatabaseValue { - - /// :nodoc: - public func hash(into hasher: inout Hasher) { - switch storage { - case .null: - hasher.combine(0) - case .int64(let int64): - // 1 == 1.0, hence 1 and 1.0 must have the same hash: - hasher.combine(Double(int64)) - case .double(let double): - hasher.combine(double) - case .string(let string): - hasher.combine(string) - case .blob(let data): - hasher.combine(data) - } - } - - /// Returns whether two DatabaseValues are equal. - /// - /// 1.databaseValue == "foo".databaseValue // false - /// 1.databaseValue == 1.databaseValue // true - /// - /// When comparing integers and doubles, the result is true if and only - /// values are equal, and if converting one type to the other does - /// not lose information: - /// - /// 1.databaseValue == 1.0.databaseValue // true - /// - /// For a comparison that distinguishes integer and doubles, compare - /// storages instead: - /// - /// 1.databaseValue.storage == 1.0.databaseValue.storage // false - public static func == (lhs: DatabaseValue, rhs: DatabaseValue) -> Bool { - switch (lhs.storage, rhs.storage) { - case (.null, .null): - return true - case (.int64(let lhs), .int64(let rhs)): - return lhs == rhs - case (.double(let lhs), .double(let rhs)): - return lhs == rhs - case (.int64(let lhs), .double(let rhs)): - return Int64(exactly: rhs) == lhs - case (.double(let lhs), .int64(let rhs)): - return rhs == Int64(exactly: lhs) - case (.string(let lhs), .string(let rhs)): - return lhs == rhs - case (.blob(let lhs), .blob(let rhs)): - return lhs == rhs - default: - return false - } - } -} - -// DatabaseValueConvertible -extension DatabaseValue { - /// Returns self - public var databaseValue: DatabaseValue { - return self - } - - /// Returns the database value - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> DatabaseValue? { - return dbValue - } -} - -// SQLExpressible -extension DatabaseValue { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var sqlExpression: SQLExpression { - return self - } -} - -// SQLExpression -extension DatabaseValue { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func expressionSQL(_ context: inout SQLGenerationContext) -> String { - // fast path for NULL - if isNull { - return "NULL" - } - - if context.append(arguments: [self]) { - return "?" - } else { - // Correctness above all: use SQLite to quote the value. - // Assume that the Quote function always succeeds - return DatabaseQueue().inDatabase { try! String.fetchOne($0, sql: "SELECT QUOTE(?)", arguments: [self])! } - } - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var negated: SQLExpression { - switch storage { - case .null: - // SELECT NOT NULL -- NULL - return DatabaseValue.null - case .int64(let int64): - return (int64 == 0).sqlExpression - case .double(let double): - return (double == 0.0).sqlExpression - case .string: - // We can't assume all strings are true, and return false: - // - // SELECT NOT '1' -- 0 (because '1' is turned into the integer 1, which is negated into 0) - // SELECT NOT '0' -- 1 (because '0' is turned into the integer 0, which is negated into 1) - return SQLExpressionNot(self) - case .blob: - // We can't assume all blobs are true, and return false: - // - // SELECT NOT X'31' -- 0 (because X'31' is turned into the string '1', then into integer 1, which is negated into 0) - // SELECT NOT X'30' -- 1 (because X'30' is turned into the string '0', then into integer 0, which is negated into 1) - return SQLExpressionNot(self) - } - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return self - } -} - -// CustomStringConvertible -extension DatabaseValue { - /// :nodoc: - public var description: String { - switch storage { - case .null: - return "NULL" - case .int64(let int64): - return String(int64) - case .double(let double): - return String(double) - case .string(let string): - return String(reflecting: string) - case .blob(let data): - return "Data(\(data.description))" - } - } -} - -/// Compares DatabaseValue like SQLite. -/// -/// See RxGRDB for tests. -/// -/// This comparison is not public because it does not handle text collations, -/// and may be dangerous when put in user hands. -/// -/// So far, the only goal of this sorting method so far is aesthetic, and -/// easier testing. -func < (lhs: DatabaseValue, rhs: DatabaseValue) -> Bool { - switch (lhs.storage, rhs.storage) { - case (.int64(let lhs), .int64(let rhs)): - return lhs < rhs - case (.double(let lhs), .double(let rhs)): - return lhs < rhs - case (.int64(let lhs), .double(let rhs)): - return Double(lhs) < rhs - case (.double(let lhs), .int64(let rhs)): - return lhs < Double(rhs) - case (.string(let lhs), .string(let rhs)): - return lhs.utf8.lexicographicallyPrecedes(rhs.utf8) - case (.blob(let lhs), .blob(let rhs)): - return lhs.lexicographicallyPrecedes(rhs, by: <) - case (.blob, _): - return false - case (_, .blob): - return true - case (.string, _): - return false - case (_, .string): - return true - case (.int64, _), (.double, _): - return false - case (_, .int64), (_, .double): - return true - case (.null, _): - return false - case (_, .null): - return true - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValueConversion.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValueConversion.swift deleted file mode 100755 index a752e34..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValueConversion.swift +++ /dev/null @@ -1,269 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -// MARK: - Conversion Context and Errors - -/// A type that helps the user understanding value conversion errors -struct ValueConversionContext { - private enum Column { - case columnIndex(Int) - case columnName(String) - } - var row: Row? - var sql: String? - var arguments: StatementArguments? - private var column: Column? - - func atColumn(_ columnIndex: Int) -> ValueConversionContext { - var result = self - result.column = .columnIndex(columnIndex) - return result - } - - func atColumn(_ columnName: String) -> ValueConversionContext { - var result = self - result.column = .columnName(columnName) - return result - } - - var columnIndex: Int? { - guard let column = column else { return nil } - switch column { - case .columnIndex(let index): - return index - case .columnName(let name): - return row?.index(ofColumn: name) - } - } - - var columnName: String? { - guard let column = column else { return nil } - switch column { - case .columnIndex(let index): - guard let row = row else { return nil } - return Array(row.columnNames)[index] - case .columnName(let name): - return name - } - } -} - -extension ValueConversionContext { - init(_ statement: SelectStatement) { - self.init( - row: Row(statement: statement).copy(), - sql: statement.sql, - arguments: statement.arguments, - column: nil) - } - - init(_ row: Row) { - if let statement = row.statement { - self.init( - row: row.copy(), - sql: statement.sql, - arguments: statement.arguments, - column: nil) - } else if let sqliteStatement = row.sqliteStatement { - let sql = String(cString: sqlite3_sql(sqliteStatement)).trimmingCharacters(in: .sqlStatementSeparators) - self.init( - row: row.copy(), - sql: sql, - arguments: nil, - column: nil) - } else { - self.init( - row: row.copy(), - sql: nil, - arguments: nil, - column: nil) - } - } -} - -/// The canonical conversion error message -/// -/// - parameter dbValue: nil means "missing column" -func conversionErrorMessage(to: T.Type, from dbValue: DatabaseValue?, conversionContext: ValueConversionContext?) -> String { - var message: String - var extras: [String] = [] - - if let dbValue = dbValue { - message = "could not convert database value \(dbValue) to \(T.self)" - if let columnName = conversionContext?.columnName { - extras.append("column: `\(columnName)`") - } - if let columnIndex = conversionContext?.columnIndex { - extras.append("column index: \(columnIndex)") - } - } else { - message = "could not read \(T.self) from missing column" - if let columnName = conversionContext?.columnName { - message += " `\(columnName)`" - } - } - - if let row = conversionContext?.row { - extras.append("row: \(row)") - } - - if let sql = conversionContext?.sql { - extras.append("sql: `\(sql)`") - if let arguments = conversionContext?.arguments, arguments.isEmpty == false { - extras.append("arguments: \(arguments)") - } - } - - if extras.isEmpty == false { - message += " (" + extras.joined(separator: ", ") + ")" - } - return message -} - -/// The canonical conversion fatal error -/// -/// - parameter dbValue: nil means "missing column", for consistency with (row["missing"] as DatabaseValue? == nil) -func fatalConversionError(to: T.Type, from dbValue: DatabaseValue?, conversionContext: ValueConversionContext?, file: StaticString = #file, line: UInt = #line) -> Never { - fatalError(conversionErrorMessage(to: T.self, from: dbValue, conversionContext: conversionContext), file: file, line: line) -} - -@usableFromInline -func fatalConversionError(to: T.Type, from dbValue: DatabaseValue?, in row: Row, atColumn columnName: String, file: StaticString = #file, line: UInt = #line) -> Never { - fatalConversionError( - to: T.self, - from: dbValue, - conversionContext: ValueConversionContext(row).atColumn(columnName)) -} - -@usableFromInline -func fatalConversionError(to: T.Type, sqliteStatement: SQLiteStatement, index: Int32, file: StaticString = #file, line: UInt = #line) -> Never { - let row = Row(sqliteStatement: sqliteStatement) - fatalConversionError( - to: T.self, - from: DatabaseValue(sqliteStatement: sqliteStatement, index: index), - conversionContext: ValueConversionContext(row).atColumn(Int(index))) -} - -@usableFromInline -func fatalConversionError(to: T.Type, from dbValue: DatabaseValue?, sqliteStatement: SQLiteStatement, index: Int32, file: StaticString = #file, line: UInt = #line) -> Never { - let row = Row(sqliteStatement: sqliteStatement) - fatalConversionError( - to: T.self, - from: dbValue, - conversionContext: ValueConversionContext(row).atColumn(Int(index))) -} - -// MARK: - DatabaseValueConvertible - -/// Lossless conversions from database values and rows -extension DatabaseValueConvertible { - @usableFromInline - static func decode(from sqliteStatement: SQLiteStatement, atUncheckedIndex index: Int32) -> Self { - let dbValue = DatabaseValue(sqliteStatement: sqliteStatement, index: index) - if let value = fromDatabaseValue(dbValue) { - return value - } else { - fatalConversionError(to: Self.self, from: dbValue, sqliteStatement: sqliteStatement, index: index) - } - } - - static func decode(from dbValue: DatabaseValue, conversionContext: @autoclosure () -> ValueConversionContext?) -> Self { - if let value = fromDatabaseValue(dbValue) { - return value - } else { - fatalConversionError(to: Self.self, from: dbValue, conversionContext: conversionContext()) - } - } - - @usableFromInline - static func decode(from row: Row, atUncheckedIndex index: Int) -> Self { - return decode( - from: row.impl.databaseValue(atUncheckedIndex: index), - conversionContext: ValueConversionContext(row).atColumn(index)) - } - - @usableFromInline - static func decodeIfPresent(from sqliteStatement: SQLiteStatement, atUncheckedIndex index: Int32) -> Self? { - let dbValue = DatabaseValue(sqliteStatement: sqliteStatement, index: index) - if let value = fromDatabaseValue(dbValue) { - return value - } else if dbValue.isNull { - return nil - } else { - fatalConversionError(to: Self.self, from: dbValue, sqliteStatement: sqliteStatement, index: index) - } - } - - static func decodeIfPresent(from dbValue: DatabaseValue, conversionContext: @autoclosure () -> ValueConversionContext?) -> Self? { - // Use fromDatabaseValue before checking for null: this allows DatabaseValue to convert NULL to .null. - if let value = fromDatabaseValue(dbValue) { - return value - } else if dbValue.isNull { - return nil - } else { - fatalConversionError(to: Self.self, from: dbValue, conversionContext: conversionContext()) - } - } - - @usableFromInline - static func decodeIfPresent(from row: Row, atUncheckedIndex index: Int) -> Self? { - return decodeIfPresent( - from: row.impl.databaseValue(atUncheckedIndex: index), - conversionContext: ValueConversionContext(row).atColumn(index)) - } -} - -// MARK: - DatabaseValueConvertible & StatementColumnConvertible - -/// Lossless conversions from database values and rows -extension DatabaseValueConvertible where Self: StatementColumnConvertible { - @inlinable - static func fastDecode(from sqliteStatement: SQLiteStatement, atUncheckedIndex index: Int32) -> Self { - if sqlite3_column_type(sqliteStatement, index) == SQLITE_NULL { - fatalConversionError(to: Self.self, sqliteStatement: sqliteStatement, index: index) - } - return self.init(sqliteStatement: sqliteStatement, index: index) - } - - @inlinable - static func fastDecode(from row: Row, atUncheckedIndex index: Int) -> Self { - if let sqliteStatement = row.sqliteStatement { - return fastDecode(from: sqliteStatement, atUncheckedIndex: Int32(index)) - } - return row.fastDecode(Self.self, atUncheckedIndex: index) - } - - @inlinable - static func fastDecodeIfPresent(from sqliteStatement: SQLiteStatement, atUncheckedIndex index: Int32) -> Self? { - if sqlite3_column_type(sqliteStatement, index) == SQLITE_NULL { - return nil - } - return self.init(sqliteStatement: sqliteStatement, index: index) - } - - @inlinable - static func fastDecodeIfPresent(from row: Row, atUncheckedIndex index: Int) -> Self? { - if let sqliteStatement = row.sqliteStatement { - return fastDecodeIfPresent(from: sqliteStatement, atUncheckedIndex: Int32(index)) - } - return row.fastDecodeIfPresent(Self.self, atUncheckedIndex: index) - } -} - -// Support for @inlinable decoding -extension Row { - @usableFromInline - func fastDecode(_ type: Value.Type, atUncheckedIndex index: Int) -> Value { - return impl.fastDecode(type, atUncheckedIndex: index) - } - - @usableFromInline - func fastDecodeIfPresent(_ type: Value.Type, atUncheckedIndex index: Int) -> Value? { - return impl.fastDecodeIfPresent(type, atUncheckedIndex: index) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValueConvertible.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValueConvertible.swift deleted file mode 100755 index 3c0095e..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseValueConvertible.swift +++ /dev/null @@ -1,575 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -// MARK: - DatabaseValueConvertible - -/// Types that adopt DatabaseValueConvertible can be initialized from -/// database values. -/// -/// The protocol comes with built-in methods that allow to fetch cursors, -/// arrays, or single values: -/// -/// try String.fetchCursor(db, sql: "SELECT name FROM ...", arguments:...) // Cursor of String -/// try String.fetchAll(db, sql: "SELECT name FROM ...", arguments:...) // [String] -/// try String.fetchOne(db, sql: "SELECT name FROM ...", arguments:...) // String? -/// -/// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") -/// try String.fetchCursor(statement, arguments:...) // Cursor of String -/// try String.fetchAll(statement, arguments:...) // [String] -/// try String.fetchOne(statement, arguments:...) // String? -/// -/// DatabaseValueConvertible is adopted by Bool, Int, String, etc. -public protocol DatabaseValueConvertible : SQLExpressible { - /// Returns a value that can be stored in the database. - var databaseValue: DatabaseValue { get } - - /// Returns a value initialized from *dbValue*, if possible. - static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? -} - -extension DatabaseValueConvertible { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var sqlExpression: SQLExpression { - return databaseValue - } -} - -/// A cursor of database values extracted from a single column. -/// For example: -/// -/// try dbQueue.read { db in -/// let urls: DatabaseValueCursor = try URL.fetchCursor(db, sql: "SELECT url FROM link") -/// while let url = urls.next() { // URL -/// print(url) -/// } -/// } -public final class DatabaseValueCursor : Cursor { - @usableFromInline let _statement: SelectStatement - @usableFromInline let _sqliteStatement: SQLiteStatement - @usableFromInline let _columnIndex: Int32 - @usableFromInline var _done = false - - init(statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { - _statement = statement - _sqliteStatement = statement.sqliteStatement - if let adapter = adapter { - // adapter may redefine the index of the leftmost column - _columnIndex = try Int32(adapter.baseColumnIndex(atIndex: 0, layout: statement)) - } else { - _columnIndex = 0 - } - _statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? _statement.reset() - } - - /// :nodoc: - @inlinable - public func next() throws -> Value? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return Value.decode(from: _sqliteStatement, atUncheckedIndex: _columnIndex) - case let code: - try _statement.didFail(withResultCode: code) - } - } -} - -/// A cursor of optional database values extracted from a single column. -/// For example: -/// -/// try dbQueue.read { db in -/// let urls: NullableDatabaseValueCursor = try Optional.fetchCursor(db, sql: "SELECT url FROM link") -/// while let url = urls.next() { // URL? -/// print(url) -/// } -/// } -public final class NullableDatabaseValueCursor : Cursor { - @usableFromInline let _statement: SelectStatement - @usableFromInline let _sqliteStatement: SQLiteStatement - @usableFromInline let _columnIndex: Int32 - @usableFromInline var _done = false - - init(statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { - _statement = statement - _sqliteStatement = statement.sqliteStatement - if let adapter = adapter { - // adapter may redefine the index of the leftmost column - _columnIndex = try Int32(adapter.baseColumnIndex(atIndex: 0, layout: statement)) - } else { - _columnIndex = 0 - } - _statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? _statement.reset() - } - - /// :nodoc: - @inlinable - public func next() throws -> Value?? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return Value.decodeIfPresent(from: _sqliteStatement, atUncheckedIndex: _columnIndex) - case let code: - try _statement.didFail(withResultCode: code) - } - } -} - -/// DatabaseValueConvertible comes with built-in methods that allow to fetch -/// cursors, arrays, or single values: -/// -/// try String.fetchCursor(db, sql: "SELECT name FROM ...", arguments:...) // Cursor of String -/// try String.fetchAll(db, sql: "SELECT name FROM ...", arguments:...) // [String] -/// try String.fetchOne(db, sql: "SELECT name FROM ...", arguments:...) // String? -/// -/// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") -/// try String.fetchCursor(statement, arguments:...) // Cursor of String -/// try String.fetchAll(statement, arguments:...) // [String] -/// try String.fetchOne(statement, arguments:...) // String -/// -/// DatabaseValueConvertible is adopted by Bool, Int, String, etc. -extension DatabaseValueConvertible { - - // MARK: Fetching From SelectStatement - - /// Returns a cursor over values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try String.fetchCursor(statement) // Cursor of String - /// while let name = try names.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseValueCursor { - return try DatabaseValueCursor(statement: statement, arguments: arguments, adapter: adapter) - } - - /// Returns an array of values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try String.fetchAll(statement) // [String] - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { - return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) - } - - /// Returns a single value fetched from a prepared statement. - /// - /// The result is nil if the query returns no row, or if no value can be - /// extracted from the first row. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let name = try String.fetchOne(statement) // String? - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { - // fetchOne returns nil if there is no row, or if there is a row with a null value - let cursor = try NullableDatabaseValueCursor(statement: statement, arguments: arguments, adapter: adapter) - return try cursor.next() ?? nil - } -} - -extension DatabaseValueConvertible { - - // MARK: Fetching From SQL - - /// Returns a cursor over values fetched from an SQL query. - /// - /// let names = try String.fetchCursor(db, sql: "SELECT name FROM ...") // Cursor of String - /// while let name = try name.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> DatabaseValueCursor { - return try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns an array of values fetched from an SQL query. - /// - /// let names = try String.fetchAll(db, sql: "SELECT name FROM ...") // [String] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> [Self] { - return try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns a single value fetched from an SQL query. - /// - /// The result is nil if the query returns no row, or if no value can be - /// extracted from the first row. - /// - /// let name = try String.fetchOne(db, sql: "SELECT name FROM ...") // String? - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> Self? { - return try fetchOne(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } -} - -extension DatabaseValueConvertible { - - // MARK: Fetching From FetchRequest - - /// Returns a cursor over values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try String.fetchCursor(db, request) // Cursor of String - /// while let name = try name.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: R) throws -> DatabaseValueCursor { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try String.fetchAll(db, request) // [String] - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An array. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: R) throws -> [Self] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchAll(statement, adapter: adapter) - } - - /// Returns a single value fetched from a fetch request. - /// - /// The result is nil if the query returns no row, or if no value can be - /// extracted from the first row. - /// - /// let request = Player.filter(key: 1).select(Column("name")) - /// let name = try String.fetchOne(db, request) // String? - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, _ request: R) throws -> Self? { - let (statement, adapter) = try request.prepare(db, forSingleResult: true) - return try fetchOne(statement, adapter: adapter) - } -} - -extension FetchRequest where RowDecoder: DatabaseValueConvertible { - - // MARK: Fetching Values - - /// A cursor over fetched values. - /// - /// let request: ... // Some FetchRequest that fetches String - /// let strings = try request.fetchCursor(db) // Cursor of String - /// while let string = try strings.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> DatabaseValueCursor { - return try RowDecoder.fetchCursor(db, self) - } - - /// An array of fetched values. - /// - /// let request: ... // Some FetchRequest that fetches String - /// let strings = try request.fetchAll(db) // [String] - /// - /// - parameter db: A database connection. - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [RowDecoder] { - return try RowDecoder.fetchAll(db, self) - } - - /// The first fetched value. - /// - /// The result is nil if the request returns no row, or if no value can be - /// extracted from the first row. - /// - /// let request: ... // Some FetchRequest that fetches String - /// let string = try request.fetchOne(db) // String? - /// - /// - parameter db: A database connection. - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchOne(_ db: Database) throws -> RowDecoder? { - return try RowDecoder.fetchOne(db, self) - } -} - -/// Swift's Optional comes with built-in methods that allow to fetch cursors -/// and arrays of optional DatabaseValueConvertible: -/// -/// try Optional.fetchCursor(db, sql: "SELECT name FROM ...", arguments:...) // Cursor of String? -/// try Optional.fetchAll(db, sql: "SELECT name FROM ...", arguments:...) // [String?] -/// -/// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") -/// try Optional.fetchCursor(statement, arguments:...) // Cursor of String? -/// try Optional.fetchAll(statement, arguments:...) // [String?] -/// -/// DatabaseValueConvertible is adopted by Bool, Int, String, etc. -extension Optional where Wrapped: DatabaseValueConvertible { - - // MARK: Fetching From SelectStatement - - /// Returns a cursor over optional values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try Optional.fetchCursor(statement) // Cursor of String? - /// while let name = try names.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> NullableDatabaseValueCursor { - return try NullableDatabaseValueCursor(statement: statement, arguments: arguments, adapter: adapter) - } - - /// Returns an array of optional values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try Optional.fetchAll(statement) // [String?] - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] { - return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) - } -} - -extension Optional where Wrapped: DatabaseValueConvertible { - - // MARK: Fetching From SQL - - /// Returns a cursor over optional values fetched from an SQL query. - /// - /// let names = try Optional.fetchCursor(db, sql: "SELECT name FROM ...") // Cursor of String? - /// while let name = try names.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> NullableDatabaseValueCursor { - return try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns an array of optional values fetched from an SQL query. - /// - /// let names = try String.fetchAll(db, sql: "SELECT name FROM ...") // [String?] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> [Wrapped?] { - return try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } -} - -extension Optional where Wrapped: DatabaseValueConvertible { - - // MARK: Fetching From FetchRequest - - /// Returns a cursor over optional values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try Optional.fetchCursor(db, request) // Cursor of String? - /// while let name = try names.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: A cursor over fetched optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: R) throws -> NullableDatabaseValueCursor { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of optional values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try Optional.fetchAll(db, request) // [String?] - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An array of optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: R) throws -> [Wrapped?] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchAll(statement, adapter: adapter) - } -} - -extension FetchRequest where RowDecoder: _OptionalProtocol, RowDecoder._Wrapped: DatabaseValueConvertible { - - // MARK: Fetching Optional values - - /// A cursor over fetched optional values. - /// - /// let request: ... // Some FetchRequest that fetches Optional - /// let strings = try request.fetchCursor(db) // Cursor of String? - /// while let string = try strings.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> NullableDatabaseValueCursor { - return try Optional.fetchCursor(db, self) - } - - /// An array of fetched optional values. - /// - /// let request: ... // Some FetchRequest that fetches Optional - /// let strings = try request.fetchAll(db) // [String?] - /// - /// - parameter db: A database connection. - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [RowDecoder._Wrapped?] { - return try Optional.fetchAll(db, self) - } -} - diff --git a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseWriter.swift b/Example/Pods/GRDB.swift/GRDB/Core/DatabaseWriter.swift deleted file mode 100755 index c313ef2..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/DatabaseWriter.swift +++ /dev/null @@ -1,396 +0,0 @@ -import Dispatch - -/// The protocol for all types that can update a database. -/// -/// It is adopted by DatabaseQueue and DatabasePool. -/// -/// The protocol comes with isolation guarantees that describe the behavior of -/// adopting types in a multithreaded application. -/// -/// Types that adopt the protocol can in practice provide stronger guarantees. -/// For example, DatabaseQueue provides a stronger isolation level -/// than DatabasePool. -/// -/// **Warning**: Isolation guarantees stand as long as there is no external -/// connection to the database. Should you have to cope with external -/// connections, protect yourself with transactions, and be ready to setup a -/// [busy handler](https://www.sqlite.org/c3ref/busy_handler.html). -public protocol DatabaseWriter : DatabaseReader { - - // MARK: - Writing in Database - - /// Synchronously executes a block that takes a database connection, and - /// returns its result. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// Eventual concurrent reads are guaranteed not to see any changes - /// performed in the block until they are all saved in the database. - /// - /// The block may, or may not, be wrapped inside a transaction. - /// - /// This method is *not* reentrant. - func write(_ block: (Database) throws -> T) throws -> T - - /// Synchronously executes a block that takes a database connection, and - /// returns its result. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// Eventual concurrent reads may see changes performed in the block before - /// the block completes. - /// - /// The block is guaranteed to be executed outside of a transaction. - /// - /// This method is *not* reentrant. - func writeWithoutTransaction(_ block: (Database) throws -> T) rethrows -> T - - /// Synchronously executes a block that takes a database connection, and - /// returns its result. - /// - /// Eventual concurrent database updates are postponed until the block - /// has executed. - /// - /// Eventual concurrent reads may see changes performed in the block before - /// the block completes. - /// - /// This method is reentrant. It should be avoided because it fosters - /// dangerous concurrency practices. - func unsafeReentrantWrite(_ block: (Database) throws -> T) rethrows -> T - - // MARK: - Reading from Database - - /// Concurrently executes a read-only block that takes a - /// database connection. - /// - /// This method must be called from a writing dispatch queue, outside of any - /// transaction. You'll get a fatal error otherwise. - /// - /// The *block* argument is guaranteed to see the database in the last - /// committed state at the moment this method is called. Eventual concurrent - /// database updates are *not visible* inside the block. - /// - /// This method returns as soon as the isolation guarantees described above - /// are established. To access the fetched results, you call the wait() - /// method of the returned future, on any dispatch queue. - /// - /// In the example below, the number of players is fetched concurrently with - /// the player insertion. Yet the future is guaranteed to return zero: - /// - /// try writer.writeWithoutTransaction { db in - /// // Delete all players - /// try Player.deleteAll() - /// - /// // Count players concurrently - /// let future = writer.concurrentRead { db in - /// return try Player.fetchCount() - /// } - /// - /// // Insert a player - /// try Player(...).insert(db) - /// - /// // Guaranteed to be zero - /// let count = try future.wait() - /// } - func concurrentRead(_ block: @escaping (Database) throws -> T) -> DatabaseFuture -} - -extension DatabaseWriter { - - // MARK: - Transaction Observers - - /// Add a transaction observer, so that it gets notified of - /// database changes. - /// - /// To remove the observer, use `DatabaseReader.remove(transactionObserver:)`. - /// - /// - parameter transactionObserver: A transaction observer. - /// - parameter extent: The duration of the observation. The default is - /// the observer lifetime (observation lasts until observer - /// is deallocated). - public func add(transactionObserver: TransactionObserver, extent: Database.TransactionObservationExtent = .observerLifetime) { - writeWithoutTransaction { $0.add(transactionObserver: transactionObserver, extent: extent) } - } - - /// Default implementation for the DatabaseReader requirement. - /// :nodoc: - public func remove(transactionObserver: TransactionObserver) { - writeWithoutTransaction { $0.remove(transactionObserver: transactionObserver) } - } - - // MARK: - Erasing the content of the database - - /// Erases the content of the database. - /// - /// - precondition: database is not accessed concurrently during the - /// execution of this method. - public func erase() throws { - #if SQLITE_HAS_CODEC - // SQLCipher does not support the backup API: https://discuss.zetetic.net/t/using-the-sqlite-online-backup-api/2631 - // So we'll drop all database objects one after the other. - try writeWithoutTransaction { db in - // Prevent foreign keys from messing with drop table statements - let foreignKeysEnabled = try Bool.fetchOne(db, sql: "PRAGMA foreign_keys")! - if foreignKeysEnabled { - try db.execute(sql: "PRAGMA foreign_keys = OFF") - } - - // Remove all database objects, one after the other - do { - try db.inTransaction { - while let row = try Row.fetchOne(db, sql: "SELECT type, name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%'") { - let type: String = row["type"] - let name: String = row["name"] - try db.execute(sql: "DROP \(type) \(name.quotedDatabaseIdentifier)") - } - return .commit - } - - // Restore foreign keys if needed - if foreignKeysEnabled { - try db.execute(sql: "PRAGMA foreign_keys = ON") - } - } catch { - // Restore foreign keys if needed - if foreignKeysEnabled { - try? db.execute(sql: "PRAGMA foreign_keys = ON") - } - throw error - } - } - #else - try DatabaseQueue().backup(to: self) - #endif - } - - // MARK: - Claiming Disk Space - - /// Rebuilds the database file, repacking it into a minimal amount of - /// disk space. - /// - /// See https://www.sqlite.org/lang_vacuum.html for more information. - public func vacuum() throws { - try writeWithoutTransaction { try $0.execute(sql: "VACUUM") } - } - - // MARK: - Value Observation - - /// Default implementation for the DatabaseReader requirement. - /// :nodoc: - public func add( - observation: ValueObservation, - onError: ((Error) -> Void)?, - onChange: @escaping (Reducer.Value) -> Void) - throws -> TransactionObserver - { - let calledOnMainQueue = DispatchQueue.isMain - var startValue: Reducer.Value? = nil - defer { - if let startValue = startValue { - onChange(startValue) - } - } - - // Use unsafeReentrantWrite so that observation can start from any - // dispatch queue. - return try unsafeReentrantWrite { db in - // Create the reducer - var reducer = try observation.makeReducer(db) - - // Take care of initial value. Make sure it is dispatched before - // any future transaction can trigger a change. - switch observation.scheduling { - case .mainQueue: - if let value = try reducer.initialValue(db, requiresWriteAccess: observation.requiresWriteAccess) { - if calledOnMainQueue { - startValue = value - } else { - DispatchQueue.main.async { onChange(value) } - } - } - case let .async(onQueue: queue, startImmediately: startImmediately): - if startImmediately { - if let value = try reducer.initialValue(db, requiresWriteAccess: observation.requiresWriteAccess) { - queue.async { onChange(value) } - } - } - case let .unsafe(startImmediately: startImmediately): - if startImmediately { - startValue = try reducer.initialValue(db, requiresWriteAccess: observation.requiresWriteAccess) - } - } - - // Start observing the database - let valueObserver = try ValueObserver( - region: observation.observedRegion(db), - reducer: reducer, - configuration: db.configuration, - fetch: observation.fetchAfterChange(in: self), - notificationQueue: observation.notificationQueue, - onError: onError, - onChange: onChange) - db.add(transactionObserver: valueObserver, extent: .observerLifetime) - - return valueObserver - } - } -} - -extension ValueReducer { - /// Helper method for DatabaseWriter.add(observation:onError:onChange:) - fileprivate mutating func initialValue(_ db: Database, requiresWriteAccess: Bool) throws -> Value? { - if requiresWriteAccess { - var fetchedValue: Fetched! - try db.inSavepoint { - fetchedValue = try fetch(db) - return .commit - } - return value(fetchedValue) - } else { - return try value(db.readOnly { try fetch(db) }) - } - } -} - -extension ValueObservation where Reducer: ValueReducer { - /// Helper method for DatabaseWriter.add(observation:onError:onChange:) - fileprivate func fetchAfterChange(in writer: DatabaseWriter) -> (Database, Reducer) -> DatabaseFuture { - // The technique to return a future value after database has changed - // depends on the requiresWriteAccess flag: - if requiresWriteAccess { - // Synchronous fetch - return { (db, reducer) in - DatabaseFuture(Result { - var fetchedValue: Reducer.Fetched! - try db.inTransaction { - fetchedValue = try reducer.fetch(db) - return .commit - } - return fetchedValue - }) - } - } else { - // Concurrent fetch - return { [unowned writer] (_, reducer) in - writer.concurrentRead(reducer.fetch) - } - } - } -} - -/// A future database value, returned by the DatabaseWriter.concurrentRead(_:) -/// method. -/// -/// let futureCount: Future = try writer.writeWithoutTransaction { db in -/// try Player(...).insert() -/// -/// // Count players concurrently -/// return writer.concurrentRead { db in -/// return try Player.fetchCount() -/// } -/// } -/// -/// let count: Int = try futureCount.wait() -public class DatabaseFuture { - private var consumed = false - private let _wait: () throws -> Value - - init(_ wait: @escaping () throws -> Value) { - _wait = wait - } - - init(_ result: Result) { - _wait = result.get - } - - /// Blocks the current thread until the value is available, and returns it. - /// - /// It is a programmer error to call this method several times. - /// - /// - throws: Any error that prevented the value from becoming available. - public func wait() throws -> Value { - // Not thread-safe and quick and dirty. - // Goal is that users learn not to call this method twice. - GRDBPrecondition(consumed == false, "DatabaseFuture.wait() must be called only once") - consumed = true - return try _wait() - } -} - -/// A type-erased DatabaseWriter -/// -/// Instances of AnyDatabaseWriter forward their methods to an arbitrary -/// underlying database writer. -public final class AnyDatabaseWriter : DatabaseWriter { - private let base: DatabaseWriter - - /// Creates a database writer that wraps a base database writer. - public init(_ base: DatabaseWriter) { - self.base = base - } - - // MARK: - Reading from Database - - /// :nodoc: - public func read(_ block: (Database) throws -> T) throws -> T { - return try base.read(block) - } - - /// :nodoc: - public func unsafeRead(_ block: (Database) throws -> T) throws -> T { - return try base.unsafeRead(block) - } - - /// :nodoc: - public func unsafeReentrantRead(_ block: (Database) throws -> T) throws -> T { - return try base.unsafeReentrantRead(block) - } - - /// :nodoc: - public func concurrentRead(_ block: @escaping (Database) throws -> T) -> DatabaseFuture { - return base.concurrentRead(block) - } - - // MARK: - Writing in Database - - /// :nodoc: - public func write(_ block: (Database) throws -> T) throws -> T { - return try base.write(block) - } - - /// :nodoc: - public func writeWithoutTransaction(_ block: (Database) throws -> T) rethrows -> T { - return try base.writeWithoutTransaction(block) - } - - /// :nodoc: - public func unsafeReentrantWrite(_ block: (Database) throws -> T) rethrows -> T { - return try base.unsafeReentrantWrite(block) - } - - // MARK: - Functions - - /// :nodoc: - public func add(function: DatabaseFunction) { - base.add(function: function) - } - - /// :nodoc: - public func remove(function: DatabaseFunction) { - base.remove(function: function) - } - - // MARK: - Collations - - /// :nodoc: - public func add(collation: DatabaseCollation) { - base.add(collation: collation) - } - - /// :nodoc: - public func remove(collation: DatabaseCollation) { - base.remove(collation: collation) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/FetchRequest.swift b/Example/Pods/GRDB.swift/GRDB/Core/FetchRequest.swift deleted file mode 100755 index 70b3f21..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/FetchRequest.swift +++ /dev/null @@ -1,159 +0,0 @@ -// MARK: - FetchRequest - -/// The protocol for all requests that run from a single select statement, and -/// tell how fetched rows should be interpreted. -/// -/// struct Player: FetchableRecord { ... } -/// let request: ... // Some FetchRequest that fetches Player -/// try request.fetchCursor(db) // Cursor of Player -/// try request.fetchAll(db) // [Player] -/// try request.fetchOne(db) // Player? -public protocol FetchRequest: DatabaseRegionConvertible { - /// The type that tells how fetched database rows should be interpreted. - associatedtype RowDecoder - - /// Returns a tuple that contains a prepared statement that is ready to be - /// executed, and an eventual row adapter. - /// - /// - parameter db: A database connection. - /// - parameter singleResult: A hint that the query should return a single - /// result. Implementations can optionally use - /// this to optimize the prepared statement. - /// - returns: A prepared statement and an eventual row adapter. - func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?) - - /// Returns the number of rows fetched by the request. - /// - /// The default implementation builds a naive SQL query based on the - /// statement returned by the `prepare` method: - /// `SELECT COUNT(*) FROM (...)`. - /// - /// Adopting types can refine this method in order to use more - /// efficient SQL. - /// - /// - parameter db: A database connection. - func fetchCount(_ db: Database) throws -> Int -} - -extension FetchRequest { - - /// Returns an adapted request. - public func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AdaptedFetchRequest { - return AdaptedFetchRequest(self, adapter) - } - - /// Returns the number of rows fetched by the request. - /// - /// This default implementation builds a naive SQL query based on the - /// statement returned by the `prepare` method: `SELECT COUNT(*) FROM (...)`. - /// - /// - parameter db: A database connection. - public func fetchCount(_ db: Database) throws -> Int { - let (statement, _) = try prepare(db, forSingleResult: false) - let sql = "SELECT COUNT(*) FROM (\(statement.sql))" - return try Int.fetchOne(db, sql: sql, arguments: statement.arguments)! - } - - /// Returns the database region that the request looks into. - /// - /// This default implementation returns a region built from the statement - /// returned by the `prepare` method. - /// - /// - parameter db: A database connection. - public func databaseRegion(_ db: Database) throws -> DatabaseRegion { - let (statement, _) = try prepare(db, forSingleResult: false) - return statement.databaseRegion - } -} - -// MARK: - AdaptedFetchRequest - -/// An adapted request. -public struct AdaptedFetchRequest : FetchRequest { - public typealias RowDecoder = Base.RowDecoder - - private let base: Base - private let adapter: (Database) throws -> RowAdapter - - /// Creates an adapted request from a base request and a closure that builds - /// a row adapter from a database connection. - init(_ base: Base, _ adapter: @escaping (Database) throws -> RowAdapter) { - self.base = base - self.adapter = adapter - } - - /// :nodoc: - public func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?) { - let (statement, baseAdapter) = try base.prepare(db, forSingleResult: singleResult) - if let baseAdapter = baseAdapter { - return try (statement, ChainedAdapter(first: baseAdapter, second: adapter(db))) - } else { - return try (statement, adapter(db)) - } - } - - /// :nodoc: - public func fetchCount(_ db: Database) throws -> Int { - return try base.fetchCount(db) - } - - /// :nodoc: - public func databaseRegion(_ db: Database) throws -> DatabaseRegion { - return try base.databaseRegion(db) - } -} - -// MARK: - AnyFetchRequest - -/// A type-erased FetchRequest. -/// -/// An AnyFetchRequest forwards its operations to an underlying request, -/// hiding its specifics. -public struct AnyFetchRequest : FetchRequest { - public typealias RowDecoder = T - - private let _prepare: (Database, _ singleResult: Bool) throws -> (SelectStatement, RowAdapter?) - private let _fetchCount: (Database) throws -> Int - private let _databaseRegion: (Database) throws -> DatabaseRegion - - /// Creates a request that wraps and forwards operations to `request`. - public init(_ request: Request) { - _prepare = request.prepare - _fetchCount = request.fetchCount - _databaseRegion = request.databaseRegion - } - - /// Creates a request whose `prepare()` method wraps and forwards - /// operations the argument closure. - public init(_ prepare: @escaping (Database, _ singleResult: Bool) throws -> (SelectStatement, RowAdapter?)) { - _prepare = { db, singleResult in - try prepare(db, singleResult) - } - - _fetchCount = { db in - let (statement, _) = try prepare(db, false) - let sql = "SELECT COUNT(*) FROM (\(statement.sql))" - return try Int.fetchOne(db, sql: sql, arguments: statement.arguments)! - } - - _databaseRegion = { db in - let (statement, _) = try prepare(db, false) - return statement.databaseRegion - } - } - - /// :nodoc: - public func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?) { - return try _prepare(db, singleResult) - } - - /// :nodoc: - public func fetchCount(_ db: Database) throws -> Int { - return try _fetchCount(db) - } - - /// :nodoc: - public func databaseRegion(_ db: Database) throws -> DatabaseRegion { - return try _databaseRegion(db) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Row.swift b/Example/Pods/GRDB.swift/GRDB/Core/Row.swift deleted file mode 100755 index c204fde..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Row.swift +++ /dev/null @@ -1,1806 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// A database row. -public final class Row : Equatable, Hashable, RandomAccessCollection, ExpressibleByDictionaryLiteral, CustomStringConvertible, CustomDebugStringConvertible { - // It is not a violation of the Demeter law when another type uses this - // property, which is exposed for optimizations. - let impl: RowImpl - - /// Unless we are producing a row array, we use a single row when iterating - /// a statement: - /// - /// let rows = try Row.fetchCursor(db, sql: "SELECT ...") - /// let players = try Player.fetchAll(db, sql: "SELECT ...") - /// - /// This row keeps an unmanaged reference to the statement, and a handle to - /// the sqlite statement, so that we avoid many retain/release invocations. - /// - /// The statementRef is released in deinit. - let statementRef: Unmanaged? - @usableFromInline let sqliteStatement: SQLiteStatement? - var statement: SelectStatement? { - return statementRef?.takeUnretainedValue() - } - - /// The number of columns in the row. - public let count: Int - - /// A view on the prefetched associated rows. - /// - /// For example: - /// - /// let request = Author.including(all: Author.books) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(row) - /// // Prints [id:1 name:"Herman Melville"] - /// - /// let bookRows = row.prefetchedRows["books"] - /// print(bookRows[0]) - /// // Prints [id:42 title:"Moby-Dick"] - public internal(set) var prefetchedRows: PrefetchedRowsView = PrefetchedRowsView() - - // MARK: - Building rows - - /// Creates an empty row. - public convenience init() { - self.init(impl: EmptyRowImpl()) - } - - /// Creates a row from a dictionary of values. - public convenience init(_ dictionary: [String: DatabaseValueConvertible?]) { - self.init(impl: ArrayRowImpl(columns: dictionary.map { ($0, $1?.databaseValue ?? .null) })) - } - - /// Creates a row from [AnyHashable: Any]. - /// - /// The result is nil unless all dictionary keys are strings, and values - /// adopt DatabaseValueConvertible. - public convenience init?(_ dictionary: [AnyHashable: Any]) { - var initDictionary = [String: DatabaseValueConvertible?]() - for (key, value) in dictionary { - guard let columnName = key as? String else { - return nil - } - guard let dbValue = DatabaseValue(value: value) else { - return nil - } - initDictionary[columnName] = dbValue - } - self.init(initDictionary) - } - - /// Returns an immutable copy of the row. - /// - /// For performance reasons, rows fetched from a cursor are reused during - /// the iteration of a query: make sure to make a copy of it whenever you - /// want to keep a specific one: `row.copy()`. - public func copy() -> Row { - return impl.copiedRow(self) - } - - // MARK: - Not Public - - /// Returns true if and only if the row was fetched from a database. - var isFetched: Bool { - return impl.isFetched - } - - deinit { - statementRef?.release() - } - - /// Creates a row that maps an SQLite statement. Further calls to - /// sqlite3_step() modify the row. - /// - /// The row is implemented on top of StatementRowImpl, which grants *direct* - /// access to the SQLite statement. Iteration of the statement does modify - /// the row. - init(statement: SelectStatement) { - let statementRef = Unmanaged.passRetained(statement) // released in deinit - self.statementRef = statementRef - self.sqliteStatement = statement.sqliteStatement - self.impl = StatementRowImpl(sqliteStatement: statement.sqliteStatement, statementRef: statementRef) - self.count = Int(sqlite3_column_count(sqliteStatement)) - } - - /// Creates a row that maps an SQLite statement. Further calls to - /// sqlite3_step() modify the row. - init(sqliteStatement: SQLiteStatement) { - self.sqliteStatement = sqliteStatement - self.statementRef = nil - self.impl = SQLiteStatementRowImpl(sqliteStatement: sqliteStatement) - self.count = Int(sqlite3_column_count(sqliteStatement)) - } - - /// Creates a row that contain a copy of the current state of the - /// SQLite statement. Further calls to sqlite3_step() do not modify the row. - /// - /// The row is implemented on top of StatementCopyRowImpl, which *copies* - /// the values from the SQLite statement so that further iteration of the - /// statement does not modify the row. - convenience init(copiedFromSQLiteStatement sqliteStatement: SQLiteStatement, statementRef: Unmanaged) { - self.init(impl: StatementCopyRowImpl(sqliteStatement: sqliteStatement, columnNames: statementRef.takeUnretainedValue().columnNames)) - } - - init(impl: RowImpl) { - self.impl = impl - self.count = impl.count - self.statementRef = nil - self.sqliteStatement = nil - } -} - -extension Row { - - // MARK: - Columns - - /// The names of columns in the row. - /// - /// Columns appear in the same order as they occur as the `.0` member - /// of column-value pairs in `self`. - public var columnNames: LazyMapCollection { - return lazy.map { $0.0 } - } - - /// Returns true if and only if the row has that column. - /// - /// This method is case-insensitive. - public func hasColumn(_ columnName: String) -> Bool { - return index(ofColumn: columnName) != nil - } - - @usableFromInline - func index(ofColumn name: String) -> Int? { - return impl.index(ofColumn: name) - } -} - -extension Row { - - // MARK: - Extracting Values - - /// Fatal errors if index is out of bounds - @inlinable - func _checkIndex(_ index: Int, file: StaticString = #file, line: UInt = #line) { - GRDBPrecondition(index >= 0 && index < count, "row index out of range", file: file, line: line) - } - - /// Returns true if and only if one column contains a non-null value, or if - /// the row was fetched with a row adapter that defines a scoped row that - /// contains a non-null value. - /// - /// For example: - /// - /// let row = try Row.fetchOne(db, sql: "SELECT 'foo', 1")! - /// row.containsNonNullValue // true - /// - /// let row = try Row.fetchOne(db, sql: "SELECT NULL, NULL")! - /// row.containsNonNullValue // false - public var containsNonNullValue: Bool { - for i in (0.. Bool { - _checkIndex(index) - return impl.hasNull(atUncheckedIndex: index) - } - - /// Returns Int64, Double, String, Data or nil, depending on the value - /// stored at the given index. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - public subscript(_ index: Int) -> DatabaseValueConvertible? { - _checkIndex(index) - return impl.databaseValue(atUncheckedIndex: index).storage.value - } - - /// Returns the value at given index, converted to the requested type. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// If the SQLite value is NULL, the result is nil. Otherwise the SQLite - /// value is converted to the requested type `Value`. Should this conversion - /// fail, a fatal error is raised. - @inlinable - public subscript(_ index: Int) -> Value? { - _checkIndex(index) - return Value.decodeIfPresent(from: self, atUncheckedIndex: index) - } - - /// Returns the value at given index, converted to the requested type. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// If the SQLite value is NULL, the result is nil. Otherwise the SQLite - /// value is converted to the requested type `Value`. Should this conversion - /// fail, a fatal error is raised. - /// - /// This method exists as an optimization opportunity for types that adopt - /// StatementColumnConvertible. It *may* trigger SQLite built-in conversions - /// (see https://www.sqlite.org/datatype3.html). - @inlinable - public subscript(_ index: Int) -> Value? { - _checkIndex(index) - return Value.fastDecodeIfPresent(from: self, atUncheckedIndex: index) - } - - /// Returns the value at given index, converted to the requested type. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// This method crashes if the fetched SQLite value is NULL, or if the - /// SQLite value can not be converted to `Value`. - @inlinable - public subscript(_ index: Int) -> Value { - _checkIndex(index) - return Value.decode(from: self, atUncheckedIndex: index) - } - - /// Returns the value at given index, converted to the requested type. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// This method crashes if the fetched SQLite value is NULL, or if the - /// SQLite value can not be converted to `Value`. - /// - /// This method exists as an optimization opportunity for types that adopt - /// StatementColumnConvertible. It *may* trigger SQLite built-in conversions - /// (see https://www.sqlite.org/datatype3.html). - @inlinable - public subscript(_ index: Int) -> Value { - _checkIndex(index) - return Value.fastDecode(from: self, atUncheckedIndex: index) - } - - /// Returns Int64, Double, String, Data or nil, depending on the value - /// stored at the given column. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// The result is nil if the row does not contain the column. - public subscript(_ columnName: String) -> DatabaseValueConvertible? { - // IMPLEMENTATION NOTE - // This method has a single know use case: checking if the value is nil, - // as in: - // - // if row["foo"] != nil { ... } - // - // Without this method, the code above would not compile. - guard let index = index(ofColumn: columnName) else { - return nil - } - return impl.databaseValue(atUncheckedIndex: index).storage.value - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the column is missing or if the SQLite value is NULL, the result is - /// nil. Otherwise the SQLite value is converted to the requested type - /// `Value`. Should this conversion fail, a fatal error is raised. - @inlinable - public subscript(_ columnName: String) -> Value? { - guard let index = index(ofColumn: columnName) else { - return nil - } - return Value.decodeIfPresent(from: self, atUncheckedIndex: index) - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the column is missing or if the SQLite value is NULL, the result is - /// nil. Otherwise the SQLite value is converted to the requested type - /// `Value`. Should this conversion fail, a fatal error is raised. - /// - /// This method exists as an optimization opportunity for types that adopt - /// StatementColumnConvertible. It *may* trigger SQLite built-in conversions - /// (see https://www.sqlite.org/datatype3.html). - @inlinable - public subscript(_ columnName: String) -> Value? { - guard let index = index(ofColumn: columnName) else { - return nil - } - return Value.fastDecodeIfPresent(from: self, atUncheckedIndex: index) - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the row does not contain the column, a fatal error is raised. - /// - /// This method crashes if the fetched SQLite value is NULL, or if the - /// SQLite value can not be converted to `Value`. - @inlinable - public subscript(_ columnName: String) -> Value { - guard let index = index(ofColumn: columnName) else { - // No such column - fatalConversionError(to: Value.self, from: nil, in: self, atColumn: columnName) - } - return Value.decode(from: self, atUncheckedIndex: index) - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the row does not contain the column, a fatal error is raised. - /// - /// This method crashes if the fetched SQLite value is NULL, or if the - /// SQLite value can not be converted to `Value`. - /// - /// This method exists as an optimization opportunity for types that adopt - /// StatementColumnConvertible. It *may* trigger SQLite built-in conversions - /// (see https://www.sqlite.org/datatype3.html). - @inlinable - public subscript(_ columnName: String) -> Value { - guard let index = index(ofColumn: columnName) else { - // No such column - fatalConversionError(to: Value.self, from: nil, in: self, atColumn: columnName) - } - return Value.fastDecode(from: self, atUncheckedIndex: index) - } - - /// Returns Int64, Double, String, NSData or nil, depending on the value - /// stored at the given column. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// The result is nil if the row does not contain the column. - @inlinable - public subscript(_ column: Column) -> DatabaseValueConvertible? { - return self[column.name] - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the column is missing or if the SQLite value is NULL, the result is - /// nil. Otherwise the SQLite value is converted to the requested type - /// `Value`. Should this conversion fail, a fatal error is raised. - @inlinable - public subscript(_ column: Column) -> Value? { - return self[column.name] - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the column is missing or if the SQLite value is NULL, the result is - /// nil. Otherwise the SQLite value is converted to the requested type - /// `Value`. Should this conversion fail, a fatal error is raised. - /// - /// This method exists as an optimization opportunity for types that adopt - /// StatementColumnConvertible. It *may* trigger SQLite built-in conversions - /// (see https://www.sqlite.org/datatype3.html). - @inlinable - public subscript(_ column: Column) -> Value? { - return self[column.name] - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the row does not contain the column, a fatal error is raised. - /// - /// This method crashes if the fetched SQLite value is NULL, or if the - /// SQLite value can not be converted to `Value`. - @inlinable - public subscript(_ column: Column) -> Value { - return self[column.name] - } - - /// Returns the value at given column, converted to the requested type. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the row does not contain the column, a fatal error is raised. - /// - /// This method crashes if the fetched SQLite value is NULL, or if the - /// SQLite value can not be converted to `Value`. - /// - /// This method exists as an optimization opportunity for types that adopt - /// StatementColumnConvertible. It *may* trigger SQLite built-in conversions - /// (see https://www.sqlite.org/datatype3.html). - @inlinable - public subscript(_ column: Column) -> Value { - return self[column.name] - } - - /// Returns the optional Data at given index. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// If the SQLite value is NULL, the result is nil. If the SQLite value can - /// not be converted to Data, a fatal error is raised. - /// - /// The returned data does not owns its bytes: it must not be used longer - /// than the row's lifetime. - public func dataNoCopy(atIndex index: Int) -> Data? { - _checkIndex(index) - return impl.dataNoCopy(atUncheckedIndex: index) - } - - /// Returns the optional Data at given column. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the column is missing or if the SQLite value is NULL, the result is - /// nil. If the SQLite value can not be converted to Data, a fatal error - /// is raised. - /// - /// The returned data does not owns its bytes: it must not be used longer - /// than the row's lifetime. - public func dataNoCopy(named columnName: String) -> Data? { - guard let index = index(ofColumn: columnName) else { - return nil - } - return impl.dataNoCopy(atUncheckedIndex: index) - } - - /// Returns the optional `NSData` at given column. - /// - /// Column name lookup is case-insensitive, and when several columns have - /// the same name, the leftmost column is considered. - /// - /// If the column is missing or if the SQLite value is NULL, the result is - /// nil. If the SQLite value can not be converted to NSData, a fatal error - /// is raised. - /// - /// The returned data does not owns its bytes: it must not be used longer - /// than the row's lifetime. - public func dataNoCopy(_ column: Column) -> Data? { - return dataNoCopy(named: column.name) - } -} - -extension Row { - - // MARK: - Extracting DatabaseValue - - /// The database values in the row. - /// - /// Values appear in the same order as they occur as the `.1` member - /// of column-value pairs in `self`. - public var databaseValues: LazyMapCollection { - return lazy.map { $0.1 } - } -} - -extension Row { - - // MARK: - Extracting Records - - /// Returns the record associated with the given scope. - /// - /// For example: - /// - /// let request = Book.including(required: Book.author) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(Book(row: row).title) - /// // Prints "Moby-Dick" - /// - /// let author: Author = row["author"] - /// print(author.name) - /// // Prints "Herman Melville" - /// - /// Associated records stored in nested associations are available, too: - /// - /// let request = Book.including(required: Book.author.including(required: Author.country)) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(Book(row: row).title) - /// // Prints "Moby-Dick" - /// - /// let country: Country = row["country"] - /// print(country.name) - /// // Prints "United States" - /// - /// A fatal error is raised if the scope is not available, or contains only - /// null values. - /// - /// See https://github.com/groue/GRDB.swift/blob/master/README.md#joined-queries-support - /// for more information. - public subscript(_ scope: String) -> Record { - guard let scopedRow = scopesTree[scope] else { - // Programmer error - let names = scopesTree.names - if names.isEmpty { - fatalError("missing scope `\(scope)` (row: \(self))") - } else { - fatalError("missing scope `\(scope)` (row: \(self), available scopes: \(names.sorted()))") - } - } - guard scopedRow.containsNonNullValue else { - // Programmer error - fatalError("scope `\(scope)` only contains null values (row: \(self))") - } - return Record(row: scopedRow) - } - - /// Returns the eventual record associated with the given scope. - /// - /// For example: - /// - /// let request = Book.including(optional: Book.author) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(Book(row: row).title) - /// // Prints "Moby-Dick" - /// - /// let author: Author? = row["author"] - /// print(author.name) - /// // Prints "Herman Melville" - /// - /// Associated records stored in nested associations are available, too: - /// - /// let request = Book.including(optional: Book.author.including(optional: Author.country)) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(Book(row: row).title) - /// // Prints "Moby-Dick" - /// - /// let country: Country? = row["country"] - /// print(country.name) - /// // Prints "United States" - /// - /// Nil is returned if the scope is not available, or contains only - /// null values. - /// - /// See https://github.com/groue/GRDB.swift/blob/master/README.md#joined-queries-support - /// for more information. - public subscript(_ scope: String) -> Record? { - guard let scopedRow = scopesTree[scope], scopedRow.containsNonNullValue else { - return nil - } - return Record(row: scopedRow) - } - - /// Returns the records encoded in the given prefetched rows. - /// - /// For example: - /// - /// let request = Author.including(all: Author.books) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(Author(row: row).name) - /// // Prints "Herman Melville" - /// - /// let books: [Book] = row["books"] - /// print(books[0].title) - /// // Prints "Moby-Dick" - public subscript(_ key: String) - -> Collection - where - Collection: RangeReplaceableCollection, - Collection.Element: FetchableRecord - { - guard let rows = prefetchedRows[key] else { - // Programmer error - let keys = prefetchedRows.keys - if keys.isEmpty { - fatalError("missing key for prefetched rows `\(key)` (row: \(self))") - } else { - fatalError("missing key for prefetched rows `\(key)` (row: \(self), available keys: \(keys.sorted()))") - } - } - var collection = Collection() - collection.reserveCapacity(rows.count) - for row in rows { - collection.append(Collection.Element(row: row)) - } - return collection - } - - /// Returns the set of records encoded in the given prefetched rows. - /// - /// For example: - /// - /// let request = Author.including(all: Author.books) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(Author(row: row).name) - /// // Prints "Herman Melville" - /// - /// let books: Set = row["books"] - /// print(books.first!.title) - /// // Prints "Moby-Dick" - public subscript(_ key: String) -> Set { - guard let rows = prefetchedRows[key] else { - // Programmer error - let keys = prefetchedRows.keys - if keys.isEmpty { - fatalError("missing key for prefetched rows `\(key)` (row: \(self))") - } else { - fatalError("missing key for prefetched rows `\(key)` (row: \(self), available keys: \(keys.sorted()))") - } - } - var set = Set(minimumCapacity: rows.count) - for row in rows { - set.insert(Record(row: row)) - } - return set - } -} - -extension Row { - - // MARK: - Scopes - - /// Returns a view on the scopes defined by row adapters. - /// - /// For example: - /// - /// // Define a tree of nested scopes - /// let adapter = ScopeAdapter([ - /// "foo": RangeRowAdapter(0..<1), - /// "bar": RangeRowAdapter(1..<2).addingScopes([ - /// "baz" : RangeRowAdapter(2..<3)])]) - /// - /// // Fetch - /// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" - /// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)! - /// - /// row.scopes.count // 2 - /// row.scopes.names // ["foo", "bar"] - /// - /// row.scopes["foo"] // [foo:1] - /// row.scopes["bar"] // [bar:2] - /// row.scopes["baz"] // nil - public var scopes: ScopesView { - return impl.scopes(prefetchedRows: prefetchedRows) - } - - /// Returns a view on the scopes tree defined by row adapters. - /// - /// For example: - /// - /// // Define a tree of nested scopes - /// let adapter = ScopeAdapter([ - /// "foo": RangeRowAdapter(0..<1), - /// "bar": RangeRowAdapter(1..<2).addingScopes([ - /// "baz" : RangeRowAdapter(2..<3)])]) - /// - /// // Fetch - /// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" - /// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)! - /// - /// row.scopesTree.names // ["foo", "bar", "baz"] - /// - /// row.scopesTree["foo"] // [foo:1] - /// row.scopesTree["bar"] // [bar:2] - /// row.scopesTree["baz"] // [baz:3] - public var scopesTree: ScopesTreeView { - return ScopesTreeView(scopes: scopes) - } - - /// Returns a copy of the row, without any scopes. - /// - /// This property can turn out useful when you want to test the content of - /// adapted rows, such as rows fetched from joined requests. - /// - /// let row = ... - /// // Failure because row equality tests for row scopes: - /// XCTAssertEqual(row, ["id": 1, "name": "foo"]) - /// // Success: - /// XCTAssertEqual(row.unscoped, ["id": 1, "name": "foo"]) - public var unscoped: Row { - var row = impl.unscopedRow(self) - - // Remove prefetchedRows - if row.prefetchedRows.isEmpty == false { - // Make sure we build another Row instance - row = Row(impl: row.copy().impl) - assert(row !== self) - assert(row.prefetchedRows.isEmpty) - } - return row - } - - /// Return the raw row fetched from the database. - /// - /// This property can turn out useful when you debug the consumption of - /// adapted rows, such as rows fetched from joined requests. - public var unadapted: Row { - return impl.unadaptedRow(self) - } -} - -// MARK: - RowCursor - -/// A cursor of database rows. For example: -/// -/// try dbQueue.read { db in -/// let rows: RowCursor = try Row.fetchCursor(db, sql: "SELECT * FROM player") -/// } -public final class RowCursor : Cursor { - public let statement: SelectStatement - @usableFromInline let _sqliteStatement: SQLiteStatement - @usableFromInline let _row: Row // Reused for performance - @usableFromInline var _done = false - - init(statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { - self.statement = statement - self._row = try Row(statement: statement).adapted(with: adapter, layout: statement) - self._sqliteStatement = statement.sqliteStatement - statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? statement.reset() - } - - /// :nodoc: - @inlinable - public func next() throws -> Row? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return _row - case let code: - try statement.didFail(withResultCode: code) - } - } -} - -extension Row { - - // MARK: - Fetching From SelectStatement - - /// Returns a cursor over rows fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT ...") - /// let rows = try Row.fetchCursor(statement) // RowCursor - /// while let row = try rows.next() { // Row - /// let id: Int64 = row[0] - /// let name: String = row[1] - /// } - /// - /// Fetched rows are reused during the cursor iteration: don't turn a row - /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since - /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` - /// instead. - /// - /// For the same reason, make sure you make a copy whenever you extract a - /// row for later use: `row.copy()`. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> RowCursor { - return try RowCursor(statement: statement, arguments: arguments, adapter: adapter) - } - - /// Returns an array of rows fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT ...") - /// let rows = try Row.fetchAll(statement) - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Row] { - // The cursor reuses a single mutable row. Return immutable copies. - return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter).map { $0.copy() }) - } - - /// Returns a single row fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT ...") - /// let row = try Row.fetchOne(statement) - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional row. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Row? { - let cursor = try fetchCursor(statement, arguments: arguments, adapter: adapter) - // Keep cursor alive until we can copy the fetched row - return try withExtendedLifetime(cursor) { - try cursor.next().map { $0.copy() } - } - } -} - -extension Row { - - // MARK: - Fetching From SQL - - /// Returns a cursor over rows fetched from an SQL query. - /// - /// let rows = try Row.fetchCursor(db, sql: "SELECT id, name FROM player") // RowCursor - /// while let row = try rows.next() { // Row - /// let id: Int64 = row[0] - /// let name: String = row[1] - /// } - /// - /// Fetched rows are reused during the cursor iteration: don't turn a row - /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since - /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` - /// instead. - /// - /// For the same reason, make sure you make a copy whenever you extract a - /// row for later use: `row.copy()`. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> RowCursor { - return try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns an array of rows fetched from an SQL query. - /// - /// let rows = try Row.fetchAll(db, sql: "SELECT id, name FROM player") // [Row] - /// for row in rows { - /// let id: Int64 = row[0] - /// let name: String = row[1] - /// } - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> [Row] { - return try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns a single row fetched from an SQL query. - /// - /// let row = try Row.fetchOne(db, sql: "SELECT id, name FROM player") // Row? - /// if let row = row { - /// let id: Int64 = row[0] - /// let name: String = row[1] - /// } - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional row. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> Row? { - return try fetchOne(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } -} - -extension Row { - - // MARK: - Fetching From FetchRequest - - /// Returns a cursor over rows fetched from a fetch request. - /// - /// let request = Player.all() - /// let rows = try Row.fetchCursor(db, request) // RowCursor - /// while let row = try rows.next() { // Row - /// let id: Int64 = row["id"] - /// let name: String = row["name"] - /// } - /// - /// Fetched rows are reused during the cursor iteration: don't turn a row - /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since - /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` - /// instead. - /// - /// For the same reason, make sure you make a copy whenever you extract a - /// row for later use: `row.copy()`. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: A cursor over fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: R) throws -> RowCursor { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of rows fetched from a fetch request. - /// - /// let request = Player.all() - /// let rows = try Row.fetchAll(db, request) - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An array of rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: R) throws -> [Row] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchAll(statement, adapter: adapter) - } - - /// Returns a single row fetched from a fetch request. - /// - /// let request = Player.filter(key: 1) - /// let row = try Row.fetchOne(db, request) - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An optional row. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, _ request: R) throws -> Row? { - let (statement, adapter) = try request.prepare(db, forSingleResult: true) - return try fetchOne(statement, adapter: adapter) - } -} - -extension FetchRequest where RowDecoder == Row { - - // MARK: Fetching Rows - - /// A cursor over fetched rows. - /// - /// let request: ... // Some FetchRequest that fetches Row - /// let rows = try request.fetchCursor(db) // RowCursor - /// while let row = try rows.next() { // Row - /// let id: Int64 = row[0] - /// let name: String = row[1] - /// } - /// - /// Fetched rows are reused during the cursor iteration: don't turn a row - /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since - /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` - /// instead. - /// - /// For the same reason, make sure you make a copy whenever you extract a - /// row for later use: `row.copy()`. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> RowCursor { - return try Row.fetchCursor(db, self) - } - - /// An array of fetched rows. - /// - /// let request: ... // Some FetchRequest that fetches Row - /// let rows = try request.fetchAll(db) - /// - /// - parameter db: A database connection. - /// - returns: An array of fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [Row] { - return try Row.fetchAll(db, self) - } - - /// The first fetched row. - /// - /// let request: ... // Some FetchRequest that fetches Row - /// let row = try request.fetchOne(db) - /// - /// - parameter db: A database connection. - /// - returns: An optional row. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchOne(_ db: Database) throws -> Row? { - return try Row.fetchOne(db, self) - } -} - -// ExpressibleByDictionaryLiteral -extension Row { - - /// Creates a row initialized with elements. Column order is preserved, and - /// duplicated columns names are allowed. - /// - /// let row: Row = ["foo": 1, "foo": "bar", "baz": nil] - /// print(row) - /// // Prints [foo:1 foo:"bar" baz:NULL] - public convenience init(dictionaryLiteral elements: (String, DatabaseValueConvertible?)...) { - self.init(impl: ArrayRowImpl(columns: elements.map { ($0, $1?.databaseValue ?? .null) })) - } -} - -// RandomAccessCollection -extension Row { - - // MARK: - Row as a Collection of (ColumnName, DatabaseValue) Pairs - - /// The index of the first (ColumnName, DatabaseValue) pair. - /// :nodoc: - public var startIndex: RowIndex { - return Index(0) - } - - /// The "past-the-end" index, successor of the index of the last - /// (ColumnName, DatabaseValue) pair. - /// :nodoc: - public var endIndex: RowIndex { - return Index(count) - } - - /// Accesses the (ColumnName, DatabaseValue) pair at given index. - public subscript(position: RowIndex) -> (String, DatabaseValue) { - let index = position.index - _checkIndex(index) - return ( - impl.columnName(atUncheckedIndex: index), - impl.databaseValue(atUncheckedIndex: index)) - } -} - -// Equatable -extension Row { - - /// Returns true if and only if both rows have the same columns and values, - /// in the same order. Columns are compared in a case-sensitive way. - /// :nodoc: - public static func == (lhs: Row, rhs: Row) -> Bool { - if lhs === rhs { - return true - } - - guard lhs.count == rhs.count else { - return false - } - - for ((lcol, lval), (rcol, rval)) in zip(lhs, rhs) { - guard lcol == rcol else { - return false - } - guard lval == rval else { - return false - } - } - - let lscopeNames = lhs.scopes.names - let rscopeNames = rhs.scopes.names - guard lscopeNames == rscopeNames else { - return false - } - - for name in lscopeNames { - let lscope = lhs.scopes[name] - let rscope = rhs.scopes[name] - guard lscope == rscope else { - return false - } - } - - guard lhs.prefetchedRows == rhs.prefetchedRows else { - return false - } - - return true - } -} - -// Hashable -extension Row { - /// :nodoc: - public func hash(into hasher: inout Hasher) { - hasher.combine(count) - for (column, dbValue) in self { - hasher.combine(column) - hasher.combine(dbValue) - } - } -} - -// CustomStringConvertible & CustomDebugStringConvertible -extension Row { - /// :nodoc: - public var description: String { - return "[" - + map { (column, dbValue) in "\(column):\(dbValue)" }.joined(separator: " ") - + "]" - } - - /// :nodoc: - public var debugDescription: String { - return debugDescription(level: 0) - } - - private func debugDescription(level: Int) -> String { - if level == 0 && self == self.unadapted && prefetchedRows.prefetches.isEmpty { - return description - } - let prefix = repeatElement(" ", count: level + 1).joined(separator: "") - var str = "" - if level == 0 { - str = "▿ " + description - let unadapted = self.unadapted - if self != unadapted { - str += "\n" + prefix + "unadapted: " + unadapted.description - } - } else { - str = description - } - for (name, scopedRow) in scopes.sorted(by: { $0.name < $1.name }) { - str += "\n" + prefix + "- " + name + ": " + scopedRow.debugDescription(level: level + 1) - } - for key in prefetchedRows.keys.sorted() { - let rows = prefetchedRows[key]! - let prefetchedRowsDescription: String - switch rows.count { - case 0: - prefetchedRowsDescription = "0 row" - case 1: - prefetchedRowsDescription = "1 row" - case let count: - prefetchedRowsDescription = "\(count) rows" - } - str += "\n" + prefix + "+ " + key + ": \(prefetchedRowsDescription)" - } - - return str - } -} - - -// MARK: - RowIndex - -/// Indexes to (ColumnName, DatabaseValue) pairs in a database row. -public struct RowIndex : Comparable, Strideable { - let index: Int - init(_ index: Int) { self.index = index } -} - -// Comparable -extension RowIndex { - /// :nodoc: - public static func == (lhs: RowIndex, rhs: RowIndex) -> Bool { - return lhs.index == rhs.index - } - - /// :nodoc: - public static func < (lhs: RowIndex, rhs: RowIndex) -> Bool { - return lhs.index < rhs.index - } -} - -// Strideable: support for Row: RandomAccessCollection -extension RowIndex { - /// :nodoc: - public func distance(to other: RowIndex) -> Int { - return other.index - index - } - - /// :nodoc: - public func advanced(by n: Int) -> RowIndex { - return RowIndex(index + n) - } -} - -// MARK: - Row.ScopesView - -extension Row { - /// A view of the scopes defined by row adapters. It is a collection of - /// tuples made of a scope name and a scoped row, which behaves like a - /// dictionary. - /// - /// For example: - /// - /// // Define a tree of nested scopes - /// let adapter = ScopeAdapter([ - /// "foo": RangeRowAdapter(0..<1), - /// "bar": RangeRowAdapter(1..<2).addingScopes([ - /// "baz" : RangeRowAdapter(2..<3)])]) - /// - /// // Fetch - /// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" - /// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)! - /// - /// row.scopes.count // 2 - /// row.scopes.names // ["foo", "bar"] - /// - /// row.scopes["foo"] // [foo:1] - /// row.scopes["bar"] // [bar:2] - /// row.scopes["baz"] // nil - public struct ScopesView: Collection { - public typealias Index = Dictionary.Index - private let row: Row - private let scopes: [String: LayoutedRowAdapter] - private let prefetchedRows: Row.PrefetchedRowsView - - /// The scopes defined on this row. - public var names: Dictionary.Keys { - return scopes.keys - } - - init() { - self.init(row: Row(), scopes: [:], prefetchedRows: Row.PrefetchedRowsView()) - } - - init(row: Row, scopes: [String: LayoutedRowAdapter], prefetchedRows: Row.PrefetchedRowsView) { - self.row = row - self.scopes = scopes - self.prefetchedRows = prefetchedRows - } - - /// :nodoc: - public var startIndex: Index { - return scopes.startIndex - } - - /// :nodoc: - public var endIndex: Index { - return scopes.endIndex - } - - /// :nodoc: - public func index(after i: Index) -> Index { - return scopes.index(after: i) - } - - /// :nodoc: - public subscript(position: Index) -> (name: String, row: Row) { - let (name, adapter) = scopes[position] - let adaptedRow = Row(base: row, adapter: adapter) - if let prefetch = prefetchedRows.prefetches[name] { - // Let the adapted row access its own prefetched rows. - // Use case: - // - // let request = A.including(required: A.b.including(all: B.c)) - // let row = try Row.fetchOne(db, request)! - // row.prefetchedRows["cs"] // Some array - // row.scopes["b"]!.prefetchedRows["cs"] // The same array - adaptedRow.prefetchedRows = Row.PrefetchedRowsView(prefetches: prefetch.prefetches) - } - return (name: name, row: adaptedRow) - } - - /// Returns the row associated with the given scope, or nil if the - /// scope is not defined. - public subscript(_ name: String) -> Row? { - return scopes.index(forKey: name).map { self[$0].row } - } - } -} - -// MARK: - Row.ScopesTreeView - -extension Row { - - /// A view on the scopes tree defined by row adapters. - /// - /// For example: - /// - /// // Define a tree of nested scopes - /// let adapter = ScopeAdapter([ - /// "foo": RangeRowAdapter(0..<1), - /// "bar": RangeRowAdapter(1..<2).addingScopes([ - /// "baz" : RangeRowAdapter(2..<3)])]) - /// - /// // Fetch - /// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" - /// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)! - /// - /// row.scopesTree.names // ["foo", "bar", "baz"] - /// - /// row.scopesTree["foo"] // [foo:1] - /// row.scopesTree["bar"] // [bar:2] - /// row.scopesTree["baz"] // [baz:3] - public struct ScopesTreeView { - let scopes: ScopesView - - /// The scopes defined on this row, recursively. - public var names: Set { - var names = Set() - for (name, row) in scopes { - names.insert(name) - names.formUnion(row.scopesTree.names) - } - return names - } - - /// Returns the row associated with the given scope. - /// - /// For example: - /// - /// let request = Book.including(required: Book.author) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(row) - /// // Prints [id:42 title:"Moby-Dick"] - /// - /// let authorRow = row.scopesTree["author"] - /// print(authorRow) - /// // Prints [id:1 name:"Herman Melville"] - /// - /// Associated rows stored in nested associations are available, too: - /// - /// let request = Book.including(required: Book.author.including(required: Author.country)) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(row) - /// // Prints [id:42 title:"Moby-Dick"] - /// - /// let countryRow = row.scopesTree["country"] - /// print(countryRow) - /// // Prints [code:"US" name:"United States"] - /// - /// Nil is returned if the scope is not available. - public subscript(_ name: String) -> Row? { - var fifo = Array(scopes) - while !fifo.isEmpty { - let scope = fifo.removeFirst() - if scope.name == name { - return scope.row - } - fifo.append(contentsOf: scope.row.scopes) - } - return nil - } - } -} - -// MARK: - Row.PrefetchedRowsView - -extension Row { - fileprivate struct Prefetch: Equatable { - // Nil for intermediate associations - var rows: [Row]? - // OrderedDictionary so that breadth-first search gives a consistent result - // (we preserve the ordering of associations in the request) - var prefetches: OrderedDictionary - } - - /// A view on the prefetched associated rows. - /// - /// For example: - /// - /// let request = Author.including(all: Author.books) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(row) - /// // Prints [id:1 name:"Herman Melville"] - /// - /// let bookRows = row.prefetchedRows["books"] - /// print(bookRows[0]) - /// // Prints [id:42 title:"Moby-Dick"] - public struct PrefetchedRowsView: Equatable { - // OrderedDictionary so that breadth-first search gives a consistent result - // (we preserve the ordering of associations in the request) - fileprivate var prefetches: OrderedDictionary = [:] - - /// True if there is no prefetched associated rows. - public var isEmpty: Bool { - return prefetches.isEmpty - } - - /// The keys for available prefetched rows - /// - /// For example: - /// - /// let request = Author.including(all: Author.books) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(row.prefetchedRows.keys) - /// // Prints ["books"] - public var keys: Set { - var result: Set = [] - var fifo = Array(prefetches) - while !fifo.isEmpty { - let (prefetchKey, prefetch) = fifo.removeFirst() - if prefetch.rows != nil { - result.insert(prefetchKey) - } - fifo.append(contentsOf: prefetch.prefetches) - } - return result - } - - /// Returns the prefetched rows associated with the given key. - /// - /// For example: - /// - /// let request = Author.including(all: Author.books) - /// let row = try Row.fetchOne(db, request)! - /// - /// print(row) - /// // Prints [id:1 name:"Herman Melville"] - /// - /// let bookRows = row.prefetchedRows["books"] - /// print(bookRows[0]) - /// // Prints [id:42 title:"Moby-Dick"] - /// - /// Prefetched rows stored in nested "to-one" associations are - /// available, too. - /// - /// Nil is returned if the key is not available. - public subscript(_ key: String) -> [Row]? { - var fifo = Array(prefetches) - while !fifo.isEmpty { - let (prefetchKey, prefetch) = fifo.removeFirst() - if prefetchKey == key { - return prefetch.rows - } - fifo.append(contentsOf: prefetch.prefetches) - } - return nil - } - - mutating func setRows(_ rows: [Row], forKeyPath keyPath: [String]) { - prefetches.setRows(rows, forKeyPath: keyPath) - } - } -} - -extension OrderedDictionary where Key == String, Value == Row.Prefetch { - fileprivate mutating func setRows(_ rows: [Row], forKeyPath keyPath: [String]) { - var keyPath = keyPath - let key = keyPath.removeFirst() - if keyPath.isEmpty { - self[key, default: Row.Prefetch(rows: nil, prefetches: [:])].rows = rows - } else { - self[key, default: Row.Prefetch(rows: nil, prefetches: [:])].prefetches.setRows(rows, forKeyPath: keyPath) - } - } -} - -// MARK: - RowImpl - -// The protocol for Row underlying implementation -protocol RowImpl { - var count: Int { get } - var isFetched: Bool { get } - func scopes(prefetchedRows: Row.PrefetchedRowsView) -> Row.ScopesView - func columnName(atUncheckedIndex index: Int) -> String - func hasNull(atUncheckedIndex index:Int) -> Bool - func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue - func fastDecode(_ type: Value.Type, atUncheckedIndex index: Int) -> Value - func fastDecodeIfPresent(_ type: Value.Type, atUncheckedIndex index: Int) -> Value? - func dataNoCopy(atUncheckedIndex index:Int) -> Data? - - /// Returns the index of the leftmost column that matches *name* (case-insensitive) - func index(ofColumn name: String) -> Int? - - // row.impl is guaranteed to be self. - func unscopedRow(_ row: Row) -> Row - func unadaptedRow(_ row: Row) -> Row - func copiedRow(_ row: Row) -> Row -} - -extension RowImpl { - func copiedRow(_ row: Row) -> Row { - // unless customized, assume unsafe and unadapted row - return Row(impl: ArrayRowImpl(columns: row.map { $0 })) - } - - func unscopedRow(_ row: Row) -> Row { - // unless customized, assume unadapted row (see AdaptedRowImpl for customization) - return row - } - - func unadaptedRow(_ row: Row) -> Row { - // unless customized, assume unadapted row (see AdaptedRowImpl for customization) - return row - } - - func scopes(prefetchedRows: Row.PrefetchedRowsView) -> Row.ScopesView { - // unless customized, assume unuscoped row (see AdaptedRowImpl for customization) - return Row.ScopesView() - } - - func hasNull(atUncheckedIndex index:Int) -> Bool { - // unless customized, use slow check (see StatementRowImpl and AdaptedRowImpl for customization) - return databaseValue(atUncheckedIndex: index).isNull - } - - func fastDecode( - _ type: Value.Type, - atUncheckedIndex index: Int) -> Value - { - // unless customized, use slow decoding (see StatementRowImpl and AdaptedRowImpl for customization) - return Value.decode( - from: databaseValue(atUncheckedIndex: index), - conversionContext: ValueConversionContext(Row(impl: self)).atColumn(index)) - } - - func fastDecodeIfPresent( - _ type: Value.Type, - atUncheckedIndex index: Int) -> Value? - { - // unless customized, use slow decoding (see StatementRowImpl and AdaptedRowImpl for customization) - return Value.decodeIfPresent( - from: databaseValue(atUncheckedIndex: index), - conversionContext: ValueConversionContext(Row(impl: self)).atColumn(index)) - } - - func dataNoCopy(atUncheckedIndex index:Int) -> Data? { - // unless customized, copy data (see StatementRowImpl and AdaptedRowImpl for customization) - return Data.decodeIfPresent( - from: databaseValue(atUncheckedIndex: index), - conversionContext: ValueConversionContext(Row(impl: self)).atColumn(index)) - } -} - -// TODO: merge with StatementCopyRowImpl eventually? -/// See Row.init(dictionary:) -private struct ArrayRowImpl : RowImpl { - let columns: [(String, DatabaseValue)] - - init(columns: [(String, DatabaseValue)]) { - self.columns = columns - } - - var count: Int { - return columns.count - } - - var isFetched: Bool { - return false - } - - func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue { - return columns[index].1 - } - - func columnName(atUncheckedIndex index: Int) -> String { - return columns[index].0 - } - - func index(ofColumn name: String) -> Int? { - let lowercaseName = name.lowercased() - return columns.firstIndex { (column, _) in column.lowercased() == lowercaseName } - } - - func copiedRow(_ row: Row) -> Row { - return row - } -} - - -// TODO: merge with ArrayRowImpl eventually? -/// See Row.init(copiedFromStatementRef:sqliteStatement:) -private struct StatementCopyRowImpl : RowImpl { - let dbValues: ContiguousArray - let columnNames: [String] - - init(sqliteStatement: SQLiteStatement, columnNames: [String]) { - let sqliteStatement = sqliteStatement - self.dbValues = ContiguousArray((0.. DatabaseValue { - return dbValues[index] - } - - func columnName(atUncheckedIndex index: Int) -> String { - return columnNames[index] - } - - func index(ofColumn name: String) -> Int? { - let lowercaseName = name.lowercased() - return columnNames.firstIndex { $0.lowercased() == lowercaseName } - } - - func copiedRow(_ row: Row) -> Row { - return row - } -} - - -/// See Row.init(statement:) -private struct StatementRowImpl : RowImpl { - let statementRef: Unmanaged - let sqliteStatement: SQLiteStatement - let lowercaseColumnIndexes: [String: Int] - - init(sqliteStatement: SQLiteStatement, statementRef: Unmanaged) { - self.statementRef = statementRef - self.sqliteStatement = sqliteStatement - // Optimize row[columnName] - let lowercaseColumnNames = (0.. Bool { - // Avoid extracting values, because this modifies the SQLite statement. - return sqlite3_column_type(sqliteStatement, Int32(index)) == SQLITE_NULL - } - - func dataNoCopy(atUncheckedIndex index:Int) -> Data? { - guard sqlite3_column_type(sqliteStatement, Int32(index)) != SQLITE_NULL else { - return nil - } - guard let bytes = sqlite3_column_blob(sqliteStatement, Int32(index)) else { - return Data() - } - let count = Int(sqlite3_column_bytes(sqliteStatement, Int32(index))) - return Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), count: count, deallocator: .none) - } - - func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue { - return DatabaseValue(sqliteStatement: sqliteStatement, index: Int32(index)) - } - - func fastDecode( - _ type: Value.Type, - atUncheckedIndex index: Int) -> Value - { - return Value.fastDecode(from: sqliteStatement, atUncheckedIndex: Int32(index)) - } - - func fastDecodeIfPresent( - _ type: Value.Type, - atUncheckedIndex index: Int) -> Value? - { - return Value.fastDecodeIfPresent(from: sqliteStatement, atUncheckedIndex: Int32(index)) - } - - func columnName(atUncheckedIndex index: Int) -> String { - return statementRef.takeUnretainedValue().columnNames[index] - } - - func index(ofColumn name: String) -> Int? { - if let index = lowercaseColumnIndexes[name] { - return index - } - return lowercaseColumnIndexes[name.lowercased()] - } - - func copiedRow(_ row: Row) -> Row { - return Row(copiedFromSQLiteStatement: sqliteStatement, statementRef: statementRef) - } -} - -// This one is not optimized at all, since it is only used in fatal conversion errors, so far -private struct SQLiteStatementRowImpl : RowImpl { - let sqliteStatement: SQLiteStatement - var count: Int { return Int(sqlite3_column_count(sqliteStatement)) } - var isFetched: Bool { return true } - - func columnName(atUncheckedIndex index: Int) -> String { - return String(cString: sqlite3_column_name(sqliteStatement, Int32(index))) - } - - func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue { - return DatabaseValue(sqliteStatement: sqliteStatement, index: Int32(index)) - } - - func index(ofColumn name: String) -> Int? { - let name = name.lowercased() - for index in 0.. DatabaseValue { - // Programmer error - fatalError("row index out of range") - } - - func columnName(atUncheckedIndex index: Int) -> String { - // Programmer error - fatalError("row index out of range") - } - - func index(ofColumn name: String) -> Int? { - return nil - } - - func copiedRow(_ row: Row) -> Row { - return row - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/RowAdapter.swift b/Example/Pods/GRDB.swift/GRDB/Core/RowAdapter.swift deleted file mode 100755 index b9ab95c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/RowAdapter.swift +++ /dev/null @@ -1,545 +0,0 @@ -import Foundation - -/// Returns an array of row adapters that split a row according to the input -/// number of columns. -/// -/// For example: -/// -/// let sql = "SELECT 1, 2,3,4, 5,6, 7,8" -/// // <.><. . .><. .><. .> -/// let adapters = splittingRowAdapters([1, 3, 2]) -/// let adapter = ScopeAdapter([ -/// "a": adapters[0], -/// "b": adapters[1], -/// "c": adapters[2], -/// "d": adapters[3]]) -/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter) -/// row.scopes["a"] // [1] -/// row.scopes["b"] // [2, 3, 4] -/// row.scopes["c"] // [5, 6] -/// row.scopes["d"] // [7, 8] -public func splittingRowAdapters(columnCounts: [Int]) -> [RowAdapter] { - guard !columnCounts.isEmpty else { - // Identity adapter - return [SuffixRowAdapter(fromIndex: 0)] - } - - // [1, 3, 2] -> [0, 1, 4, 6] - let columnIndexes = columnCounts.reduce(into: [0]) { (acc, count) in - acc.append(acc.last! + count) - } - - // [0, 1, 4, 6] -> [(0..<1), (1..<4), (4..<6)] - let rangeAdapters = zip(columnIndexes, columnIndexes.suffix(from: 1)) - .map { RangeRowAdapter($0..<$1) } - - // (6...) - let suffixAdapter = SuffixRowAdapter(fromIndex: columnIndexes.last!) - - // [(0..<1), (1..<4), (4..<6), (6...)] - return rangeAdapters + [suffixAdapter] -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// LayoutedColumnMapping is a type that supports the RowAdapter protocol. -/// -/// :nodoc: -public struct LayoutedColumnMapping { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// An array of (baseIndex, mappedName) pairs, where baseIndex is the index - /// of a column in a base row, and mappedName the mapped name of - /// that column. - public let layoutColumns: [(Int, String)] - - /// A cache for layoutIndex(ofColumn:) - let lowercaseColumnIndexes: [String: Int] // [mappedColumn: layoutColumnIndex] - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates a LayoutedColumnMapping from an array of (baseIndex, mappedName) - /// pairs. In each pair: - /// - /// - baseIndex is the index of a column in a base row - /// - name is the mapped name of the column - /// - /// For example, the following LayoutedColumnMapping defines two columns, "foo" - /// and "bar", based on the base columns at indexes 1 and 2: - /// - /// LayoutedColumnMapping(layoutColumns: [(1, "foo"), (2, "bar")]) - /// - /// Use it in your custom RowAdapter type: - /// - /// struct FooBarAdapter : RowAdapter { - /// func layoutAdapter(layout: RowLayout) throws -> LayoutedRowAdapter { - /// return LayoutedColumnMapping(layoutColumns: [(1, "foo"), (2, "bar")]) - /// } - /// } - /// - /// // [foo:"foo" bar: "bar"] - /// try Row.fetchOne(db, sql: "SELECT NULL, 'foo', 'bar'", adapter: FooBarAdapter()) - public init(layoutColumns: S) where S.Iterator.Element == (Int, String) { - self.layoutColumns = Array(layoutColumns) - self.lowercaseColumnIndexes = Dictionary( - layoutColumns - .enumerated() - .map { ($0.element.1.lowercased(), $0.offset) }, - uniquingKeysWith: { (left, _) in left }) // keep leftmost indexes - } - - func baseColumnIndex(atMappingIndex index: Int) -> Int { - return layoutColumns[index].0 - } - - func columnName(atMappingIndex index: Int) -> String { - return layoutColumns[index].1 - } -} - -/// LayoutedColumnMapping adopts LayoutedRowAdapter -/// -/// :nodoc: -extension LayoutedColumnMapping : LayoutedRowAdapter { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns self. - public var mapping: LayoutedColumnMapping { - return self - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns the empty dictionary. - public var scopes: [String: LayoutedRowAdapter] { - return [:] - } -} - -/// :nodoc: -extension LayoutedColumnMapping : RowLayout { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns the index of the leftmost column named `name`, in a - /// case-insensitive way. - public func layoutIndex(ofColumn name: String) -> Int? { - if let index = lowercaseColumnIndexes[name] { - return index - } - return lowercaseColumnIndexes[name.lowercased()] - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// LayoutedRowAdapter is a protocol that supports the RowAdapter protocol. -/// -/// GRBD ships with a ready-made type that adopts this protocol: -/// LayoutedColumnMapping. -/// -/// :nodoc: -public protocol LayoutedRowAdapter { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// A LayoutedColumnMapping that defines how to map a column name to a - /// column in a base row. - var mapping: LayoutedColumnMapping { get } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The layouted row adapters for each scope. - var scopes: [String: LayoutedRowAdapter] { get } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// RowLayout is a protocol that supports the RowAdapter protocol. It describes -/// a layout of a base row. -/// -/// :nodoc: -public protocol RowLayout { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// An array of (baseIndex, name) pairs, where baseIndex is the index - /// of a column in a base row, and name the name of that column. - var layoutColumns: [(Int, String)] { get } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns the index of the leftmost column named `name`, in a - /// case-insensitive way. - func layoutIndex(ofColumn name: String) -> Int? -} - -extension SelectStatement : RowLayout { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var layoutColumns: [(Int, String)] { - return Array(columnNames.enumerated()) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func layoutIndex(ofColumn name: String) -> Int? { - return index(ofColumn: name) - } -} - -/// RowAdapter is a protocol that helps two incompatible row interfaces working -/// together. -/// -/// GRDB ships with four concrete types that adopt the RowAdapter protocol: -/// -/// - ColumnMapping: renames row columns -/// - SuffixRowAdapter: hides the first columns of a row -/// - RangeRowAdapter: only exposes a range of columns -/// - ScopeAdapter: groups several adapters together to define named scopes -/// -/// To use a row adapter, provide it to any method that fetches: -/// -/// let adapter = SuffixRowAdapter(fromIndex: 2) -/// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" -/// -/// // [baz:3] -/// try Row.fetchOne(db, sql: sql, adapter: adapter) -public protocol RowAdapter { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// You never call this method directly. It is called for you whenever an - /// adapter has to be applied. - /// - /// The result is a value that adopts LayoutedRowAdapter, such as - /// LayoutedColumnMapping. - /// - /// For example: - /// - /// // An adapter that turns any row to a row that contains a single - /// // column named "foo" whose value is the leftmost value of the - /// // base row. - /// struct FirstColumnAdapter : RowAdapter { - /// func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - /// return LayoutedColumnMapping(layoutColumns: [(0, "foo")]) - /// } - /// } - /// - /// // [foo:1] - /// try Row.fetchOne(db, sql: "SELECT 1, 2, 3", adapter: FirstColumnAdapter()) - func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter -} - -extension RowAdapter { - /// Returns an adapter based on self, with added scopes. - /// - /// If self already defines scopes, the added scopes replace - /// eventual existing scopes with the same name. - /// - /// - parameter scopes: A dictionary that maps scope names to - /// row adapters. - public func addingScopes(_ scopes: [String: RowAdapter]) -> RowAdapter { - if scopes.isEmpty { - return self - } else { - return ScopeAdapter(base: self, scopes: scopes) - } - } -} - -extension RowAdapter { - func baseColumnIndex(atIndex index: Int, layout: RowLayout) throws -> Int { - return try layoutedAdapter(from: layout).mapping.baseColumnIndex(atMappingIndex: index) - } -} - -/// EmptyRowAdapter is a row adapter that hides all columns. -public struct EmptyRowAdapter: RowAdapter { - /// Creates an EmptyRowAdapter - public init() { } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - return LayoutedColumnMapping(layoutColumns: []) - } -} - -/// ColumnMapping is a row adapter that maps column names. -/// -/// let adapter = ColumnMapping(["foo": "bar"]) -/// let sql = "SELECT 'foo' AS foo, 'bar' AS bar, 'baz' AS baz" -/// -/// // [foo:"bar"] -/// try Row.fetchOne(db, sql: sql, adapter: adapter) -public struct ColumnMapping : RowAdapter { - /// A dictionary from mapped column names to column names in a base row. - let mapping: [String: String] - - /// Creates a ColumnMapping with a dictionary from mapped column names to - /// column names in a base row. - public init(_ mapping: [String: String]) { - self.mapping = mapping - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - let layoutColumns = try mapping - .map { (mappedColumn, baseColumn) -> (Int, String) in - guard let index = layout.layoutIndex(ofColumn: baseColumn) else { - let columnNames = layout.layoutColumns.map { $0.1 } - throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "Mapping references missing column \(baseColumn). Valid column names are: \(columnNames.joined(separator: ", ")).") - } - let baseIndex = layout.layoutColumns[index].0 - return (baseIndex, mappedColumn) - } - .sorted { $0.0 < $1.0 } // preserve ordering of base columns - return LayoutedColumnMapping(layoutColumns: layoutColumns) - } -} - -/// SuffixRowAdapter is a row adapter that hides the first columns in a row. -/// -/// let adapter = SuffixRowAdapter(fromIndex: 2) -/// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz" -/// -/// // [baz:3] -/// try Row.fetchOne(db, sql: sql, adapter: adapter) -public struct SuffixRowAdapter : RowAdapter { - /// The suffix index - let index: Int - - /// Creates a SuffixRowAdapter that hides all columns before the - /// provided index. - /// - /// If index is 0, the layout row is identical to the base row. - public init(fromIndex index: Int) { - GRDBPrecondition(index >= 0, "Negative column index is out of range") - self.index = index - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - return LayoutedColumnMapping(layoutColumns: layout.layoutColumns.suffix(from: index)) - } -} - -/// RangeRowAdapter is a row adapter that only exposes a range of columns. -/// -/// let adapter = RangeRowAdapter(1..<3) -/// let sql = "SELECT 1 AS foo, 2 AS bar, 3 AS baz, 4 as qux" -/// -/// // [bar:2 baz:3] -/// try Row.fetchOne(db, sql: sql, adapter: adapter) -public struct RangeRowAdapter : RowAdapter { - /// The range - let range: CountableRange - - /// Creates a RangeRowAdapter that only exposes a range of columns. - public init(_ range: CountableRange) { - GRDBPrecondition(range.lowerBound >= 0, "Negative column index is out of range") - self.range = range - } - - /// Creates a RangeRowAdapter that only exposes a range of columns. - public init(_ range: CountableClosedRange) { - GRDBPrecondition(range.lowerBound >= 0, "Negative column index is out of range") - self.range = range.lowerBound..<(range.upperBound + 1) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - return LayoutedColumnMapping(layoutColumns: layout.layoutColumns[range]) - } -} - -/// ScopeAdapter is a row adapter that lets you define scopes on rows. -/// -/// // Two adapters -/// let fooAdapter = ColumnMapping(["value": "foo"]) -/// let barAdapter = ColumnMapping(["value": "bar"]) -/// -/// // Define scopes -/// let adapter = ScopeAdapter([ -/// "foo": fooAdapter, -/// "bar": barAdapter]) -/// -/// // Fetch -/// let sql = "SELECT 'foo' AS foo, 'bar' AS bar" -/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)! -/// -/// // Scoped rows: -/// if let fooRow = row.scopes["foo"] { -/// fooRow["value"] // "foo" -/// } -/// if let barRow = row.scopes["bar"] { -/// barRow["value"] // "bar" -/// } -public struct ScopeAdapter : RowAdapter { - - /// The base adapter - let base: RowAdapter - - /// The scope adapters - let scopes: [String: RowAdapter] - - /// Creates an adapter that preserves row contents and add scoped rows. - /// - /// For example: - /// - /// let adapter = ScopeAdapter(["suffix": SuffixRowAdapter(fromIndex: 1)]) - /// let row = try Row.fetchOne(db, sql: "SELECT 1, 2, 3", adapter: adapter)! - /// row // [1, 2, 3] - /// row.scopes["suffix"] // [2, 3] - /// - /// - parameter scopes: A dictionary that maps scope names to - /// row adapters. - public init(_ scopes: [String: RowAdapter]) { - // Use SuffixRowAdapter(fromIndex: 0) as the identity adapter - self.init(base: SuffixRowAdapter(fromIndex: 0), scopes: scopes) - } - - /// Creates an adapter based on the base adapter, and add scoped rows. - /// - /// For example: - /// - /// let baseAdapter = RangeRowAdapter(0..<1) - /// let adapter = ScopeAdapter(base: baseAdapter, scopes: ["suffix": SuffixRowAdapter(fromIndex: 1)]) - /// let row = try Row.fetchOne(db, sql: "SELECT 1, 2, 3", adapter: adapter)! - /// row // [1] - /// row.scopes["initial"] // [2, 3] - /// - /// If the base adapter already defines scopes, the given scopes replace - /// eventual existing scopes with the same name. - /// - /// This initializer is equivalent to `baseAdapter.addingScopes(scopes)`. - /// - /// - parameter base: A dictionary that maps scope names to - /// row adapters. - /// - parameter scopes: A dictionary that maps scope names to - /// row adapters. - public init(base: RowAdapter, scopes: [String: RowAdapter]) { - self.base = base - self.scopes = scopes - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - let layoutedAdapter = try base.layoutedAdapter(from: layout) - var layoutedScopes = layoutedAdapter.scopes - for (name, adapter) in scopes { - try layoutedScopes[name] = adapter.layoutedAdapter(from: layout) - } - return LayoutedScopeAdapter( - mapping: layoutedAdapter.mapping, - scopes: layoutedScopes) - } -} - -/// The LayoutedRowAdapter for ScopeAdapter -struct LayoutedScopeAdapter : LayoutedRowAdapter { - let mapping: LayoutedColumnMapping - let scopes: [String: LayoutedRowAdapter] -} - -struct ChainedAdapter : RowAdapter { - let first: RowAdapter - let second: RowAdapter - - func layoutedAdapter(from layout: RowLayout) throws -> LayoutedRowAdapter { - return try second.layoutedAdapter(from: first.layoutedAdapter(from: layout).mapping) - } -} - -extension Row { - /// Creates a row from a base row and a statement adapter - convenience init(base: Row, adapter: LayoutedRowAdapter) { - self.init(impl: AdaptedRowImpl(base: base, adapter: adapter)) - } - - /// Returns self if adapter is nil - func adapted(with adapter: RowAdapter?, layout: RowLayout) throws -> Row { - guard let adapter = adapter else { - return self - } - return try Row(base: self, adapter: adapter.layoutedAdapter(from: layout)) - } -} - -struct AdaptedRowImpl : RowImpl { - let base: Row - let adapter: LayoutedRowAdapter - let mapping: LayoutedColumnMapping - - init(base: Row, adapter: LayoutedRowAdapter) { - self.base = base - self.adapter = adapter - self.mapping = adapter.mapping - } - - var count: Int { - return mapping.layoutColumns.count - } - - var isFetched: Bool { - return base.isFetched - } - - func scopes(prefetchedRows: Row.PrefetchedRowsView) -> Row.ScopesView { - return Row.ScopesView(row: base, scopes: adapter.scopes, prefetchedRows: prefetchedRows) - } - - func hasNull(atUncheckedIndex index: Int) -> Bool { - let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index) - return base.impl.hasNull(atUncheckedIndex: mappedIndex) - } - - func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue { - let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index) - return base.impl.databaseValue(atUncheckedIndex: mappedIndex) - } - - func fastDecode( - _ type: Value.Type, - atUncheckedIndex index: Int) -> Value - { - let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index) - return Value.fastDecode(from: base, atUncheckedIndex: mappedIndex) - } - - func fastDecodeIfPresent( - _ type: Value.Type, - atUncheckedIndex index: Int) -> Value? - { - let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index) - return Value.fastDecodeIfPresent(from: base, atUncheckedIndex: mappedIndex) - } - - func dataNoCopy(atUncheckedIndex index:Int) -> Data? { - let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index) - return base.impl.dataNoCopy(atUncheckedIndex: mappedIndex) - } - - func columnName(atUncheckedIndex index: Int) -> String { - return mapping.columnName(atMappingIndex: index) - } - - func index(ofColumn name: String) -> Int? { - return mapping.layoutIndex(ofColumn: name) - } - - func copiedRow(_ row: Row) -> Row { - return Row(base: base.copy(), adapter: adapter) - } - - func unscopedRow(_ row: Row) -> Row { - assert(adapter.mapping.scopes.isEmpty) - return Row(base: base, adapter: adapter.mapping) - } - - func unadaptedRow(_ row: Row) -> Row { - return base.unadapted - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/SQLInterpolation.swift b/Example/Pods/GRDB.swift/GRDB/Core/SQLInterpolation.swift deleted file mode 100755 index 49bc2b3..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/SQLInterpolation.swift +++ /dev/null @@ -1,33 +0,0 @@ -#if swift(>=5.0) -/// :nodoc: -public struct SQLInterpolation: StringInterpolationProtocol { - var context = SQLGenerationContext.literalGenerationContext(withArguments: true) - var sql: String - var arguments: StatementArguments { - get { return context.arguments! } - set { context.arguments = newValue } - } - - public init(literalCapacity: Int, interpolationCount: Int) { - sql = "" - sql.reserveCapacity(literalCapacity + interpolationCount) - } - - /// "SELECT * FROM player" - public mutating func appendLiteral(_ sql: String) { - self.sql += sql - } - - /// "SELECT * FROM \(sql: "player")" - public mutating func appendInterpolation(sql: String, arguments: StatementArguments = StatementArguments()) { - self.sql += sql - self.arguments += arguments - } - - /// "SELECT * FROM player WHERE \(literal: condition)" - public mutating func appendInterpolation(literal sqlLiteral: SQLLiteral) { - sql += sqlLiteral.sql - arguments += sqlLiteral.arguments - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/SQLLiteral.swift b/Example/Pods/GRDB.swift/GRDB/Core/SQLLiteral.swift deleted file mode 100755 index e95e58b..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/SQLLiteral.swift +++ /dev/null @@ -1,145 +0,0 @@ -/// SQLLiteral is a type which support [SQL Interpolation](https://github.com/groue/GRDB.swift/blob/master/Documentation/SQLInterpolation.md). -/// -/// For example: -/// -/// try dbQueue.write { db in -/// let name: String = ... -/// let id: Int64 = ... -/// let query: SQLLiteral = "UPDATE player SET name = \(name) WHERE id = \(id)" -/// try db.execute(literal: query) -/// } -public struct SQLLiteral { - private(set) public var sql: String - private(set) public var arguments: StatementArguments - - /// Creates an SQLLiteral from a plain SQL string, and eventual arguments. - /// - /// For example: - /// - /// let query = SQLLiteral( - /// sql: "UPDATE player SET name = ? WHERE id = ?", - /// arguments: [name, id]) - public init(sql: String, arguments: StatementArguments = StatementArguments()) { - self.sql = sql - self.arguments = arguments - } - - /// Returns a literal whose SQL is transformed by the given closure. - public func mapSQL(_ transform: (String) throws -> String) rethrows -> SQLLiteral { - var result = self - result.sql = try transform(sql) - return result - } -} - -extension SQLLiteral { - /// Returns the SQLLiteral produced by the concatenation of two literals. - /// - /// let name = "O'Brien" - /// let selection: SQLLiteral = "SELECT * FROM player " - /// let condition: SQLLiteral = "WHERE name = \(name)" - /// let query = selection + condition - public static func + (lhs: SQLLiteral, rhs: SQLLiteral) -> SQLLiteral { - var result = lhs - result += rhs - return result - } - - /// Appends an SQLLiteral to the receiver. - /// - /// let name = "O'Brien" - /// var query: SQLLiteral = "SELECT * FROM player " - /// query += "WHERE name = \(name)" - public static func += (lhs: inout SQLLiteral, rhs: SQLLiteral) { - lhs.sql += rhs.sql - lhs.arguments += rhs.arguments - } - - /// Appends an SQLLiteral to the receiver. - /// - /// let name = "O'Brien" - /// var query: SQLLiteral = "SELECT * FROM player " - /// query.append(literal: "WHERE name = \(name)") - public mutating func append(literal sqlLiteral: SQLLiteral) { - self += sqlLiteral - } - - /// Appends a plain SQL string to the receiver, and eventual arguments. - /// - /// let name = "O'Brien" - /// var query: SQLLiteral = "SELECT * FROM player " - /// query.append(sql: "WHERE name = ?", arguments: [name]) - public mutating func append(sql: String, arguments: StatementArguments = StatementArguments()) { - self += SQLLiteral(sql: sql, arguments: arguments) - } -} - -extension Sequence where Element == SQLLiteral { - /// Returns the concatenated SQLLiteral of this sequence of literals, - /// inserting the given separator between each element. - /// - /// let components: [SQLLiteral] = [ - /// "UPDATE player", - /// "SET name = \(name)", - /// "WHERE id = \(id)" - /// ] - /// let query = components.joined(separator: " ") - public func joined(separator: String = "") -> SQLLiteral { - var sql = "" - var arguments = StatementArguments() - var first = true - for literal in self { - if first { - first = false - } else { - sql += separator - } - sql += literal.sql - arguments += literal.arguments - } - return SQLLiteral(sql: sql, arguments: arguments) - } -} - -extension Collection where Element == SQLLiteral { - /// Returns the concatenated SQLLiteral of this collection of literals, - /// inserting the given separator between each element. - /// - /// let components: [SQLLiteral] = [ - /// "UPDATE player", - /// "SET name = \(name)", - /// "WHERE id = \(id)" - /// ] - /// let query = components.joined(separator: " ") - public func joined(separator: String = "") -> SQLLiteral { - let sql = map { $0.sql }.joined(separator: separator) - let arguments = reduce(into: StatementArguments()) { $0 += $1.arguments } - return SQLLiteral(sql: sql, arguments: arguments) - } -} - -// MARK: - ExpressibleByStringInterpolation - -#if swift(>=5.0) -extension SQLLiteral: ExpressibleByStringInterpolation { - /// :nodoc - public init(unicodeScalarLiteral: String) { - self.init(sql: unicodeScalarLiteral, arguments: []) - } - - /// :nodoc: - public init(extendedGraphemeClusterLiteral: String) { - self.init(sql: extendedGraphemeClusterLiteral, arguments: []) - } - - /// :nodoc: - public init(stringLiteral: String) { - self.init(sql: stringLiteral, arguments: []) - } - - /// :nodoc: - public init(stringInterpolation sqlInterpolation: SQLInterpolation) { - self.init(sql: sqlInterpolation.sql, arguments: sqlInterpolation.arguments) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/SQLRequest.swift b/Example/Pods/GRDB.swift/GRDB/Core/SQLRequest.swift deleted file mode 100755 index 46efea6..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/SQLRequest.swift +++ /dev/null @@ -1,168 +0,0 @@ -/// A FetchRequest built from raw SQL. -public struct SQLRequest : FetchRequest { - /// There are two statement caches: one "public" for statements generated by - /// the user, and one "internal" for the statements generated by GRDB. Those - /// are separated so that GRDB has no opportunity to inadvertently modify - /// the arguments of user's cached statements. - enum Cache { - /// The public cache, for library user - case `public` - - /// The internal cache, for GRDB - case `internal` - } - - public typealias RowDecoder = T - - /// The raw SQL query - /// - /// let id = 42 - /// let request: SQLRequest = "SELECT * FROM player WHERE id = \(id)" - /// request.sql // "SELECT * FROM player WHERE id = ?" - public var sql: String { return sqlLiteral.sql } - - /// The request argument - /// - /// let id = 42 - /// let request: SQLRequest = "SELECT * FROM player WHERE id = \(id)" - /// request.arguments // [42] - public var arguments: StatementArguments { return sqlLiteral.arguments } - - /// The request adapter - public var adapter: RowAdapter? - - private var sqlLiteral: SQLLiteral - private let cache: Cache? - - /// Creates a request from an SQL string, optional arguments, and - /// optional row adapter. - /// - /// let request = SQLRequest(sql: """ - /// SELECT name FROM player - /// """) - /// let request = SQLRequest(sql: """ - /// SELECT * FROM player WHERE id = ? - /// """, arguments: [1]) - /// - /// - parameters: - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter. - /// - cached: Defaults to false. If true, the request reuses a cached - /// prepared statement. - /// - returns: A SQLRequest - public init(sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil, cached: Bool = false) { - self.init(literal: SQLLiteral(sql: sql, arguments: arguments), adapter: adapter, fromCache: cached ? .public : nil) - } - - /// Creates a request from an SQLLiteral, and optional row adapter. - /// - /// let request = SQLRequest(literal: SQLLiteral(sql: """ - /// SELECT name FROM player - /// """)) - /// let request = SQLRequest(literal: SQLLiteral(sql: """ - /// SELECT * FROM player WHERE name = ? - /// """, arguments: ["O'Brien"])) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// let request = SQLRequest(literal: """ - /// SELECT * FROM player WHERE name = \("O'brien") - /// """) - /// - /// - parameters: - /// - sqlLiteral: An SQLLiteral. - /// - adapter: Optional RowAdapter. - /// - cached: Defaults to false. If true, the request reuses a cached - /// prepared statement. - /// - returns: A SQLRequest - public init(literal sqlLiteral: SQLLiteral, adapter: RowAdapter? = nil, cached: Bool = false) { - self.init(literal: sqlLiteral, adapter: adapter, fromCache: cached ? .public : nil) - } - - /// Creates an SQL request from any other fetch request. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A request. - /// - cached: Defaults to false. If true, the request reuses a cached - /// prepared statement. - /// - returns: An SQLRequest - public init(_ db: Database, request: Request, cached: Bool = false) throws where Request.RowDecoder == RowDecoder { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - self.init(literal: SQLLiteral(sql: statement.sql, arguments: statement.arguments), adapter: adapter, cached: cached) - } - - /// Creates a request from an SQLLiteral, and optional row adapter. - /// - /// let request = SQLRequest(literal: SQLLiteral(sql: """ - /// SELECT name FROM player - /// """)) - /// let request = SQLRequest(literal: SQLLiteral(sql: """ - /// SELECT * FROM player WHERE name = ? - /// """, arguments: ["O'Brien"])) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// let request = SQLRequest(literal: """ - /// SELECT * FROM player WHERE name = \("O'brien") - /// """) - /// - /// - parameters: - /// - sqlLiteral: An SQLLiteral. - /// - adapter: Optional RowAdapter. - /// - cache: The eventual cache - /// - returns: A SQLRequest - init(literal sqlLiteral: SQLLiteral, adapter: RowAdapter? = nil, fromCache cache: Cache?) { - self.sqlLiteral = sqlLiteral - self.adapter = adapter - self.cache = cache - } - - /// A tuple that contains a prepared statement that is ready to be - /// executed, and an eventual row adapter. - /// - /// - parameter db: A database connection. - /// - parameter singleResult: SQLRequest disregards this hint. - /// - /// :nodoc: - public func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?) { - let statement: SelectStatement - switch cache { - case .none: - statement = try db.makeSelectStatement(sql: sqlLiteral.sql) - case .public?: - statement = try db.cachedSelectStatement(sql: sqlLiteral.sql) - case .internal?: - statement = try db.internalCachedSelectStatement(sql: sqlLiteral.sql) - } - try statement.setArgumentsWithValidation(sqlLiteral.arguments) - return (statement, adapter) - } -} - -#if swift(>=5.0) -extension SQLRequest: ExpressibleByStringInterpolation { - /// :nodoc - public init(unicodeScalarLiteral: String) { - self.init(sql: unicodeScalarLiteral) - } - - /// :nodoc: - public init(extendedGraphemeClusterLiteral: String) { - self.init(sql: extendedGraphemeClusterLiteral) - } - - /// :nodoc: - public init(stringLiteral: String) { - self.init(sql: stringLiteral) - } - - /// :nodoc: - public init(stringInterpolation sqlInterpolation: SQLInterpolation) { - self.init(literal: SQLLiteral(stringInterpolation: sqlInterpolation)) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/SchedulingWatchdog.swift b/Example/Pods/GRDB.swift/GRDB/Core/SchedulingWatchdog.swift deleted file mode 100755 index 9331863..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/SchedulingWatchdog.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Dispatch - -/// SchedulingWatchdog makes sure that databases connections are used on correct -/// dispatch queues, and warns the user with a fatal error whenever she misuses -/// a database connection. -/// -/// Generally speaking, each connection has its own dispatch queue. But it's not -/// enough: users need to use two database connections at the same time: -/// https://github.com/groue/GRDB.swift/issues/55. To support this use case, a -/// single dispatch queue can be temporarily shared by two or more connections. -/// -/// - SchedulingWatchdog.makeSerializedQueue(allowingDatabase:) creates a -/// dispatch queue that allows one database. -/// -/// It does so by registering one instance of SchedulingWatchdog as a specific -/// of the dispatch queue, a SchedulingWatchdog that allows that database only. -/// -/// Later on, the queue can be shared by several databases with the method -/// inheritingAllowedDatabases(from:execute:). See SerializedDatabase.sync() -/// for an example. -/// -/// - preconditionValidQueue() crashes whenever a database is used in an invalid -/// dispatch queue. -final class SchedulingWatchdog { - private static let watchDogKey = DispatchSpecificKey() - private(set) var allowedDatabases: [Database] - var databaseObservationBroker: DatabaseObservationBroker? - - private init(allowedDatabase database: Database) { - allowedDatabases = [database] - } - - static func allowDatabase(_ database: Database, onQueue queue: DispatchQueue) { - precondition(queue.getSpecific(key: watchDogKey) == nil) - let watchdog = SchedulingWatchdog(allowedDatabase: database) - queue.setSpecific(key: watchDogKey, value: watchdog) - } - - func inheritingAllowedDatabases(from other: SchedulingWatchdog, execute body: () throws -> T) rethrows -> T { - let backup = allowedDatabases - allowedDatabases.append(contentsOf: other.allowedDatabases) - defer { allowedDatabases = backup } - return try body() - } - - static func preconditionValidQueue(_ db: Database, _ message: @autoclosure() -> String = "Database was not used on the correct thread.", file: StaticString = #file, line: UInt = #line) { - GRDBPrecondition(current?.allows(db) ?? false, message(), file: file, line: line) - } - - static var current: SchedulingWatchdog? { - return DispatchQueue.getSpecific(key: watchDogKey) - } - - func allows(_ db: Database) -> Bool { - return allowedDatabases.contains { $0 === db } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/SerializedDatabase.swift b/Example/Pods/GRDB.swift/GRDB/Core/SerializedDatabase.swift deleted file mode 100755 index 7fabe2e..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/SerializedDatabase.swift +++ /dev/null @@ -1,204 +0,0 @@ -import Foundation - -/// A class that serializes accesses to a database. -final class SerializedDatabase { - /// The database connection - private let db: Database - - /// The database configuration - var configuration: Configuration { - return db.configuration - } - - /// The path to the database file - var path: String - - /// The dispatch queue - private let queue: DispatchQueue - - init(path: String, configuration: Configuration = Configuration(), schemaCache: DatabaseSchemaCache, defaultLabel: String, purpose: String? = nil) throws { - // According to https://www.sqlite.org/threadsafe.html - // - // > SQLite support three different threading modes: - // > - // > 1. Multi-thread. In this mode, SQLite can be safely used by - // > multiple threads provided that no single database connection is - // > used simultaneously in two or more threads. - // > - // > 2. Serialized. In serialized mode, SQLite can be safely used by - // > multiple threads with no restriction. - // > - // > [...] - // > - // > The default mode is serialized. - // - // Since our database connection is only used via our serial dispatch - // queue, there is no purpose using the default serialized mode. - var config = configuration - config.threadingMode = .multiThread - - self.path = path - self.db = try Database(path: path, configuration: config, schemaCache: schemaCache) - self.queue = configuration.makeDispatchQueue(defaultLabel: defaultLabel, purpose: purpose) - SchedulingWatchdog.allowDatabase(db, onQueue: queue) - try queue.sync { - do { - try db.setup() - } catch { - db.close() - throw error - } - } - } - - deinit { - // Database may be deallocated in its own queue: allow reentrancy - reentrantSync { db in - db.close() - } - } - - /// Synchronously executes a block the serialized dispatch queue, and - /// returns its result. - /// - /// This method is *not* reentrant. - func sync(_ block: (Database) throws -> T) rethrows -> T { - // Three different cases: - // - // 1. A database is invoked from some queue like the main queue: - // - // serializedDatabase.sync { db in // <-- we're here - // } - // - // 2. A database is invoked in a reentrant way: - // - // serializedDatabase.sync { db in - // serializedDatabase.sync { db in // <-- we're here - // } - // } - // - // 3. A database in invoked from another database: - // - // serializedDatabase1.sync { db1 in - // serializedDatabase2.sync { db2 in // <-- we're here - // } - // } - - guard let watchdog = SchedulingWatchdog.current else { - // Case 1 - return try queue.sync { - defer { preconditionNoUnsafeTransactionLeft(db) } - return try block(db) - } - } - - // Case 2 is forbidden. - GRDBPrecondition(!watchdog.allows(db), "Database methods are not reentrant.") - - // Case 3 - return try queue.sync { - try SchedulingWatchdog.current!.inheritingAllowedDatabases(from: watchdog) { - defer { preconditionNoUnsafeTransactionLeft(db) } - return try block(db) - } - } - } - - /// Synchronously executes a block the serialized dispatch queue, and - /// returns its result. - /// - /// This method is reentrant. - func reentrantSync(_ block: (Database) throws -> T) rethrows -> T { - // Three different cases: - // - // 1. A database is invoked from some queue like the main queue: - // - // serializedDatabase.reentrantSync { db in // <-- we're here - // } - // - // 2. A database is invoked in a reentrant way: - // - // serializedDatabase.reentrantSync { db in - // serializedDatabase.reentrantSync { db in // <-- we're here - // } - // } - // - // 3. A database in invoked from another database: - // - // serializedDatabase1.reentrantSync { db1 in - // serializedDatabase2.reentrantSync { db2 in // <-- we're here - // } - // } - - guard let watchdog = SchedulingWatchdog.current else { - // Case 1 - return try queue.sync { - // Since we are reentrant, a transaction may already be opened. - // In this case, don't check for unsafe transaction at the end. - if db.isInsideTransaction { - return try block(db) - } else { - defer { preconditionNoUnsafeTransactionLeft(db) } - return try block(db) - } - } - } - - // Case 2 - if watchdog.allows(db) { - // Since we are reentrant, a transaction may already be opened. - // In this case, don't check for unsafe transaction at the end. - if db.isInsideTransaction { - return try block(db) - } else { - defer { preconditionNoUnsafeTransactionLeft(db) } - return try block(db) - } - } - - // Case 3 - return try queue.sync { - try SchedulingWatchdog.current!.inheritingAllowedDatabases(from: watchdog) { - // Since we are reentrant, a transaction may already be opened. - // In this case, don't check for unsafe transaction at the end. - if db.isInsideTransaction { - return try block(db) - } else { - defer { preconditionNoUnsafeTransactionLeft(db) } - return try block(db) - } - } - } - } - - /// Asynchronously executes a block in the serialized dispatch queue. - func async(_ block: @escaping (Database) -> Void) { - queue.async { - block(self.db) - self.preconditionNoUnsafeTransactionLeft(self.db) - } - } - - /// Returns true if any only if the current dispatch queue is valid. - var onValidQueue: Bool { - return SchedulingWatchdog.current?.allows(db) ?? false - } - - /// Executes the block in the current queue. - /// - /// - precondition: the current dispatch queue is valid. - func execute(_ block: (Database) throws -> T) rethrows -> T { - preconditionValidQueue() - return try block(db) - } - - /// Fatal error if current dispatch queue is not valid. - func preconditionValidQueue(_ message: @autoclosure() -> String = "Database was not used on the correct thread.", file: StaticString = #file, line: UInt = #line) { - SchedulingWatchdog.preconditionValidQueue(db, message(), file: file, line: line) - } - - /// Fatal error if a transaction has been left opened. - private func preconditionNoUnsafeTransactionLeft(_ db: Database, _ message: @autoclosure() -> String = "A transaction has been left opened at the end of a database access", file: StaticString = #file, line: UInt = #line) { - GRDBPrecondition(configuration.allowsUnsafeTransactions || !db.isInsideTransaction, message(), file: file, line: line) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Statement.swift b/Example/Pods/GRDB.swift/GRDB/Core/Statement.swift deleted file mode 100755 index 73a76d4..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Statement.swift +++ /dev/null @@ -1,914 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// A raw SQLite statement, suitable for the SQLite C API. -public typealias SQLiteStatement = OpaquePointer - -extension CharacterSet { - /// Statements are separated by semicolons and white spaces - static let sqlStatementSeparators = CharacterSet(charactersIn: ";").union(.whitespacesAndNewlines) -} - -/// A statement represents an SQL query. -/// -/// It is the base class of UpdateStatement that executes *update statements*, -/// and SelectStatement that fetches rows. -public class Statement { - - /// The raw SQLite statement, suitable for the SQLite C API. - public let sqliteStatement: SQLiteStatement - - /// The SQL query - public var sql: String { - // trim white space and semicolumn for homogeneous output - return String(cString: sqlite3_sql(sqliteStatement)) - .trimmingCharacters(in: .sqlStatementSeparators) - } - - unowned let database: Database - - /// Creates a prepared statement. Returns nil if the compiled string is - /// blank or empty. - /// - /// - parameter database: A database connection. - /// - parameter statementStart: A pointer to a UTF-8 encoded C string - /// containing SQL. - /// - parameter statementEnd: Upon success, the pointer to the next - /// statement in the C string. - /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from - /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html) - /// - throws: DatabaseError in case of compilation error. - required init?( - database: Database, - statementStart: UnsafePointer, - statementEnd: UnsafeMutablePointer?>, - prepFlags: Int32, - authorizer: StatementCompilationAuthorizer) throws - { - SchedulingWatchdog.preconditionValidQueue(database) - - var sqliteStatement: SQLiteStatement? = nil - // sqlite3_prepare_v3 was introduced in SQLite 3.20.0 http://www.sqlite.org/changes.html#version_3_20 - #if GRDBCUSTOMSQLITE || GRDBCIPHER - let code = sqlite3_prepare_v3(database.sqliteConnection, statementStart, -1, UInt32(bitPattern: prepFlags), &sqliteStatement, statementEnd) - #else - let code: Int32 - if #available(iOS 12.0, OSX 10.14, watchOS 5.0, *) { - code = sqlite3_prepare_v3(database.sqliteConnection, statementStart, -1, UInt32(bitPattern: prepFlags), &sqliteStatement, statementEnd) - } else { - code = sqlite3_prepare_v2(database.sqliteConnection, statementStart, -1, &sqliteStatement, statementEnd) - } - #endif - - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: String(cString: statementStart)) - } - - guard let statement = sqliteStatement else { - return nil - } - - self.database = database - self.sqliteStatement = statement - } - - deinit { - sqlite3_finalize(sqliteStatement) - } - - final func reset() throws { - SchedulingWatchdog.preconditionValidQueue(database) - let code = sqlite3_reset(sqliteStatement) - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql) - } - } - - - // MARK: Arguments - - var argumentsNeedValidation = true - var _arguments = StatementArguments() - - lazy var sqliteArgumentCount: Int = { - Int(sqlite3_bind_parameter_count(self.sqliteStatement)) - }() - - // Returns ["id", nil", "name"] for "INSERT INTO table VALUES (:id, ?, :name)" - fileprivate lazy var sqliteArgumentNames: [String?] = { - return (0..=5.0) - code = data.withUnsafeBytes { - sqlite3_bind_blob(sqliteStatement, index, $0.baseAddress, Int32($0.count), SQLITE_TRANSIENT) - } - #else - code = data.withUnsafeBytes { - sqlite3_bind_blob(sqliteStatement, index, $0, Int32(data.count), SQLITE_TRANSIENT) - } - #endif - } - - // It looks like sqlite3_bind_xxx() functions do not access the file system. - // They should thus succeed, unless a GRDB bug: there is no point throwing any error. - guard code == SQLITE_OK else { - fatalError(DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql).description) - } - } - - // Don't make this one public unless we keep the arguments property in sync. - private func clearBindings() { - // It looks like sqlite3_clear_bindings() does not access the file system. - // This function call should thus succeed, unless a GRDB bug: there is - // no point throwing any error. - let code = sqlite3_clear_bindings(sqliteStatement) - guard code == SQLITE_OK else { - fatalError(DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql).description) - } - } - - fileprivate func prepare(withArguments arguments: StatementArguments?) { - // Force arguments validity: it is a programmer error to provide - // arguments that do not match the statement. - if let arguments = arguments { - try! setArgumentsWithValidation(arguments) - } else if argumentsNeedValidation { - try! validate(arguments: self.arguments) - } - } -} - -// MARK: - Statement Preparation - -/// A common protocol for UpdateStatement and SelectStatement, only used as -/// support for SelectStatement.prepare(...) and UpdateStatement.prepare(...). -protocol StatementProtocol { } -extension Statement: StatementProtocol { } -extension StatementProtocol where Self: Statement { - // Static method instead of an initializer because initializer can't run - // inside `sqlCodeUnits.withUnsafeBufferPointer`. - static func prepare(sql: String, prepFlags: Int32, in database: Database) throws -> Self { - let authorizer = StatementCompilationAuthorizer() - database.authorizer = authorizer - defer { database.authorizer = nil } - - return try sql.utf8CString.withUnsafeBufferPointer { buffer in - let statementStart = buffer.baseAddress! - var statementEnd: UnsafePointer? = nil - guard let statement = try self.init( - database: database, - statementStart: statementStart, - statementEnd: &statementEnd, - prepFlags: prepFlags, - authorizer: authorizer) else - { - throw DatabaseError( - resultCode: .SQLITE_ERROR, - message: "empty statement", - sql: sql, - arguments: nil) - } - - let remainingSQL = String(cString: statementEnd!).trimmingCharacters(in: .sqlStatementSeparators) - guard remainingSQL.isEmpty else { - throw DatabaseError( - resultCode: .SQLITE_MISUSE, - message: "Multiple statements found. To execute multiple statements, use Database.execute(sql:) instead.", - sql: sql, - arguments: nil) - } - - return statement - } - } -} - -// MARK: - SelectStatement - -/// A subclass of Statement that fetches database rows. -/// -/// You create SelectStatement with the Database.makeSelectStatement() method: -/// -/// try dbQueue.read { db in -/// let statement = try db.makeSelectStatement(sql: "SELECT COUNT(*) FROM player WHERE score > ?") -/// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])! -/// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])! -/// } -public final class SelectStatement : Statement { - /// The database region that the statement looks into. - public private(set) var databaseRegion = DatabaseRegion() - - /// Creates a prepared statement. Returns nil if the compiled string is - /// blank or empty. - /// - /// - parameter database: A database connection. - /// - parameter statementStart: A pointer to a UTF-8 encoded C string - /// containing SQL. - /// - parameter statementEnd: Upon success, the pointer to the next - /// statement in the C string. - /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from - /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html) - /// - authorizer: A StatementCompilationAuthorizer - /// - throws: DatabaseError in case of compilation error. - required init?( - database: Database, - statementStart: UnsafePointer, - statementEnd: UnsafeMutablePointer?>, - prepFlags: Int32, - authorizer: StatementCompilationAuthorizer) throws - { - try super.init( - database: database, - statementStart: statementStart, - statementEnd: statementEnd, - prepFlags: prepFlags, - authorizer: authorizer) - - GRDBPrecondition(authorizer.invalidatesDatabaseSchemaCache == false, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.") - GRDBPrecondition(authorizer.transactionEffect == nil, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.") - - self.databaseRegion = authorizer.databaseRegion - } - - /// The number of columns in the resulting rows. - public var columnCount: Int { - return Int(sqlite3_column_count(self.sqliteStatement)) - } - - /// The column names, ordered from left to right. - public lazy var columnNames: [String] = { - let sqliteStatement = self.sqliteStatement - return (0.. Int? { - return columnIndexes[name.lowercased()] - } - - /// Creates a cursor over the statement which does not produce any - /// value. Each call to the next() cursor method calls the sqlite3_step() - /// C function. - func makeCursor(arguments: StatementArguments? = nil) -> StatementCursor { - return StatementCursor(statement: self, arguments: arguments) - } - - /// Utility function for cursors - func reset(withArguments arguments: StatementArguments? = nil) { - prepare(withArguments: arguments) - try! reset() - } - - /// Utility function for cursors - @usableFromInline - func didFail(withResultCode resultCode: Int32) throws -> Never { - database.selectStatementDidFail(self) - throw DatabaseError( - resultCode: resultCode, - message: database.lastErrorMessage, - sql: sql, - arguments: arguments) - - } -} - -/// A cursor that iterates a database statement without producing any value. -/// Each call to the next() cursor method calls the sqlite3_step() C function. -/// -/// For example: -/// -/// try dbQueue.read { db in -/// let statement = db.makeSelectStatement(sql: "SELECT performSideEffect()") -/// let cursor = statement.makeCursor() -/// try cursor.next() -/// } -final class StatementCursor: Cursor { - let _statement: SelectStatement - let _sqliteStatement: SQLiteStatement - var _done = false - - // Use SelectStatement.makeCursor() instead - init(statement: SelectStatement, arguments: StatementArguments? = nil) { - _statement = statement - _sqliteStatement = statement.sqliteStatement - _statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? _statement.reset() - } - - /// :nodoc: - @inlinable - func next() throws -> Void? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return .some(()) - case let code: - try _statement.didFail(withResultCode: code) - } - } -} - - -// MARK: - UpdateStatement - -/// A subclass of Statement that executes SQL queries. -/// -/// You create UpdateStatement with the Database.makeUpdateStatement() method: -/// -/// try dbQueue.inTransaction { db in -/// let statement = try db.makeUpdateStatement(sql: "INSERT INTO player (name) VALUES (?)") -/// try statement.execute(arguments: ["Arthur"]) -/// try statement.execute(arguments: ["Barbara"]) -/// return .commit -/// } -public final class UpdateStatement : Statement { - enum TransactionEffect { - case beginTransaction - case commitTransaction - case rollbackTransaction - case beginSavepoint(String) - case releaseSavepoint(String) - case rollbackSavepoint(String) - } - - /// If true, the database schema cache gets invalidated after this statement - /// is executed. - private(set) var invalidatesDatabaseSchemaCache: Bool = false - - private(set) var transactionEffect: TransactionEffect? - private(set) var databaseEventKinds: [DatabaseEventKind] = [] - - /// Creates a prepared statement. Returns nil if the compiled string is - /// blank or empty. - /// - /// - parameter database: A database connection. - /// - parameter statementStart: A pointer to a UTF-8 encoded C string - /// containing SQL. - /// - parameter statementEnd: Upon success, the pointer to the next - /// statement in the C string. - /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from - /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html) - /// - authorizer: A StatementCompilationAuthorizer - /// - throws: DatabaseError in case of compilation error. - required init?( - database: Database, - statementStart: UnsafePointer, - statementEnd: UnsafeMutablePointer?>, - prepFlags: Int32, - authorizer: StatementCompilationAuthorizer) throws - { - try super.init( - database: database, - statementStart: statementStart, - statementEnd: statementEnd, - prepFlags: prepFlags, - authorizer: authorizer) - self.invalidatesDatabaseSchemaCache = authorizer.invalidatesDatabaseSchemaCache - self.transactionEffect = authorizer.transactionEffect - self.databaseEventKinds = authorizer.databaseEventKinds - } - - /// Executes the SQL query. - /// - /// - parameter arguments: Optional statement arguments. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func execute(arguments: StatementArguments? = nil) throws { - SchedulingWatchdog.preconditionValidQueue(database) - prepare(withArguments: arguments) - try reset() - database.updateStatementWillExecute(self) - - while true { - switch sqlite3_step(sqliteStatement) { - case SQLITE_ROW: - // The statement did return a row, and the user ignores the - // content of this row: - // - // try db.execute(sql: "SELECT ...") - // - // That's OK: maybe the selected rows perform side effects. - // For example: - // - // try db.execute(sql: "SELECT sqlcipher_export(...)") - // - // Or maybe the user doesn't know that the executed statement - // return rows (https://github.com/groue/GRDB.swift/issues/15); - // - // try db.execute(sql: "PRAGMA journal_mode=WAL") - // - // It is thus important that we consume *all* rows. - continue - - case SQLITE_DONE: - try database.updateStatementDidExecute(self) - return - - case let code: - try database.updateStatementDidFail(self) - throw DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql, arguments: self.arguments) // Error uses self.arguments, not the optional arguments parameter. - } - } - } -} - -// MARK: - StatementArguments - -/// StatementArguments provide values to argument placeholders in raw -/// SQL queries. -/// -/// Placeholders can take several forms (see https://www.sqlite.org/lang_expr.html#varparam -/// for more information): -/// -/// - `?NNN` (e.g. `?2`): the NNN-th argument (starts at 1) -/// - `?`: the N-th argument, where N is one greater than the largest argument -/// number already assigned -/// - `:AAAA` (e.g. `:name`): named argument -/// - `@AAAA` (e.g. `@name`): named argument -/// - `$AAAA` (e.g. `$name`): named argument -/// -/// ## Positional Arguments -/// -/// To fill question marks placeholders, feed StatementArguments with an array: -/// -/// db.execute( -/// sql: "INSERT ... (?, ?)", -/// arguments: StatementArguments(["Arthur", 41])) -/// -/// // Array literals are automatically converted: -/// db.execute( -/// sql: "INSERT ... (?, ?)", -/// arguments: ["Arthur", 41]) -/// -/// ## Named Arguments -/// -/// To fill named arguments, feed StatementArguments with a dictionary: -/// -/// db.execute( -/// sql: "INSERT ... (:name, :score)", -/// arguments: StatementArguments(["name": "Arthur", "score": 41])) -/// -/// // Dictionary literals are automatically converted: -/// db.execute( -/// sql: "INSERT ... (:name, :score)", -/// arguments: ["name": "Arthur", "score": 41]) -/// -/// ## Concatenating Arguments -/// -/// Several arguments can be concatenated and mixed with the -/// `append(contentsOf:)` method and the `+`, `&+`, `+=` operators: -/// -/// var arguments: StatementArguments = ["Arthur"] -/// arguments += [41] -/// db.execute(sql: "INSERT ... (?, ?)", arguments: arguments) -/// -/// `+` and `+=` operators consider that overriding named arguments is a -/// programmer error: -/// -/// var arguments: StatementArguments = ["name": "Arthur"] -/// arguments += ["name": "Barbara"] -/// // fatal error: already defined statement argument: name -/// -/// `&+` and `append(contentsOf:)` allow overriding named arguments: -/// -/// var arguments: StatementArguments = ["name": "Arthur"] -/// arguments = arguments &+ ["name": "Barbara"] -/// print(arguments) -/// // Prints ["name": "Barbara"] -/// -/// ## Mixed Arguments -/// -/// It is possible to mix named and positional arguments. Yet this is usually -/// confusing, and it is best to avoid this practice: -/// -/// let sql = "SELECT ?2 AS two, :foo AS foo, ?1 AS one, :foo AS foo2, :bar AS bar" -/// var arguments: StatementArguments = [1, 2, "bar"] + ["foo": "foo"] -/// let row = try Row.fetchOne(db, sql: sql, arguments: arguments)! -/// print(row) -/// // Prints [two:2 foo:"foo" one:1 foo2:"foo" bar:"bar"] -/// -/// Mixed arguments exist as a support for requests like the following: -/// -/// let players = try Player -/// .filter(sql: "team = :team", arguments: ["team": "Blue"]) -/// .filter(sql: "score > ?", arguments: [1000]) -/// .fetchAll(db) -public struct StatementArguments: CustomStringConvertible, Equatable, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral { - private(set) var values: [DatabaseValue] = [] - private(set) var namedValues: [String: DatabaseValue] = [:] - - public var isEmpty: Bool { - return values.isEmpty && namedValues.isEmpty - } - - - // MARK: Empty Arguments - - /// Creates empty StatementArguments. - public init() { - } - - // MARK: Positional Arguments - - /// Creates statement arguments from a sequence of optional values. - /// - /// let values: [DatabaseValueConvertible?] = ["foo", 1, nil] - /// db.execute(sql: "INSERT ... (?,?,?)", arguments: StatementArguments(values)) - /// - /// - parameter sequence: A sequence of DatabaseValueConvertible values. - /// - returns: A StatementArguments. - public init(_ sequence: Sequence) where Sequence.Element == DatabaseValueConvertible? { - values = sequence.map { $0?.databaseValue ?? .null } - } - - /// Creates statement arguments from a sequence of optional values. - /// - /// let values: [String] = ["foo", "bar"] - /// db.execute(sql: "INSERT ... (?,?)", arguments: StatementArguments(values)) - /// - /// - parameter sequence: A sequence of DatabaseValueConvertible values. - /// - returns: A StatementArguments. - public init(_ sequence: Sequence) where Sequence.Element: DatabaseValueConvertible { - values = sequence.map { $0.databaseValue } - } - - /// Creates statement arguments from any array. The result is nil unless all - /// array elements adopt DatabaseValueConvertible. - /// - /// - parameter array: An array - /// - returns: A StatementArguments. - public init?(_ array: [Any]) { - var values = [DatabaseValueConvertible?]() - for value in array { - guard let dbValue = DatabaseValue(value: value) else { - return nil - } - values.append(dbValue) - } - self.init(values) - } - - - // MARK: Named Arguments - - /// Creates statement arguments from a sequence of (key, value) dictionary, - /// such as a dictionary. - /// - /// let values: [String: DatabaseValueConvertible?] = ["firstName": nil, "lastName": "Miller"] - /// db.execute(sql: "INSERT ... (:firstName, :lastName)", arguments: StatementArguments(values)) - /// - /// - parameter sequence: A sequence of (key, value) pairs - /// - returns: A StatementArguments. - public init(_ dictionary: [String: DatabaseValueConvertible?]) { - namedValues = dictionary.mapValues { $0?.databaseValue ?? .null } - } - - /// Creates statement arguments from a sequence of (key, value) pairs, such - /// as a dictionary. - /// - /// let values: [String: DatabaseValueConvertible?] = ["firstName": nil, "lastName": "Miller"] - /// db.execute(sql: "INSERT ... (:firstName, :lastName)", arguments: StatementArguments(values)) - /// - /// - parameter sequence: A sequence of (key, value) pairs - /// - returns: A StatementArguments. - public init(_ sequence: Sequence) where Sequence.Element == (String, DatabaseValueConvertible?) { - namedValues = Dictionary(uniqueKeysWithValues: sequence.map { ($0.0, $0.1?.databaseValue ?? .null) }) - } - - /// Creates statement arguments from [AnyHashable: Any]. - /// - /// The result is nil unless all dictionary keys are strings, and values - /// adopt DatabaseValueConvertible. - /// - /// - parameter dictionary: A dictionary. - /// - returns: A StatementArguments. - public init?(_ dictionary: [AnyHashable: Any]) { - var initDictionary = [String: DatabaseValueConvertible?]() - for (key, value) in dictionary { - guard let columnName = key as? String else { - return nil - } - guard let dbValue = DatabaseValue(value: value) else { - return nil - } - initDictionary[columnName] = dbValue - } - self.init(initDictionary) - } - - - // MARK: Adding arguments - - /// Extends statement arguments with other arguments. - /// - /// Positional arguments (provided as arrays) are concatenated: - /// - /// var arguments: StatementArguments = [1] - /// arguments.append(contentsOf: [2, 3]) - /// print(arguments) - /// // Prints [1, 2, 3] - /// - /// Named arguments (provided as dictionaries) are updated: - /// - /// var arguments: StatementArguments = ["foo": 1] - /// arguments.append(contentsOf: ["bar": 2]) - /// print(arguments) - /// // Prints ["foo": 1, "bar": 2] - /// - /// Arguments that were replaced, if any, are returned: - /// - /// var arguments: StatementArguments = ["foo": 1, "bar": 2] - /// let replacedValues = arguments.append(contentsOf: ["foo": 3]) - /// print(arguments) - /// // Prints ["foo": 3, "bar": 2] - /// print(replacedValues) - /// // Prints ["foo": 1] - /// - /// You can mix named and positional arguments (see documentation of - /// the StatementArguments type for more information about mixed arguments): - /// - /// var arguments: StatementArguments = ["foo": 1] - /// arguments.append(contentsOf: [2, 3]) - /// print(arguments) - /// // Prints ["foo": 1, 2, 3] - public mutating func append(contentsOf arguments: StatementArguments) -> [String: DatabaseValue] { - var replacedValues: [String: DatabaseValue] = [:] - values.append(contentsOf: arguments.values) - for (name, value) in arguments.namedValues { - if let replacedValue = namedValues.updateValue(value, forKey: name) { - replacedValues[name] = replacedValue - } - } - return replacedValues - } - - /// Creates a new StatementArguments by extending the left-hand size - /// arguments with the right-hand side arguments. - /// - /// Positional arguments (provided as arrays) are concatenated: - /// - /// let arguments: StatementArguments = [1] + [2, 3] - /// print(arguments) - /// // Prints [1, 2, 3] - /// - /// Named arguments (provided as dictionaries) are updated: - /// - /// let arguments: StatementArguments = ["foo": 1] + ["bar": 2] - /// print(arguments) - /// // Prints ["foo": 1, "bar": 2] - /// - /// You can mix named and positional arguments (see documentation of - /// the StatementArguments type for more information about mixed arguments): - /// - /// let arguments: StatementArguments = ["foo": 1] + [2, 3] - /// print(arguments) - /// // Prints ["foo": 1, 2, 3] - /// - /// If the arguments on the right-hand side has named parameters that are - /// already defined on the left, a fatal error is raised: - /// - /// let arguments: StatementArguments = ["foo": 1] + ["foo": 2] - /// // fatal error: already defined statement argument: foo - /// - /// This fatal error can be avoided with the &+ operator, or the - /// append(contentsOf:) method. - public static func + (lhs: StatementArguments, rhs: StatementArguments) -> StatementArguments { - var lhs = lhs - lhs += rhs - return lhs - } - - /// Creates a new StatementArguments by extending the left-hand size - /// arguments with the right-hand side arguments. - /// - /// Positional arguments (provided as arrays) are concatenated: - /// - /// let arguments: StatementArguments = [1] &+ [2, 3] - /// print(arguments) - /// // Prints [1, 2, 3] - /// - /// Named arguments (provided as dictionaries) are updated: - /// - /// let arguments: StatementArguments = ["foo": 1] &+ ["bar": 2] - /// print(arguments) - /// // Prints ["foo": 1, "bar": 2] - /// - /// You can mix named and positional arguments (see documentation of - /// the StatementArguments type for more information about mixed arguments): - /// - /// let arguments: StatementArguments = ["foo": 1] &+ [2, 3] - /// print(arguments) - /// // Prints ["foo": 1, 2, 3] - /// - /// If a named arguments is defined in both arguments, the right-hand - /// side wins: - /// - /// let arguments: StatementArguments = ["foo": 1] &+ ["foo": 2] - /// print(arguments) - /// // Prints ["foo": 2] - public static func &+ (lhs: StatementArguments, rhs: StatementArguments) -> StatementArguments { - var lhs = lhs - _ = lhs.append(contentsOf: rhs) - return lhs - } - - /// Extends the left-hand size arguments with the right-hand side arguments. - /// - /// Positional arguments (provided as arrays) are concatenated: - /// - /// var arguments: StatementArguments = [1] - /// arguments += [2, 3] - /// print(arguments) - /// // Prints [1, 2, 3] - /// - /// Named arguments (provided as dictionaries) are updated: - /// - /// var arguments: StatementArguments = ["foo": 1] - /// arguments += ["bar": 2] - /// print(arguments) - /// // Prints ["foo": 1, "bar": 2] - /// - /// You can mix named and positional arguments (see documentation of - /// the StatementArguments type for more information about mixed arguments): - /// - /// var arguments: StatementArguments = ["foo": 1] - /// arguments.append(contentsOf: [2, 3]) - /// print(arguments) - /// // Prints ["foo": 1, 2, 3] - /// - /// If the arguments on the right-hand side has named parameters that are - /// already defined on the left, a fatal error is raised: - /// - /// var arguments: StatementArguments = ["foo": 1] - /// arguments += ["foo": 2] - /// // fatal error: already defined statement argument: foo - /// - /// This fatal error can be avoided with the &+ operator, or the - /// append(contentsOf:) method. - public static func += (lhs: inout StatementArguments, rhs: StatementArguments) { - let replacedValues = lhs.append(contentsOf: rhs) - GRDBPrecondition(replacedValues.isEmpty, "already defined statement argument: \(replacedValues.keys.joined(separator: ", "))") - } - - - // MARK: Not Public - - mutating func extractBindings(forStatement statement: Statement, allowingRemainingValues: Bool) throws -> [DatabaseValue] { - let initialValuesCount = values.count - let bindings = try statement.sqliteArgumentNames.map { argumentName -> DatabaseValue in - if let argumentName = argumentName { - if let dbValue = namedValues[argumentName] { - return dbValue - } else if values.isEmpty { - throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "missing statement argument: \(argumentName)", sql: statement.sql, arguments: nil) - } else { - return values.removeFirst() - } - } else { - if values.isEmpty { - throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "wrong number of statement arguments: \(initialValuesCount)", sql: statement.sql, arguments: nil) - } else { - return values.removeFirst() - } - } - } - if !allowingRemainingValues && !values.isEmpty { - throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "wrong number of statement arguments: \(initialValuesCount)", sql: statement.sql, arguments: nil) - } - return bindings - } -} - -// ExpressibleByArrayLiteral -extension StatementArguments { - /// Returns a StatementArguments from an array literal: - /// - /// let arguments: StatementArguments = ["Arthur", 41] - /// try db.execute( - /// sql: "INSERT INTO player (name, score) VALUES (?, ?)" - /// arguments: arguments) - public init(arrayLiteral elements: DatabaseValueConvertible?...) { - self.init(elements) - } -} - -// ExpressibleByDictionaryLiteral -extension StatementArguments { - /// Returns a StatementArguments from a dictionary literal: - /// - /// let arguments: StatementArguments = ["name": "Arthur", "score": 41] - /// try db.execute( - /// sql: "INSERT INTO player (name, score) VALUES (:name, :score)" - /// arguments: arguments) - public init(dictionaryLiteral elements: (String, DatabaseValueConvertible?)...) { - self.init(elements) - } -} - -// CustomStringConvertible -extension StatementArguments { - /// :nodoc: - public var description: String { - let valuesDescriptions = values.map { $0.description } - let namedValuesDescriptions = namedValues.map { (key, value) -> String in - return "\(String(reflecting: key)): \(value)" - } - return "[" + (namedValuesDescriptions + valuesDescriptions).joined(separator: ", ") + "]" - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/StatementAuthorizer.swift b/Example/Pods/GRDB.swift/GRDB/Core/StatementAuthorizer.swift deleted file mode 100755 index e94bfa4..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/StatementAuthorizer.swift +++ /dev/null @@ -1,184 +0,0 @@ -#if os(Linux) - import Glibc -#endif -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// A protocol around sqlite3_set_authorizer -protocol StatementAuthorizer : class { - func authorize( - _ actionCode: Int32, - _ cString1: UnsafePointer?, - _ cString2: UnsafePointer?, - _ cString3: UnsafePointer?, - _ cString4: UnsafePointer?) - -> Int32 -} - -/// A class that gathers information about one statement during its compilation. -final class StatementCompilationAuthorizer : StatementAuthorizer { - /// What this statements reads - var databaseRegion = DatabaseRegion() - - /// What this statements writes - var databaseEventKinds: [DatabaseEventKind] = [] - - /// True if a statement alter the schema in a way that required schema cache - /// invalidation. For example, adding a column to a table invalidates the - /// schema cache, but not creating a table. - var invalidatesDatabaseSchemaCache = false - - /// Not nil if a statement is a BEGIN/COMMIT/ROLLBACK/RELEASE transaction or - /// savepoint statement. - var transactionEffect: UpdateStatement.TransactionEffect? - - private var isDropStatement = false - - func authorize( - _ actionCode: Int32, - _ cString1: UnsafePointer?, - _ cString2: UnsafePointer?, - _ cString3: UnsafePointer?, - _ cString4: UnsafePointer?) - -> Int32 - { - // print("StatementCompilationAuthorizer: \(actionCode) \([cString1, cString2, cString3, cString4].flatMap { $0.map({ String(cString: $0) }) })") - - switch actionCode { - case SQLITE_DROP_TABLE, SQLITE_DROP_VTABLE, SQLITE_DROP_TEMP_TABLE, - SQLITE_DROP_INDEX, SQLITE_DROP_TEMP_INDEX, - SQLITE_DROP_VIEW, SQLITE_DROP_TEMP_VIEW, - SQLITE_DROP_TRIGGER, SQLITE_DROP_TEMP_TRIGGER: - isDropStatement = true - invalidatesDatabaseSchemaCache = true - return SQLITE_OK - - case SQLITE_ALTER_TABLE, SQLITE_DETACH, - SQLITE_CREATE_INDEX, SQLITE_CREATE_TABLE, - SQLITE_CREATE_TEMP_INDEX, SQLITE_CREATE_TEMP_TABLE, - SQLITE_CREATE_TEMP_TRIGGER, SQLITE_CREATE_TEMP_VIEW, - SQLITE_CREATE_TRIGGER, SQLITE_CREATE_VIEW: - invalidatesDatabaseSchemaCache = true - return SQLITE_OK - - case SQLITE_READ: - guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK } - guard let columnName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK } - if columnName.isEmpty { - // SELECT COUNT(*) FROM table - databaseRegion.formUnion(DatabaseRegion(table: tableName)) - } else { - // SELECT column FROM table - databaseRegion.formUnion(DatabaseRegion(table: tableName, columns: [columnName])) - } - return SQLITE_OK - - case SQLITE_INSERT: - guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK } - databaseEventKinds.append(.insert(tableName: tableName)) - return SQLITE_OK - - case SQLITE_DELETE: - if isDropStatement { return SQLITE_OK } - guard let cString1 = cString1 else { return SQLITE_OK } - - // Deletions from sqlite_master and sqlite_temp_master are not like - // other deletions: the update hook does not notify them, and they - // are prevented when the truncate optimization is disabled. - // Let's authorize such deletions by returning SQLITE_OK: - guard strcmp(cString1, "sqlite_master") != 0 else { return SQLITE_OK } - guard strcmp(cString1, "sqlite_temp_master") != 0 else { return SQLITE_OK } - - // Now we prevent the truncate optimization so that transaction - // observers are notified of individual row deletions. - databaseEventKinds.append(.delete(tableName: String(cString: cString1))) - return SQLITE_IGNORE - - case SQLITE_UPDATE: - guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK } - guard let columnName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK } - insertUpdateEventKind(tableName: tableName, columnName: columnName) - return SQLITE_OK - - case SQLITE_TRANSACTION: - guard let cString1 = cString1 else { return SQLITE_OK } - if strcmp(cString1, "BEGIN") == 0 { - transactionEffect = .beginTransaction - } else if strcmp(cString1, "COMMIT") == 0 { - transactionEffect = .commitTransaction - } else if strcmp(cString1, "ROLLBACK") == 0 { - transactionEffect = .rollbackTransaction - } - return SQLITE_OK - - case SQLITE_SAVEPOINT: - guard let cString1 = cString1 else { return SQLITE_OK } - guard let name = cString2.map({ String(cString: $0) }) else { return SQLITE_OK } - if strcmp(cString1, "BEGIN") == 0 { - transactionEffect = .beginSavepoint(name) - } else if strcmp(cString1, "RELEASE") == 0 { - transactionEffect = .releaseSavepoint(name) - } else if strcmp(cString1, "ROLLBACK") == 0 { - transactionEffect = .rollbackSavepoint(name) - } - return SQLITE_OK - - case SQLITE_FUNCTION: - // Starting SQLite 3.19.0, `SELECT COUNT(*) FROM table` triggers - // an authorization callback for SQLITE_READ with an empty - // column: http://www.sqlite.org/changes.html#version_3_19_0 - // - // Before SQLite 3.19.0, `SELECT COUNT(*) FROM table` does not - // trigger any authorization callback that tells about the - // counted table: any use of the COUNT function makes the - // region undetermined (the full database). - guard sqlite3_libversion_number() < 3019000 else { return SQLITE_OK } - guard let cString2 = cString2 else { return SQLITE_OK } - if sqlite3_stricmp(cString2, "COUNT") == 0 { - databaseRegion = .fullDatabase - } - return SQLITE_OK - - default: - return SQLITE_OK - } - } - - func insertUpdateEventKind(tableName: String, columnName: String) { - for (index, eventKind) in databaseEventKinds.enumerated() { - if case .update(let t, let columnNames) = eventKind, t == tableName { - var columnNames = columnNames - columnNames.insert(columnName) - databaseEventKinds[index] = .update(tableName: tableName, columnNames: columnNames) - return - } - } - databaseEventKinds.append(.update(tableName: tableName, columnNames: [columnName])) - } -} - -/// This authorizer prevents the [truncate optimization](https://www.sqlite.org/lang_delete.html#truncateopt) -/// which makes transaction observers unable to observe individual deletions -/// when user runs `DELETE FROM t` statements. -// -/// Warning: to perform well, this authorizer must be used during statement -/// execution, not during statement compilation. -final class TruncateOptimizationBlocker : StatementAuthorizer { - func authorize( - _ actionCode: Int32, - _ cString1: UnsafePointer?, - _ cString2: UnsafePointer?, - _ cString3: UnsafePointer?, - _ cString4: UnsafePointer?) - -> Int32 - { - // print("TruncateOptimizationBlocker: \(actionCode) \([cString1, cString2, cString3, cString4].flatMap { $0.map({ String(cString: $0) }) })") - return (actionCode == SQLITE_DELETE) ? SQLITE_IGNORE : SQLITE_OK - } -} - diff --git a/Example/Pods/GRDB.swift/GRDB/Core/StatementColumnConvertible.swift b/Example/Pods/GRDB.swift/GRDB/Core/StatementColumnConvertible.swift deleted file mode 100755 index f0a03fa..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/StatementColumnConvertible.swift +++ /dev/null @@ -1,570 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// The StatementColumnConvertible protocol grants access to the low-level C -/// interface that extracts values from query results: -/// https://www.sqlite.org/c3ref/column_blob.html. It can bring performance -/// improvements. -/// -/// To use it, have a value type adopt both StatementColumnConvertible and -/// DatabaseValueConvertible. GRDB will then automatically apply the -/// optimization whenever direct access to SQLite is possible: -/// -/// let rows = Row.fetchCursor(db, sql: "SELECT ...") -/// while let row = try rows.next() { -/// let int: Int = row[0] // there -/// } -/// let ints = Int.fetchAll(db, sql: "SELECT ...") // there -/// struct Player { -/// init(row: Row) { -/// name = row["name"] // there -/// score = row["score"] // there -/// } -/// } -/// -/// StatementColumnConvertible is already adopted by all Swift integer types, -/// Float, Double, String, and Bool. -public protocol StatementColumnConvertible { - - /// Initializes a value from a raw SQLite statement pointer. - /// - /// For example, here is the how Int64 adopts StatementColumnConvertible: - /// - /// extension Int64: StatementColumnConvertible { - /// init(sqliteStatement: SQLiteStatement, index: Int32) { - /// self = sqlite3_column_int64(sqliteStatement, index) - /// } - /// } - /// - /// This initializer is never called for NULL database values: don't perform - /// any extra check. - /// - /// See https://www.sqlite.org/c3ref/column_blob.html for more information. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - init(sqliteStatement: SQLiteStatement, index: Int32) -} - -/// A cursor of database values extracted from a single column. -/// For example: -/// -/// try dbQueue.read { db in -/// let names: ColumnCursor = try String.fetchCursor(db, sql: "SELECT name FROM player") -/// while let name = names.next() { // String -/// print(name) -/// } -/// } -public final class FastDatabaseValueCursor : Cursor { - @usableFromInline let _statement: SelectStatement - @usableFromInline let _columnIndex: Int32 - @usableFromInline let _sqliteStatement: SQLiteStatement - @usableFromInline var _done = false - - init(statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { - _statement = statement - _sqliteStatement = statement.sqliteStatement - if let adapter = adapter { - // adapter may redefine the index of the leftmost column - _columnIndex = try Int32(adapter.baseColumnIndex(atIndex: 0, layout: statement)) - } else { - _columnIndex = 0 - } - _statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? _statement.reset() - } - - /// :nodoc: - @inlinable - public func next() throws -> Value? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return Value.fastDecode(from: _sqliteStatement, atUncheckedIndex: _columnIndex) - case let code: - try _statement.didFail(withResultCode: code) - } - } -} - -/// A cursor of optional database values extracted from a single column. -/// For example: -/// -/// try dbQueue.read { db in -/// let emails: NullableColumnCursor = try Optional.fetchCursor(db, sql: "SELECT email FROM player") -/// while let email = emails.next() { // String? -/// print(email ?? "") -/// } -/// } -public final class FastNullableDatabaseValueCursor : Cursor { - @usableFromInline let _statement: SelectStatement - @usableFromInline let _columnIndex: Int32 - @usableFromInline let _sqliteStatement: SQLiteStatement - @usableFromInline var _done = false - - init(statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { - _statement = statement - _sqliteStatement = statement.sqliteStatement - if let adapter = adapter { - // adapter may redefine the index of the leftmost column - _columnIndex = try Int32(adapter.baseColumnIndex(atIndex: 0, layout: statement)) - } else { - _columnIndex = 0 - } - _statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? _statement.reset() - } - - /// :nodoc: - @inlinable - public func next() throws -> Value?? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return Value.fastDecodeIfPresent(from: _sqliteStatement, atUncheckedIndex: _columnIndex) - case let code: - try _statement.didFail(withResultCode: code) - } - } -} - -/// Types that adopt both DatabaseValueConvertible and -/// StatementColumnConvertible can be efficiently initialized from -/// database values. -/// -/// See DatabaseValueConvertible for more information. -extension DatabaseValueConvertible where Self: StatementColumnConvertible { - - - // MARK: Fetching From SelectStatement - - /// Returns a cursor over values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try String.fetchCursor(statement) // Cursor of String - /// while let name = try names.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> FastDatabaseValueCursor { - return try FastDatabaseValueCursor(statement: statement, arguments: arguments, adapter: adapter) - } - - /// Returns an array of values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try String.fetchAll(statement) // [String] - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { - return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) - } - - /// Returns a single value fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let name = try String.fetchOne(statement) // String? - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { - // fetchOne returns nil if there is no row, or if there is a row with a null value - let cursor = try FastNullableDatabaseValueCursor(statement: statement, arguments: arguments, adapter: adapter) - return try cursor.next() ?? nil - } -} - -extension DatabaseValueConvertible where Self: StatementColumnConvertible { - - // MARK: Fetching From SQL - - /// Returns a cursor over values fetched from an SQL query. - /// - /// let names = try String.fetchCursor(db, sql: "SELECT name FROM ...") // Cursor of String - /// while let name = try names.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> FastDatabaseValueCursor { - return try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns an array of values fetched from an SQL query. - /// - /// let names = try String.fetchAll(db, sql: "SELECT name FROM ...") // [String] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> [Self] { - return try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns a single value fetched from an SQL query. - /// - /// let name = try String.fetchOne(db, sql: "SELECT name FROM ...") // String? - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> Self? { - return try fetchOne(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } -} - -extension DatabaseValueConvertible where Self: StatementColumnConvertible { - - // MARK: Fetching From FetchRequest - - /// Returns a cursor over values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try String.fetchCursor(db, request) // Cursor of String - /// while let name = try names.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: R) throws -> FastDatabaseValueCursor { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try String.fetchAll(db, request) // [String] - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: R) throws -> [Self] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchAll(statement, adapter: adapter) - } - - /// Returns a single value fetched from a fetch request. - /// - /// let request = Player.filter(key: 1).select(Column("name")) - /// let name = try String.fetchOne(db, request) // String? - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, _ request: R) throws -> Self? { - let (statement, adapter) = try request.prepare(db, forSingleResult: true) - return try fetchOne(statement, adapter: adapter) - } -} - -extension FetchRequest where RowDecoder: DatabaseValueConvertible & StatementColumnConvertible { - - // MARK: Fetching Values - - /// A cursor over fetched values. - /// - /// let request: ... // Some FetchRequest that fetches String - /// let strings = try request.fetchCursor(db) // Cursor of String - /// while let string = try strings.next() { // String - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> FastDatabaseValueCursor { - return try RowDecoder.fetchCursor(db, self) - } - - /// An array of fetched values. - /// - /// let request: ... // Some FetchRequest that fetches String - /// let strings = try request.fetchAll(db) // [String] - /// - /// - parameter db: A database connection. - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [RowDecoder] { - return try RowDecoder.fetchAll(db, self) - } - - /// The first fetched value. - /// - /// The result is nil if the request returns no row, or if no value can be - /// extracted from the first row. - /// - /// let request: ... // Some FetchRequest that fetches String - /// let string = try request.fetchOne(db) // String? - /// - /// - parameter db: A database connection. - /// - returns: An optional value. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchOne(_ db: Database) throws -> RowDecoder? { - return try RowDecoder.fetchOne(db, self) - } -} - -/// Swift's Optional comes with built-in methods that allow to fetch cursors -/// and arrays of optional DatabaseValueConvertible: -/// -/// try Optional.fetchCursor(db, sql: "SELECT name FROM ...", arguments:...) // Cursor of String? -/// try Optional.fetchAll(db, sql: "SELECT name FROM ...", arguments:...) // [String?] -/// -/// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") -/// try Optional.fetchCursor(statement, arguments:...) // Cursor of String? -/// try Optional.fetchAll(statement, arguments:...) // [String?] -/// -/// DatabaseValueConvertible is adopted by Bool, Int, String, etc. -extension Optional where Wrapped: DatabaseValueConvertible & StatementColumnConvertible { - - // MARK: Fetching From SelectStatement - - /// Returns a cursor over optional values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try Optional.fetchCursor(statement) // Cursor of String? - /// while let name = try names.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> FastNullableDatabaseValueCursor { - return try FastNullableDatabaseValueCursor(statement: statement, arguments: arguments, adapter: adapter) - } - - /// Returns an array of optional values fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT name FROM ...") - /// let names = try Optional.fetchAll(statement) // [String?] - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] { - return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) - } -} - -extension Optional where Wrapped: DatabaseValueConvertible & StatementColumnConvertible { - - // MARK: Fetching From SQL - - /// Returns a cursor over optional values fetched from an SQL query. - /// - /// let names = try Optional.fetchCursor(db, sql: "SELECT name FROM ...") // Cursor of String? - /// while let name = try names.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> FastNullableDatabaseValueCursor { - return try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns an array of optional values fetched from an SQL query. - /// - /// let names = try String.fetchAll(db, sql: "SELECT name FROM ...") // [String?] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> [Wrapped?] { - return try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } -} - -extension Optional where Wrapped: DatabaseValueConvertible & StatementColumnConvertible { - - // MARK: Fetching From FetchRequest - - /// Returns a cursor over optional values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try Optional.fetchCursor(db, request) // Cursor of String? - /// while let name = try names.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: A cursor over fetched optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: R) throws -> FastNullableDatabaseValueCursor { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of optional values fetched from a fetch request. - /// - /// let request = Player.select(Column("name")) - /// let names = try Optional.fetchAll(db, request) // [String?] - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An array of optional values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: R) throws -> [Wrapped?] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchAll(statement, adapter: adapter) - } -} - -extension FetchRequest where RowDecoder: _OptionalProtocol, RowDecoder._Wrapped: DatabaseValueConvertible & StatementColumnConvertible { - - // MARK: Fetching Optional values - - /// A cursor over fetched optional values. - /// - /// let request: ... // Some FetchRequest that fetches Optional - /// let strings = try request.fetchCursor(db) // Cursor of String? - /// while let string = try strings.next() { // String? - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> FastNullableDatabaseValueCursor { - return try Optional.fetchCursor(db, self) - } - - /// An array of fetched optional values. - /// - /// let request: ... // Some FetchRequest that fetches Optional - /// let strings = try request.fetchAll(db) // [String?] - /// - /// - parameter db: A database connection. - /// - returns: An array of values. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [RowDecoder._Wrapped?] { - return try Optional.fetchAll(db, self) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/CoreGraphics/CGFloat.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/CoreGraphics/CGFloat.swift deleted file mode 100755 index 444dc0c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/CoreGraphics/CGFloat.swift +++ /dev/null @@ -1,20 +0,0 @@ -#if canImport(CoreGraphics) -import CoreGraphics - -/// CGFloat adopts DatabaseValueConvertible -extension CGFloat : DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Double(self).databaseValue - } - - /// Returns a CGFloat initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> CGFloat? { - guard let double = Double.fromDatabaseValue(dbValue) else { - return nil - } - return CGFloat(double) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/Data.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/Data.swift deleted file mode 100755 index 8c4202b..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/Data.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// Data is convertible to and from DatabaseValue. -extension Data : DatabaseValueConvertible, StatementColumnConvertible { - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - if let bytes = sqlite3_column_blob(sqliteStatement, index) { - let count = Int(sqlite3_column_bytes(sqliteStatement, index)) - self.init(bytes: bytes, count: count) // copy bytes - } else { - self.init() - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return DatabaseValue(storage: .blob(self)) - } - - /// Returns a Data initialized from *dbValue*, if it contains - /// a Blob. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Data? { - switch dbValue.storage { - case .blob(let data): - return data - case .string(let string): - // Implicit conversion from string to blob, just as SQLite does - // See https://www.sqlite.org/c3ref/column_blob.html - return string.data(using: .utf8) - default: - return nil - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/DatabaseDateComponents.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/DatabaseDateComponents.swift deleted file mode 100755 index f84be49..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/DatabaseDateComponents.swift +++ /dev/null @@ -1,158 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// DatabaseDateComponents reads and stores DateComponents in the database. -public struct DatabaseDateComponents : DatabaseValueConvertible, StatementColumnConvertible { - - /// The available formats for reading and storing date components. - public enum Format : String { - - /// The format "yyyy-MM-dd". - case YMD = "yyyy-MM-dd" - - /// The format "yyyy-MM-dd HH:mm". - /// - /// This format is lexically comparable with SQLite's CURRENT_TIMESTAMP. - case YMD_HM = "yyyy-MM-dd HH:mm" - - /// The format "yyyy-MM-dd HH:mm:ss". - /// - /// This format is lexically comparable with SQLite's CURRENT_TIMESTAMP. - case YMD_HMS = "yyyy-MM-dd HH:mm:ss" - - /// The format "yyyy-MM-dd HH:mm:ss.SSS". - /// - /// This format is lexically comparable with SQLite's CURRENT_TIMESTAMP. - case YMD_HMSS = "yyyy-MM-dd HH:mm:ss.SSS" - - /// The format "HH:mm". - case HM = "HH:mm" - - /// The format "HH:mm:ss". - case HMS = "HH:mm:ss" - - /// The format "HH:mm:ss.SSS". - case HMSS = "HH:mm:ss.SSS" - - var hasYMDComponents: Bool { - switch self { - case .YMD, .YMD_HM, .YMD_HMS, .YMD_HMSS: - return true - case .HM, .HMS, .HMSS: - return false - } - } - } - - // MARK: - NSDateComponents conversion - - /// The date components - public let dateComponents: DateComponents - - /// The database format - public let format: Format - - /// Creates a DatabaseDateComponents from a DateComponents and a format. - /// - /// - parameters: - /// - dateComponents: An optional DateComponents. - /// - format: The format used for storing the date components in - /// the database. - public init(_ dateComponents: DateComponents, format: Format) { - self.format = format - self.dateComponents = dateComponents - } - - // MARK: - StatementColumnConvertible adoption - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - public init(sqliteStatement: SQLiteStatement, index: Int32) { - guard let cString = sqlite3_column_text(sqliteStatement, index) else { - fatalConversionError(to: DatabaseDateComponents.self, sqliteStatement: sqliteStatement, index: index) - } - let length = Int(sqlite3_column_bytes(sqliteStatement, index)) // avoid an strlen - let optionalComponents = cString.withMemoryRebound(to: Int8.self, capacity: length + 1 /* trailing \0 */) { cString in - SQLiteDateParser().components(cString: cString, length: length) - } - guard let components = optionalComponents else { - fatalConversionError(to: DatabaseDateComponents.self, sqliteStatement: sqliteStatement, index: index) - } - self.dateComponents = components.dateComponents - self.format = components.format - } - - // MARK: - DatabaseValueConvertible adoption - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - let dateString: String? - switch format { - case .YMD_HM, .YMD_HMS, .YMD_HMSS, .YMD: - let year = dateComponents.year ?? 0 - let month = dateComponents.month ?? 1 - let day = dateComponents.day ?? 1 - dateString = String(format: "%04d-%02d-%02d", year, month, day) - default: - dateString = nil - } - - let timeString: String? - switch format { - case .YMD_HM, .HM: - let hour = dateComponents.hour ?? 0 - let minute = dateComponents.minute ?? 0 - timeString = String(format: "%02d:%02d", hour, minute) - case .YMD_HMS, .HMS: - let hour = dateComponents.hour ?? 0 - let minute = dateComponents.minute ?? 0 - let second = dateComponents.second ?? 0 - timeString = String(format: "%02d:%02d:%02d", hour, minute, second) - case .YMD_HMSS, .HMSS: - let hour = dateComponents.hour ?? 0 - let minute = dateComponents.minute ?? 0 - let second = dateComponents.second ?? 0 - let nanosecond = dateComponents.nanosecond ?? 0 - timeString = String(format: "%02d:%02d:%02d.%03d", hour, minute, second, Int(round(Double(nanosecond) / 1_000_000.0))) - default: - timeString = nil - } - - return [dateString, timeString].compactMap { $0 }.joined(separator: " ").databaseValue - } - - /// Returns a DatabaseDateComponents if *dbValue* contains a - /// valid date. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> DatabaseDateComponents? { - // https://www.sqlite.org/lang_datefunc.html - // - // Supported formats are: - // - // - YYYY-MM-DD - // - YYYY-MM-DD HH:MM - // - YYYY-MM-DD HH:MM:SS - // - YYYY-MM-DD HH:MM:SS.SSS - // - YYYY-MM-DDTHH:MM - // - YYYY-MM-DDTHH:MM:SS - // - YYYY-MM-DDTHH:MM:SS.SSS - // - HH:MM - // - HH:MM:SS - // - HH:MM:SS.SSS - - // We need a String - guard let string = String.fromDatabaseValue(dbValue) else { - return nil - } - - return SQLiteDateParser().components(from: string) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/DatabaseValueConvertible+ReferenceConvertible.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/DatabaseValueConvertible+ReferenceConvertible.swift deleted file mode 100755 index d5fedd5..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/DatabaseValueConvertible+ReferenceConvertible.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -/// DatabaseValueConvertible is free for ReferenceConvertible types whose -/// ReferenceType is itself DatabaseValueConvertible. -/// -/// class FooReference { ... } -/// struct Foo : ReferenceConvertible { -/// typealias ReferenceType = FooReference -/// } -/// -/// // If the ReferenceType adopts DatabaseValueConvertible... -/// extension FooReference : DatabaseValueConvertible { ... } -/// -/// // ... then the ReferenceConvertible type can freely adopt DatabaseValueConvertible: -/// extension Foo : DatabaseValueConvertible { /* empty */ } -extension DatabaseValueConvertible where Self: ReferenceConvertible, Self.ReferenceType: DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return (self as! ReferenceType).databaseValue - } - - /// Returns a value initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - return ReferenceType.fromDatabaseValue(dbValue).flatMap { cast($0) } - } -} - -extension DatabaseValueConvertible where Self: Decodable & ReferenceConvertible, Self.ReferenceType: DatabaseValueConvertible { - public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? { - // Preserve custom database decoding - return ReferenceType.fromDatabaseValue(databaseValue).flatMap { cast($0) } - } -} - -extension DatabaseValueConvertible where Self: Encodable & ReferenceConvertible, Self.ReferenceType: DatabaseValueConvertible { - public var databaseValue: DatabaseValue { - // Preserve custom database encoding - return (self as! ReferenceType).databaseValue - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/Date.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/Date.swift deleted file mode 100755 index 4742c84..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/Date.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -#if !os(Linux) -/// NSDate is stored in the database using the format -/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone. -extension NSDate : DatabaseValueConvertible { - /// Returns a database value that contains the date encoded as - /// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone. - public var databaseValue: DatabaseValue { - return (self as Date).databaseValue - } - - /// Returns a date initialized from dbValue, if possible. - /// - /// If database value contains a number, that number is interpreted as a - /// timeinterval since 00:00:00 UTC on 1 January 1970. - /// - /// If database value contains a string, that string is interpreted as a - /// [SQLite date](https://sqlite.org/lang_datefunc.html) in the UTC time - /// zone. Nil is returned if the date string does not contain at least the - /// year, month and day components. Other components (minutes, etc.) - /// are set to zero if missing. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - guard let date = Date.fromDatabaseValue(dbValue) else { - return nil - } - return cast(date) - } -} -#endif - -/// Date is stored in the database using the format -/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone. -extension Date : DatabaseValueConvertible { - /// Returns a database value that contains the date encoded as - /// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone. - public var databaseValue: DatabaseValue { - return storageDateFormatter.string(from: self).databaseValue - } - - /// Returns a date initialized from dbValue, if possible. - /// - /// If database value contains a number, that number is interpreted as a - /// timeinterval since 00:00:00 UTC on 1 January 1970. - /// - /// If database value contains a string, that string is interpreted as a - /// [SQLite date](https://sqlite.org/lang_datefunc.html) in the UTC time - /// zone. Nil is returned if the date string does not contain at least the - /// year, month and day components. Other components (minutes, etc.) - /// are set to zero if missing. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Date? { - if let databaseDateComponents = DatabaseDateComponents.fromDatabaseValue(dbValue) { - return Date(databaseDateComponents: databaseDateComponents) - } - if let timestamp = Double.fromDatabaseValue(dbValue) { - return Date(timeIntervalSince1970: timestamp) - } - return nil - } - - @usableFromInline - init?(databaseDateComponents: DatabaseDateComponents) { - guard databaseDateComponents.format.hasYMDComponents else { - // Refuse to turn hours without any date information into Date: - return nil - } - guard let date = UTCCalendar.date(from: databaseDateComponents.dateComponents) else { - return nil - } - self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate) - } - - /// Creates a date from a [Julian Day](https://en.wikipedia.org/wiki/Julian_day). - public init?(julianDay: Double) { - // Conversion uses the same algorithm as SQLite: https://www.sqlite.org/src/artifact/8ec787fed4929d8c - // TODO: check for overflows one day, and return nil when computation can't complete. - let JD = Int64(julianDay * 86400000) - let Z = Int(((JD + 43200000)/86400000)) - var A = Int(((Double(Z) - 1867216.25)/36524.25)) - A = Z + 1 + A - (A/4) - let B = A + 1524 - let C = Int(((Double(B) - 122.1)/365.25)) - let D = (36525*(C&32767))/100 - let E = Int((Double(B-D)/30.6001)) - let X1 = Int((30.6001*Double(E))) - let day = B - D - X1 - let month = E<14 ? E-1 : E-13 - let year = month>2 ? C - 4716 : C - 4715 - var s = Int(((JD + 43200000) % 86400000)) - var second = Double(s)/1000.0 - s = Int(second) - second -= Double(s) - let hour = s/3600 - s -= hour*3600 - let minute = s/60 - second += Double(s - minute*60) - - var dateComponents = DateComponents() - dateComponents.year = year - dateComponents.month = month - dateComponents.day = day - dateComponents.hour = hour - dateComponents.minute = minute - dateComponents.second = Int(second) - dateComponents.nanosecond = Int((second - Double(Int(second))) * 1.0e9) - - guard let date = UTCCalendar.date(from: dateComponents) else { - return nil - } - self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate) - } -} - -extension Date: StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - switch sqlite3_column_type(sqliteStatement, index) { - case SQLITE_INTEGER, SQLITE_FLOAT: - self.init(timeIntervalSince1970: sqlite3_column_double(sqliteStatement, index)) - case SQLITE_TEXT: - let databaseDateComponents = DatabaseDateComponents(sqliteStatement: sqliteStatement, index: index) - guard let date = Date(databaseDateComponents: databaseDateComponents) else { - fatalConversionError(to: Date.self, sqliteStatement: sqliteStatement, index: index) - } - self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate) - default: - fatalConversionError(to: Date.self, sqliteStatement: sqliteStatement, index: index) - } - } -} - -/// The DatabaseDate date formatter for stored dates. -private let storageDateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - return formatter - }() - -// The NSCalendar for stored dates. -private let UTCCalendar: Calendar = { - var calendar = Calendar(identifier: .gregorian) - calendar.locale = Locale(identifier: "en_US_POSIX") - calendar.timeZone = TimeZone(secondsFromGMT: 0)! - return calendar - }() diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSData.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSData.swift deleted file mode 100755 index 8bd9d2e..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSData.swift +++ /dev/null @@ -1,21 +0,0 @@ -#if !os(Linux) -import Foundation - -/// NSData is convertible to and from DatabaseValue. -extension NSData : DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return (self as Data).databaseValue - } - - /// Returns an NSData initialized from *dbValue*, if it contains - /// a Blob. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - guard let data = Data.fromDatabaseValue(dbValue) else { - return nil - } - return cast(data) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSNull.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSNull.swift deleted file mode 100755 index e954bef..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSNull.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -/// NSNull adopts DatabaseValueConvertible -extension NSNull : DatabaseValueConvertible { - - /// Returns DatabaseValue.null. - public var databaseValue: DatabaseValue { - return .null - } - - /// Returns nil. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - return nil - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSNumber.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSNumber.swift deleted file mode 100755 index 2caf608..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSNumber.swift +++ /dev/null @@ -1,69 +0,0 @@ -#if !os(Linux) -import Foundation - -private let integerRoundingBehavior = NSDecimalNumberHandler(roundingMode: .plain, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) - -/// NSNumber adopts DatabaseValueConvertible -extension NSNumber : DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - // Don't lose precision: store integers that fits in Int64 as Int64 - if let decimal = self as? NSDecimalNumber, - decimal == decimal.rounding(accordingToBehavior: integerRoundingBehavior), // integer - decimal.compare(NSDecimalNumber(value: Int64.max)) != .orderedDescending, // decimal <= Int64.max - decimal.compare(NSDecimalNumber(value: Int64.min)) != .orderedAscending // decimal >= Int64.min - { - return int64Value.databaseValue - } - - switch String(cString: objCType) { - case "c": - return Int64(int8Value).databaseValue - case "C": - return Int64(uint8Value).databaseValue - case "s": - return Int64(int16Value).databaseValue - case "S": - return Int64(uint16Value).databaseValue - case "i": - return Int64(int32Value).databaseValue - case "I": - return Int64(uint32Value).databaseValue - case "l": - return Int64(intValue).databaseValue - case "L": - let uint = uintValue - GRDBPrecondition(UInt64(uint) <= UInt64(Int64.max), "could not convert \(uint) to an Int64 that can be stored in the database") - return Int64(uint).databaseValue - case "q": - return Int64(int64Value).databaseValue - case "Q": - let uint64 = uint64Value - GRDBPrecondition(uint64 <= UInt64(Int64.max), "could not convert \(uint64) to an Int64 that can be stored in the database") - return Int64(uint64).databaseValue - case "f": - return Double(floatValue).databaseValue - case "d": - return doubleValue.databaseValue - case "B": - return boolValue.databaseValue - case let objCType: - // Assume a GRDB bug: there is no point throwing any error. - fatalError("DatabaseValueConvertible: Unsupported NSNumber type: \(objCType)") - } - } - - /// Returns an NSNumber initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - switch dbValue.storage { - case .int64(let int64): - return self.init(value: int64) - case .double(let double): - return self.init(value: double) - default: - return nil - } - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSString.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSString.swift deleted file mode 100755 index d5455be..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/NSString.swift +++ /dev/null @@ -1,20 +0,0 @@ -#if !os(Linux) -import Foundation - -/// NSString adopts DatabaseValueConvertible -extension NSString : DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return (self as String).databaseValue - } - - /// Returns an NSString initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - guard let string = String.fromDatabaseValue(dbValue) else { - return nil - } - return self.init(string: string) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/SQLiteDateParser.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/SQLiteDateParser.swift deleted file mode 100755 index cd42663..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/SQLiteDateParser.swift +++ /dev/null @@ -1,144 +0,0 @@ -import Foundation - -// inspired by: http://jordansmith.io/performant-date-parsing/ - -class SQLiteDateParser { - - private struct ParserComponents { - var year: Int32 = 0 - var month: Int32 = 0 - var day: Int32 = 0 - var hour: Int32 = 0 - var minute: Int32 = 0 - var second: Int32 = 0 - var nanosecond = ContiguousArray(repeating: 0, count: 10) // 9 digits, and trailing \0 - } - - func components(from dateString: String) -> DatabaseDateComponents? { - return dateString.withCString { cString in - components(cString: cString, length: strlen(cString)) - } - } - - func components(cString: UnsafePointer, length: Int) -> DatabaseDateComponents? { - assert(strlen(cString) == length) - - // "HH:MM" is the shortest valid string - guard length >= 5 else { return nil } - - // "YYYY-..." -> datetime - if cString[4] == UInt8(ascii: "-") { - return datetimeComponents(cString: cString, length: length) - } - - // "HH-:..." -> time - if cString[2] == UInt8(ascii: ":") { - return timeComponents(cString: cString, length: length) - } - - // Invalid - return nil - } - - // - YYYY-MM-DD - // - YYYY-MM-DD HH:MM - // - YYYY-MM-DD HH:MM:SS - // - YYYY-MM-DD HH:MM:SS.SSS - // - YYYY-MM-DDTHH:MM - // - YYYY-MM-DDTHH:MM:SS - // - YYYY-MM-DDTHH:MM:SS.SSS - private func datetimeComponents(cString: UnsafePointer, length: Int) -> DatabaseDateComponents? { - var parserComponents = ParserComponents() - - // TODO: Get rid of this pyramid when SE-0210 has shipped - let parseCount = withUnsafeMutablePointer(to: &parserComponents.year) { yearP in - withUnsafeMutablePointer(to: &parserComponents.month) { monthP in - withUnsafeMutablePointer(to: &parserComponents.day) { dayP in - withUnsafeMutablePointer(to: &parserComponents.hour) { hourP in - withUnsafeMutablePointer(to: &parserComponents.minute) { minuteP in - withUnsafeMutablePointer(to: &parserComponents.second) { secondP in - parserComponents.nanosecond.withUnsafeMutableBufferPointer { nanosecondBuffer in - withVaList([yearP, monthP, dayP, hourP, minuteP, secondP, nanosecondBuffer.baseAddress!]) { pointer in - vsscanf(cString, "%4d-%2d-%2d%*1[ T]%2d:%2d:%2d.%9s", pointer) - } - } - } - } - } - } - } - } - - guard parseCount >= 3 else { return nil } - - var components = DateComponents() - components.year = Int(parserComponents.year) - components.month = Int(parserComponents.month) - components.day = Int(parserComponents.day) - - guard parseCount >= 5 else { return DatabaseDateComponents(components, format: .YMD) } - - components.hour = Int(parserComponents.hour) - components.minute = Int(parserComponents.minute) - - guard parseCount >= 6 else { return DatabaseDateComponents(components, format: .YMD_HM) } - - components.second = Int(parserComponents.second) - - guard parseCount >= 7 else { return DatabaseDateComponents(components, format: .YMD_HMS) } - - components.nanosecond = nanosecondsInt(for: parserComponents.nanosecond) - - return DatabaseDateComponents(components, format: .YMD_HMSS) - } - - // - HH:MM - // - HH:MM:SS - // - HH:MM:SS.SSS - private func timeComponents(cString: UnsafePointer, length: Int) -> DatabaseDateComponents? { - var parserComponents = ParserComponents() - - // TODO: Get rid of this pyramid when SE-0210 has shipped - let parseCount = withUnsafeMutablePointer(to: &parserComponents.hour) { hourP in - withUnsafeMutablePointer(to: &parserComponents.minute) { minuteP in - withUnsafeMutablePointer(to: &parserComponents.second) { secondP in - parserComponents.nanosecond.withUnsafeMutableBufferPointer { nanosecondBuffer in - withVaList([hourP, minuteP, secondP, nanosecondBuffer.baseAddress!]) { pointer in - vsscanf(cString, "%2d:%2d:%2d.%9s", pointer) - } - } - } - } - } - - guard parseCount >= 2 else { return nil } - - var components = DateComponents() - components.hour = Int(parserComponents.hour) - components.minute = Int(parserComponents.minute) - - guard parseCount >= 3 else { return DatabaseDateComponents(components, format: .HM) } - - components.second = Int(parserComponents.second) - - guard parseCount >= 4 else { return DatabaseDateComponents(components, format: .HMS) } - - guard let nanoseconds = nanosecondsInt(for: parserComponents.nanosecond) else { return nil } - components.nanosecond = nanoseconds - - return DatabaseDateComponents(components, format: .HMSS) - } - - private func nanosecondsInt(for nanosecond: ContiguousArray) -> Int? { - // truncate after the third digit - var result = 0 - let multipliers = [100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1] - for (char, multiplier) in zip(nanosecond.prefix(3), multipliers) { - if char == 0 { return result } - let digit = Int(char) - 48 /* '0' */ - guard (0...9).contains(digit) else { return nil } - result += multiplier * digit - } - return result - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/URL.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/URL.swift deleted file mode 100755 index 4cc95b1..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/URL.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - -#if !os(Linux) -/// NSURL stores its absoluteString in the database. -extension NSURL : DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - /// (the URL's absoluteString). - public var databaseValue: DatabaseValue { - return absoluteString?.databaseValue ?? .null - } - - /// Returns an NSURL initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - guard let string = String.fromDatabaseValue(dbValue) else { - return nil - } - return cast(URL(string: string)) - } -} -#endif - -/// URL stores its absoluteString in the database. -extension URL : DatabaseValueConvertible { } diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/UUID.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/UUID.swift deleted file mode 100755 index 8ed8d68..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/Foundation/UUID.swift +++ /dev/null @@ -1,91 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -#if !os(Linux) -/// NSUUID adopts DatabaseValueConvertible -extension NSUUID: DatabaseValueConvertible { - public var databaseValue: DatabaseValue { - var uuidBytes = ContiguousArray(repeating: UInt8(0), count: 16) - return uuidBytes.withUnsafeMutableBufferPointer { buffer in - getBytes(buffer.baseAddress!) - return NSData(bytes: buffer.baseAddress, length: 16).databaseValue - } - } - - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - switch dbValue.storage { - case .blob(let data) where data.count == 16: - #if swift(>=5.0) - return data.withUnsafeBytes { - self.init(uuidBytes: $0.bindMemory(to: UInt8.self).baseAddress) - } - #else - return data.withUnsafeBytes { - self.init(uuidBytes: $0) - } - #endif - case .string(let string): - return self.init(uuidString: string) - default: - return nil - } - } -} -#endif - -/// UUID adopts DatabaseValueConvertible -extension UUID: DatabaseValueConvertible { - public var databaseValue: DatabaseValue { - return withUnsafeBytes(of: uuid) { - Data(bytes: $0.baseAddress!, count: $0.count).databaseValue - } - } - - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UUID? { - switch dbValue.storage { - case .blob(let data) where data.count == 16: - #if swift(>=5.0) - return data.withUnsafeBytes { - UUID(uuid: $0.bindMemory(to: uuid_t.self).first!) - } - #else - return data.withUnsafeBytes { - UUID(uuid: $0.pointee) - } - #endif - case .string(let string): - return UUID(uuidString: string) - default: - return nil - } - } -} - -extension UUID: StatementColumnConvertible { - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - switch sqlite3_column_type(sqliteStatement, index) { - case SQLITE_TEXT: - let string = String(cString: sqlite3_column_text(sqliteStatement, index)!) - guard let uuid = UUID(uuidString: string) else { - fatalConversionError(to: UUID.self, sqliteStatement: sqliteStatement, index: index) - } - self.init(uuid: uuid.uuid) - case SQLITE_BLOB: - guard sqlite3_column_bytes(sqliteStatement, index) == 16, - let blob = sqlite3_column_blob(sqliteStatement, index) else - { - fatalConversionError(to: UUID.self, sqliteStatement: sqliteStatement, index: index) - } - self.init(uuid: blob.assumingMemoryBound(to: uuid_t.self).pointee) - default: - fatalConversionError(to: UUID.self, sqliteStatement: sqliteStatement, index: index) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Decodable.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Decodable.swift deleted file mode 100755 index f04cfd0..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Decodable.swift +++ /dev/null @@ -1,192 +0,0 @@ -private struct DatabaseValueDecodingContainer: SingleValueDecodingContainer { - let dbValue: DatabaseValue - let codingPath: [CodingKey] - - /// Decodes a null value. - /// - /// - returns: Whether the encountered value was null. - func decodeNil() -> Bool { - return dbValue.isNull - } - - /// Decodes a single value of the given type. - /// - /// - parameter type: The type to decode as. - /// - returns: A value of the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value cannot be converted to the requested type. - /// - throws: `DecodingError.valueNotFound` if the encountered encoded value is null. - func decode(_ type: Bool.Type) throws -> Bool { - if let result = Bool.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Int.Type) throws -> Int { - if let result = Int.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Int8.Type) throws -> Int8 { - if let result = Int8.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Int16.Type) throws -> Int16 { - if let result = Int16.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Int32.Type) throws -> Int32 { - if let result = Int32.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Int64.Type) throws -> Int64 { - if let result = Int64.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: UInt.Type) throws -> UInt { - if let result = UInt.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: UInt8.Type) throws -> UInt8 { - if let result = UInt8.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: UInt16.Type) throws -> UInt16 { - if let result = UInt16.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: UInt32.Type) throws -> UInt32 { - if let result = UInt32.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: UInt64.Type) throws -> UInt64 { - if let result = UInt64.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Float.Type) throws -> Float { - if let result = Float.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: Double.Type) throws -> Double { - if let result = Double.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - func decode(_ type: String.Type) throws -> String { - if let result = String.fromDatabaseValue(dbValue) { - return result - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } - - /// Decodes a single value of the given type. - /// - /// - parameter type: The type to decode as. - /// - returns: A value of the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value cannot be converted to the requested type. - /// - throws: `DecodingError.valueNotFound` if the encountered encoded value is null. - func decode(_ type: T.Type) throws -> T where T : Decodable { - if let type = T.self as? DatabaseValueConvertible.Type { - // Prefer DatabaseValueConvertible decoding over Decodable. - // This allows custom database decoding, such as decoding Date from - // String, for example. - if let result = type.fromDatabaseValue(dbValue) { - return result as! T - } else { - throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch") - } - } else { - return try T(from: DatabaseValueDecoder(dbValue: dbValue, codingPath: codingPath)) - } - } -} - -private struct DatabaseValueDecoder: Decoder { - let dbValue: DatabaseValue - - init(dbValue: DatabaseValue, codingPath: [CodingKey]) { - self.dbValue = dbValue - self.codingPath = codingPath - } - - // Decoder - let codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey : Any] { return [:] } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - throw DecodingError.typeMismatch( - type, - DecodingError.Context(codingPath: codingPath, debugDescription: "keyed decoding is not supported")) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError.typeMismatch( - UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: codingPath, debugDescription: "unkeyed decoding is not supported")) - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - return DatabaseValueDecodingContainer(dbValue: dbValue, codingPath: codingPath) - } -} - -extension DatabaseValueConvertible where Self: Decodable { - public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? { - return try? self.init(from: DatabaseValueDecoder(dbValue: databaseValue, codingPath: [])) - } -} - -extension DatabaseValueConvertible where Self: Decodable & RawRepresentable, Self.RawValue: DatabaseValueConvertible { - public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? { - // Preserve custom database decoding - return RawValue.fromDatabaseValue(databaseValue).flatMap { self.init(rawValue: $0) } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Encodable.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Encodable.swift deleted file mode 100755 index 8aa705b..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Encodable.swift +++ /dev/null @@ -1,106 +0,0 @@ -private struct DatabaseValueEncodingContainer : SingleValueEncodingContainer { - let encode: (DatabaseValue) -> Void - - var codingPath: [CodingKey] { return [] } - - /// Encodes a null value. - /// - /// - throws: `EncodingError.invalidValue` if a null value is invalid in the current context for this format. - /// - precondition: May not be called after a previous `self.encode(_:)` call. - mutating func encodeNil() throws { encode(.null) } - - /// Encodes a single value of the given type. - /// - /// - parameter value: The value to encode. - /// - throws: `EncodingError.invalidValue` if the given value is invalid in the current context for this format. - /// - precondition: May not be called after a previous `self.encode(_:)` call. - mutating func encode(_ value: Bool) throws { encode(value.databaseValue) } - mutating func encode(_ value: Int) throws { encode(value.databaseValue) } - mutating func encode(_ value: Int8) throws { encode(value.databaseValue) } - mutating func encode(_ value: Int16) throws { encode(value.databaseValue) } - mutating func encode(_ value: Int32) throws { encode(value.databaseValue) } - mutating func encode(_ value: Int64) throws { encode(value.databaseValue) } - mutating func encode(_ value: UInt) throws { encode(value.databaseValue) } - mutating func encode(_ value: UInt8) throws { encode(value.databaseValue) } - mutating func encode(_ value: UInt16) throws { encode(value.databaseValue) } - mutating func encode(_ value: UInt32) throws { encode(value.databaseValue) } - mutating func encode(_ value: UInt64) throws { encode(value.databaseValue) } - mutating func encode(_ value: Float) throws { encode(value.databaseValue) } - mutating func encode(_ value: Double) throws { encode(value.databaseValue) } - mutating func encode(_ value: String) throws { encode(value.databaseValue) } - - /// Encodes a single value of the given type. - /// - /// - parameter value: The value to encode. - /// - throws: `EncodingError.invalidValue` if the given value is invalid in the current context for this format. - /// - precondition: May not be called after a previous `self.encode(_:)` call. - mutating func encode(_ value: T) throws where T : Encodable { - if let dbValueConvertible = value as? DatabaseValueConvertible { - // Prefer DatabaseValueConvertible encoding over Decodable. - // This allows us to encode Date as String, for example. - encode(dbValueConvertible.databaseValue) - } else { - try value.encode(to: DatabaseValueEncoder(encode: encode)) - } - } -} - -private struct DatabaseValueEncoder : Encoder { - let encode: (DatabaseValue) -> Void - - /// The path of coding keys taken to get to this point in encoding. - /// A `nil` value indicates an unkeyed container. - var codingPath: [CodingKey] { return [] } - - /// Any contextual information set by the user for encoding. - var userInfo: [CodingUserInfoKey : Any] = [:] - - init(encode: @escaping (DatabaseValue) -> Void) { - self.encode = encode - } - - /// Returns an encoding container appropriate for holding multiple values keyed by the given key type. - /// - /// - parameter type: The key type to use for the container. - /// - returns: A new keyed encoding container. - /// - precondition: May not be called after a prior `self.unkeyedContainer()` call. - /// - precondition: May not be called after a value has been encoded through a previous `self.singleValueContainer()` call. - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer { - fatalError("keyed encoding is not supported") - } - - /// Returns an encoding container appropriate for holding multiple unkeyed values. - /// - /// - returns: A new empty unkeyed container. - /// - precondition: May not be called after a prior `self.container(keyedBy:)` call. - /// - precondition: May not be called after a value has been encoded through a previous `self.singleValueContainer()` call. - func unkeyedContainer() -> UnkeyedEncodingContainer { - fatalError("unkeyed encoding is not supported") - } - - /// Returns an encoding container appropriate for holding a single primitive value. - /// - /// - returns: A new empty single value container. - /// - precondition: May not be called after a prior `self.container(keyedBy:)` call. - /// - precondition: May not be called after a prior `self.unkeyedContainer()` call. - /// - precondition: May not be called after a value has been encoded through a previous `self.singleValueContainer()` call. - func singleValueContainer() -> SingleValueEncodingContainer { - return DatabaseValueEncodingContainer(encode: encode) - } -} - -extension DatabaseValueConvertible where Self: Encodable { - public var databaseValue: DatabaseValue { - var dbValue: DatabaseValue! = nil - let encoder = DatabaseValueEncoder(encode: { dbValue = $0 }) - try! self.encode(to: encoder) - return dbValue - } -} - -extension DatabaseValueConvertible where Self: Encodable & RawRepresentable, Self.RawValue: DatabaseValueConvertible { - public var databaseValue: DatabaseValue { - // Preserve custom database encoding - return rawValue.databaseValue - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+RawRepresentable.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+RawRepresentable.swift deleted file mode 100755 index 0bb05cb..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+RawRepresentable.swift +++ /dev/null @@ -1,24 +0,0 @@ -/// DatabaseValueConvertible is free for RawRepresentable types whose raw value -/// is itself DatabaseValueConvertible. -/// -/// // If the RawValue adopts DatabaseValueConvertible... -/// enum Color : Int { -/// case red -/// case white -/// case rose -/// } -/// -/// // ... then the RawRepresentable type can freely adopt DatabaseValueConvertible: -/// extension Color : DatabaseValueConvertible { /* empty */ } -extension DatabaseValueConvertible where Self: RawRepresentable, Self.RawValue: DatabaseValueConvertible { - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return rawValue.databaseValue - } - - /// Returns a value initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { - return RawValue.fromDatabaseValue(dbValue).flatMap { self.init(rawValue: $0) } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift b/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift deleted file mode 100755 index 79a3d58..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/Support/StandardLibrary/StandardLibrary.swift +++ /dev/null @@ -1,715 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -// MARK: - Value Types - -/// Bool adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Bool: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - self = sqlite3_column_int64(sqliteStatement, index) != 0 - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return (self ? 1 : 0).databaseValue - } - - /// Returns a Bool initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Bool? { - // IMPLEMENTATION NOTE - // - // https://www.sqlite.org/lang_expr.html#booleanexpr - // - // > # Boolean Expressions - // > - // > The SQL language features several contexts where an expression is - // > evaluated and the result converted to a boolean (true or false) - // > value. These contexts are: - // > - // > - the WHERE clause of a SELECT, UPDATE or DELETE statement, - // > - the ON or USING clause of a join in a SELECT statement, - // > - the HAVING clause of a SELECT statement, - // > - the WHEN clause of an SQL trigger, and - // > - the WHEN clause or clauses of some CASE expressions. - // > - // > To convert the results of an SQL expression to a boolean value, - // > SQLite first casts the result to a NUMERIC value in the same way as - // > a CAST expression. A numeric zero value (integer value 0 or real - // > value 0.0) is considered to be false. A NULL value is still NULL. - // > All other values are considered true. - // > - // > For example, the values NULL, 0.0, 0, 'english' and '0' are all - // > considered to be false. Values 1, 1.0, 0.1, -0.1 and '1english' are - // > considered to be true. - // - // OK so we have to support boolean for all storage classes? - // Actually we won't, because of the SQLite boolean interpretation of - // strings: - // - // The doc says that "english" should be false, and "1english" should - // be true. I guess "-1english" and "0.1english" should be true also. - // And... what about "0.0e10english"? - // - // Ideally, we'd ask SQLite to perform the conversion itself, and return - // its own boolean interpretation of the string. Unfortunately, it looks - // like it is not so easy... - // - // So we could take a short route, and assume all strings are false, - // since most strings are falsey for SQLite. - // - // Considering all strings falsey is unfortunately very - // counter-intuitive. This is not the correct way to tackle the boolean - // problem. - // - // Instead, let's use the fact that the BOOLEAN typename has Numeric - // affinity (https://www.sqlite.org/datatype3.html), and that the doc - // says: - // - // > SQLite does not have a separate Boolean storage class. Instead, - // > Boolean values are stored as integers 0 (false) and 1 (true). - // - // So we extract bools from Integer and Real only. Integer because it is - // the natural boolean storage class, and Real because Numeric affinity - // store big numbers as Real. - - switch dbValue.storage { - case .int64(let int64): - return (int64 != 0) - case .double(let double): - return (double != 0.0) - default: - return nil - } - } -} - -/// Int adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Int: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = Int(exactly: int64) { - self = v - } else { - fatalConversionError(to: Int.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an Int initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int? { - return Int64.fromDatabaseValue(dbValue).flatMap { Int(exactly: $0) } - } -} - -/// Int8 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Int8: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = Int8(exactly: int64) { - self = v - } else { - fatalConversionError(to: Int8.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an Int8 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int8? { - return Int64.fromDatabaseValue(dbValue).flatMap { Int8(exactly: $0) } - } -} - -/// Int16 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Int16: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = Int16(exactly: int64) { - self = v - } else { - fatalConversionError(to: Int16.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an Int16 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int16? { - return Int64.fromDatabaseValue(dbValue).flatMap { Int16(exactly: $0) } - } -} - -/// Int32 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Int32: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = Int32(exactly: int64) { - self = v - } else { - fatalConversionError(to: Int32.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an Int32 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int32? { - return Int64.fromDatabaseValue(dbValue).flatMap { Int32(exactly: $0) } - } -} - -/// Int64 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Int64: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - self = sqlite3_column_int64(sqliteStatement, index) - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return DatabaseValue(storage: .int64(self)) - } - - /// Returns an Int64 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int64? { - switch dbValue.storage { - case .int64(let int64): - return int64 - case .double(let double): - guard double >= Double(Int64.min) else { return nil } - guard double < Double(Int64.max) else { return nil } - return Int64(double) - default: - return nil - } - } -} - -/// UInt adopts DatabaseValueConvertible and StatementColumnConvertible. -extension UInt: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = UInt(exactly: int64) { - self = v - } else { - fatalConversionError(to: UInt.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an Int initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt? { - return Int64.fromDatabaseValue(dbValue).flatMap { UInt(exactly: $0) } - } -} - -/// UInt8 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension UInt8: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = UInt8(exactly: int64) { - self = v - } else { - fatalConversionError(to: UInt8.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an UInt8 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt8? { - return Int64.fromDatabaseValue(dbValue).flatMap { UInt8(exactly: $0) } - } -} - -/// UInt16 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension UInt16: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = UInt16(exactly: int64) { - self = v - } else { - fatalConversionError(to: UInt16.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an UInt16 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt16? { - return Int64.fromDatabaseValue(dbValue).flatMap { UInt16(exactly: $0) } - } -} - -/// UInt32 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension UInt32: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = UInt32(exactly: int64) { - self = v - } else { - fatalConversionError(to: UInt32.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an UInt32 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt32? { - return Int64.fromDatabaseValue(dbValue).flatMap { UInt32(exactly: $0) } - } -} - -/// UInt64 adopts DatabaseValueConvertible and StatementColumnConvertible. -extension UInt64: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - let int64 = sqlite3_column_int64(sqliteStatement, index) - if let v = UInt64(exactly: int64) { - self = v - } else { - fatalConversionError(to: UInt64.self, sqliteStatement: sqliteStatement, index: index) - } - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Int64(self).databaseValue - } - - /// Returns an UInt64 initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt64? { - return Int64.fromDatabaseValue(dbValue).flatMap { UInt64(exactly: $0) } - } -} - -/// Double adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Double: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - self = sqlite3_column_double(sqliteStatement, index) - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return DatabaseValue(storage: .double(self)) - } - - /// Returns a Double initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Double? { - switch dbValue.storage { - case .int64(let int64): - return Double(int64) - case .double(let double): - return double - default: - return nil - } - } -} - -/// Float adopts DatabaseValueConvertible and StatementColumnConvertible. -extension Float: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - self = Float(sqlite3_column_double(sqliteStatement, index)) - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return Double(self).databaseValue - } - - /// Returns a Float initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Float? { - switch dbValue.storage { - case .int64(let int64): - return Float(int64) - case .double(let double): - return Float(double) - default: - return nil - } - } -} - -/// String adopts DatabaseValueConvertible and StatementColumnConvertible. -extension String: DatabaseValueConvertible, StatementColumnConvertible { - - /// Returns a value initialized from a raw SQLite statement pointer. - /// - /// - parameters: - /// - sqliteStatement: A pointer to an SQLite statement. - /// - index: The column index. - @inlinable - public init(sqliteStatement: SQLiteStatement, index: Int32) { - self = String(cString: sqlite3_column_text(sqliteStatement, index)!) - } - - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return DatabaseValue(storage: .string(self)) - } - - /// Returns a String initialized from *dbValue*, if possible. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> String? { - switch dbValue.storage { - case .blob(let data): - // Implicit conversion from blob to string, just as SQLite does - // See https://www.sqlite.org/c3ref/column_blob.html - return String(data: data, encoding: .utf8) - case .string(let string): - return string - default: - return nil - } - } -} - - -// MARK: - SQL Functions - -extension DatabaseFunction { - /// An SQL function that returns the Swift built-in capitalized - /// String property. - /// - /// The function returns NULL for non-strings values. - /// - /// This function is automatically added by GRDB to your database - /// connections. It is the function used by the query interface's - /// capitalized: - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.capitalized) - /// let names = try String.fetchAll(dbQueue, request) // [String] - public static let capitalize = DatabaseFunction("swiftCapitalizedString", argumentCount: 1, pure: true) { dbValues in - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return nil - } - return string.capitalized - } - - /// An SQL function that returns the Swift built-in lowercased - /// String property. - /// - /// The function returns NULL for non-strings values. - /// - /// This function is automatically added by GRDB to your database - /// connections. It is the function used by the query interface's - /// lowercased: - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.lowercased()) - /// let names = try String.fetchAll(dbQueue, request) // [String] - public static let lowercase = DatabaseFunction("swiftLowercaseString", argumentCount: 1, pure: true) { dbValues in - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return nil - } - return string.lowercased() - } - - /// An SQL function that returns the Swift built-in uppercased - /// String property. - /// - /// The function returns NULL for non-strings values. - /// - /// This function is automatically added by GRDB to your database - /// connections. It is the function used by the query interface's - /// uppercased: - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.uppercased()) - /// let names = try String.fetchAll(dbQueue, request) // [String] - public static let uppercase = DatabaseFunction("swiftUppercaseString", argumentCount: 1, pure: true) { dbValues in - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return nil - } - return string.uppercased() - } -} - -extension DatabaseFunction { - /// An SQL function that returns the Swift built-in - /// localizedCapitalized String property. - /// - /// The function returns NULL for non-strings values. - /// - /// This function is automatically added by GRDB to your database - /// connections. It is the function used by the query interface's - /// localizedCapitalized: - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.localizedCapitalized) - /// let names = try String.fetchAll(dbQueue, request) // [String] - @available(OSX 10.11, watchOS 3.0, *) - public static let localizedCapitalize = DatabaseFunction("swiftLocalizedCapitalizedString", argumentCount: 1, pure: true) { dbValues in - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return nil - } - return string.localizedCapitalized - } - - /// An SQL function that returns the Swift built-in - /// localizedLowercased String property. - /// - /// The function returns NULL for non-strings values. - /// - /// This function is automatically added by GRDB to your database - /// connections. It is the function used by the query interface's - /// localizedLowercased: - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.localizedLowercased) - /// let names = try String.fetchAll(dbQueue, request) // [String] - @available(OSX 10.11, watchOS 3.0, *) - public static let localizedLowercase = DatabaseFunction("swiftLocalizedLowercaseString", argumentCount: 1, pure: true) { dbValues in - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return nil - } - return string.localizedLowercase - } - - /// An SQL function that returns the Swift built-in - /// localizedUppercased String property. - /// - /// The function returns NULL for non-strings values. - /// - /// This function is automatically added by GRDB to your database - /// connections. It is the function used by the query interface's - /// localizedUppercased: - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.localizedUppercased) - /// let names = try String.fetchAll(dbQueue, request) // [String] - @available(OSX 10.11, watchOS 3.0, *) - public static let localizedUppercase = DatabaseFunction("swiftLocalizedUppercaseString", argumentCount: 1, pure: true) { dbValues in - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return nil - } - return string.localizedUppercase - } -} - - -// MARK: - SQLite Collations - -extension DatabaseCollation { - // Here we define a set of predefined collations. - // - // We should avoid renaming those collations, because database created with - // earlier versions of the library may have used those collations in the - // definition of tables. A renaming would prevent SQLite to find the - // collation. - // - // Yet we're not absolutely stuck: we could register support for obsolete - // collation names with sqlite3_collation_needed(). - // See https://www.sqlite.org/capi3ref.html#sqlite3_collation_needed - - /// A collation, or SQL string comparison function, that compares strings - /// according to the the Swift built-in == and <= operators. - /// - /// This collation is automatically added by GRDB to your database - /// connections. - /// - /// You can use it when creating database tables: - /// - /// let collationName = DatabaseCollation.caseInsensitiveCompare.name - /// dbQueue.execute(sql: """ - /// CREATE TABLE players ( - /// name TEXT COLLATE \(collationName) - /// ) - /// """) - public static let unicodeCompare = DatabaseCollation("swiftCompare") { (lhs, rhs) in - return (lhs < rhs) ? .orderedAscending : ((lhs == rhs) ? .orderedSame : .orderedDescending) - } - - /// A collation, or SQL string comparison function, that compares strings - /// according to the the Swift built-in caseInsensitiveCompare(_:) method. - /// - /// This collation is automatically added by GRDB to your database - /// connections. - /// - /// You can use it when creating database tables: - /// - /// let collationName = DatabaseCollation.caseInsensitiveCompare.name - /// dbQueue.execute(sql: """ - /// CREATE TABLE players ( - /// name TEXT COLLATE \(collationName) - /// ) - /// """) - public static let caseInsensitiveCompare = DatabaseCollation("swiftCaseInsensitiveCompare") { (lhs, rhs) in - return lhs.caseInsensitiveCompare(rhs) - } - - /// A collation, or SQL string comparison function, that compares strings - /// according to the the Swift built-in localizedCaseInsensitiveCompare(_:) method. - /// - /// This collation is automatically added by GRDB to your database - /// connections. - /// - /// You can use it when creating database tables: - /// - /// let collationName = DatabaseCollation.localizedCaseInsensitiveCompare.name - /// dbQueue.execute(sql: """ - /// CREATE TABLE players ( - /// name TEXT COLLATE \(collationName) - /// ) - /// """) - public static let localizedCaseInsensitiveCompare = DatabaseCollation("swiftLocalizedCaseInsensitiveCompare") { (lhs, rhs) in - return lhs.localizedCaseInsensitiveCompare(rhs) - } - - /// A collation, or SQL string comparison function, that compares strings - /// according to the the Swift built-in localizedCompare(_:) method. - /// - /// This collation is automatically added by GRDB to your database - /// connections. - /// - /// You can use it when creating database tables: - /// - /// let collationName = DatabaseCollation.localizedCompare.name - /// dbQueue.execute(sql: """ - /// CREATE TABLE players ( - /// name TEXT COLLATE \(collationName) - /// ) - /// """) - public static let localizedCompare = DatabaseCollation("swiftLocalizedCompare") { (lhs, rhs) in - return lhs.localizedCompare(rhs) - } - - /// A collation, or SQL string comparison function, that compares strings - /// according to the the Swift built-in localizedStandardCompare(_:) method. - /// - /// This collation is automatically added by GRDB to your database - /// connections. - /// - /// You can use it when creating database tables: - /// - /// let collationName = DatabaseCollation.localizedStandardCompare.name - /// dbQueue.execute(sql: """ - /// CREATE TABLE players ( - /// name TEXT COLLATE \(collationName) - /// ) - /// """) - public static let localizedStandardCompare = DatabaseCollation("swiftLocalizedStandardCompare") { (lhs, rhs) in - return lhs.localizedStandardCompare(rhs) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Core/TransactionObserver.swift b/Example/Pods/GRDB.swift/GRDB/Core/TransactionObserver.swift deleted file mode 100755 index 096a246..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Core/TransactionObserver.swift +++ /dev/null @@ -1,1333 +0,0 @@ -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -extension Database { - - // MARK: - Database Observation - - /// Add a transaction observer, so that it gets notified of - /// database changes. - /// - /// - parameter transactionObserver: A transaction observer. - /// - parameter extent: The duration of the observation. The default is - /// the observer lifetime (observation lasts until observer - /// is deallocated). - public func add(transactionObserver: TransactionObserver, extent: TransactionObservationExtent = .observerLifetime) { - SchedulingWatchdog.preconditionValidQueue(self) - observationBroker.add(transactionObserver: transactionObserver, extent: extent) - } - - /// Remove a transaction observer. - public func remove(transactionObserver: TransactionObserver) { - SchedulingWatchdog.preconditionValidQueue(self) - observationBroker.remove(transactionObserver: transactionObserver) - } - - /// Registers a closure to be executed after the next or current - /// transaction completion. - /// - /// try dbQueue.write { db in - /// db.afterNextTransactionCommit { _ in - /// print("success") - /// } - /// ... - /// } // prints "success" - /// - /// If the transaction is rollbacked, the closure is not executed. - /// - /// If the transaction is committed, the closure is executed in a protected - /// dispatch queue, serialized will all database updates. - public func afterNextTransactionCommit(_ closure: @escaping (Database) -> ()) { - class CommitHandler : TransactionObserver { - let closure: (Database) -> () - - init(_ closure: @escaping (Database) -> ()) { - self.closure = closure - } - - // Ignore individual changes and transaction rollbacks - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { return false } - func databaseDidChange(with event: DatabaseEvent) { } - func databaseDidRollback(_ db: Database) { } - - // On commit, run closure - func databaseDidCommit(_ db: Database) { - closure(db) - } - } - - add(transactionObserver: CommitHandler(closure), extent: .nextTransaction) - } - - /// The extent of a transaction observation - /// - /// See Database.add(transactionObserver:extent:) - public enum TransactionObservationExtent { - /// Observation lasts until observer is deallocated - case observerLifetime - /// Observation lasts until the next transaction - case nextTransaction - /// Observation lasts until the database is closed - case databaseLifetime - } -} - -// MARK: - DatabaseObservationBroker - -/// This class provides support for transaction observers. -/// -/// Let's have a detailed look at how a transaction observer is notified: -/// -/// class MyObserver: TransactionObserver { -/// func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool -/// func databaseDidChange(with event: DatabaseEvent) -/// func databaseWillCommit() throws -/// func databaseDidCommit(_ db: Database) -/// func databaseDidRollback(_ db: Database) -/// } -/// -/// First observer is added, and a transaction is started. At this point, -/// there's not much to say: -/// -/// let observer = MyObserver() -/// dbQueue.add(transactionObserver: observer) -/// dbQueue.inDatabase { db in -/// try db.execute(sql: "BEGIN TRANSACTION") -/// -/// Then a statement is executed: -/// -/// try db.execute(sql: "INSERT INTO document ...") -/// -/// The observation process starts when the statement is *compiled*: -/// sqlite3_set_authorizer tells that the statement performs insertion into the -/// `document` table. Generally speaking, statements may have many effects, by -/// the mean of foreign key actions and SQL triggers. SQLite takes care of -/// exposing all those effects to sqlite3_set_authorizer. -/// -/// When the statement is *about to be executed*, the broker queries the -/// observer.observes(eventsOfKind:) method. If it returns true, the observer is -/// *activated*. -/// -/// During the statement *execution*, SQLite tells that a row has been inserted -/// through sqlite3_update_hook: the broker calls the observer.databaseDidChange(with:) -/// method, if and only if the observer has been activated at the previous step. -/// -/// Now a savepoint is started: -/// -/// try db.execute(sql: "SAVEPOINT foo") -/// -/// Statement compilation has sqlite3_set_authorizer tell that this statement -/// begins a "foo" savepoint. -/// -/// After the statement *has been executed*, the broker knows that the SQLite -/// [savepoint stack](https://www.sqlite.org/lang_savepoint.html) contains the -/// "foo" savepoint. -/// -/// Then another statement is executed: -/// -/// try db.execute(sql: "INSERT INTO document ...") -/// -/// This time, when the statement is *executed* and SQLite tells that a row has -/// been inserted, the broker buffers the change event instead of immediately -/// notifying the activated observers. That is because the savepoint can be -/// rollbacked, and GRDB guarantees observers that they are only notified of -/// changes that have an opportunity to be committed. -/// -/// The savepoint is released: -/// -/// try db.execute(sql: "RELEASE SAVEPOINT foo") -/// -/// Statement compilation has sqlite3_set_authorizer tell that this statement -/// releases the "foo" savepoint. -/// -/// After the statement *has been executed*, the broker knows that the SQLite -/// [savepoint stack](https://www.sqlite.org/lang_savepoint.html) is now empty, -/// and notifies the buffered changes to activated observers. -/// -/// Finally the transaction is committed: -/// -/// try db.execute(sql: "COMMIT") -/// -/// During the statement *execution*, SQlite tells the broker that the -/// transaction is about to be committed through sqlite3_commit_hook. The broker -/// invokes observer.databaseWillCommit(). If the observer throws an error, the -/// broker asks SQLite to rollback the transaction. Otherwise, the broker lets -/// the transaction complete. -/// -/// After the statement *has been executed*, the broker calls -/// observer.databaseDidCommit(). -class DatabaseObservationBroker { - private unowned var database: Database - private var savepointStack = SavepointStack() - private var transactionState: TransactionState = .none - private var transactionObservations: [TransactionObservation] = [] - private var statementObservations: [StatementObservation] = [] { - didSet { observesDatabaseChanges = !statementObservations.isEmpty } - } - private var observesDatabaseChanges: Bool = false { - didSet { - if observesDatabaseChanges == oldValue { return } - if observesDatabaseChanges { - installUpdateHook() - } else { - uninstallUpdateHook() - } - } - } - - init(_ database: Database) { - self.database = database - } - - // MARK: - Transaction observers - - func add(transactionObserver: TransactionObserver, extent: Database.TransactionObservationExtent) { - transactionObservations.append(TransactionObservation(observer: transactionObserver, extent: extent)) - } - - func remove(transactionObserver: TransactionObserver) { - transactionObservations.removeFirst { $0.isWrapping(transactionObserver) } - } - - func disableUntilNextTransaction(transactionObserver: TransactionObserver) { - if let observation = transactionObservations.first(where: { $0.isWrapping(transactionObserver)}) { - observation.isDisabled = true - statementObservations.removeFirst { $0.0 === observation } - } - } - - // MARK: - Statement execution - - func updateStatementWillExecute(_ statement: UpdateStatement) { - // As statement executes, it may trigger database changes that will - // be notified to transaction observers. As a consequence, observers - // may disable themselves with stopObservingDatabaseChangesUntilNextTransaction() - // - // This method takes no argument, and requires access to the "current - // broker", which is a per-thread global stored in - // SchedulingWatchdog.current: - SchedulingWatchdog.current!.databaseObservationBroker = self - - // Fill statementObservations with observations that are interested in - // the kind of events performed by the statement. - // - // Those statementObservations will be notified of individual changes - // in databaseWillChange() and databaseDidChange(). - let eventKinds = statement.databaseEventKinds - - // If any observer observes row deletions, we'll have to disable - // [truncate optimization](https://www.sqlite.org/lang_delete.html#truncateopt) - // so that observers are notified. - var observesRowDeletion = false - - switch eventKinds.count { - case 0: - // Statement has no effect on any database table. - // - // For example: PRAGMA foreign_keys = ON - statementObservations = [] - case 1: - // We'll execute a simple statement without any side effect. - // Eventual database events will thus all have the same kind. All - // detabase events can be notified to interested observations. - // - // For example, if one observes all deletions in the table T, then - // all individual deletions of DELETE FROM T are notified: - let eventKind = eventKinds[0] - statementObservations = transactionObservations.compactMap { observation in - guard observation.observes(eventsOfKind: eventKind) else { - // observation is not interested - return nil - } - - if case .delete = eventKind { - observesRowDeletion = true - } - - // observation will be notified of all individual events - return (observation, DatabaseEventPredicate.true) - } - default: - // We'll execute a complex statement with side effects performed by - // an SQL trigger or a foreign key action. Eventual database events - // may not all have the same kind: we need to filter them before - // notifying interested observations. - // - // For example, if DELETE FROM T1 generates deletions in T1 and T2 - // by the mean of a foreign key action, then when one only observes - // deletions in T1, one must not be notified of deletions in T2: - statementObservations = transactionObservations.compactMap { observation in - let observedKinds = eventKinds.filter(observation.observes) - if observedKinds.isEmpty { - // observation is not interested - return nil - } - - for eventKind in observedKinds { - if case .delete = eventKind { - observesRowDeletion = true - break - } - } - - // observation will only be notified of individual events that - // match one of the observed kinds. - return (observation, DatabaseEventPredicate.matching(observedKinds)) - } - } - - if observesRowDeletion { - database.authorizer = TruncateOptimizationBlocker() - } else { - database.authorizer = nil - } - } - - func updateStatementDidFail(_ statement: UpdateStatement) throws { - // Undo updateStatementWillExecute - statementObservations = [] - database.authorizer = nil - SchedulingWatchdog.current!.databaseObservationBroker = nil - - // Reset transactionState before databaseDidRollback eventually - // executes other statements. - let transactionState = self.transactionState - self.transactionState = .none - - switch transactionState { - case .rollback: - // Don't notify observers because we're in a failed implicit - // transaction here (like an INSERT which fails with - // SQLITE_CONSTRAINT error) - databaseDidRollback(notifyTransactionObservers: false) - case .cancelledCommit(let error): - databaseDidRollback(notifyTransactionObservers: true) - throw error - default: - break - } - } - - func updateStatementDidExecute(_ statement: UpdateStatement) throws { - // Undo updateStatementWillExecute - statementObservations = [] - database.authorizer = nil - SchedulingWatchdog.current!.databaseObservationBroker = nil - - // Has statement any effect on transaction/savepoints? - if let transactionEffect = statement.transactionEffect { - switch transactionEffect { - case .beginTransaction: - break - - case .commitTransaction: // 1. A COMMIT statement has been executed - if case .none = self.transactionState { // 2. sqlite3_commit_hook was not triggered - // 1+2 mean that an empty deferred transaction has been completed: - // - // BEGIN DEFERRED TRANSACTION; COMMIT - // - // This special case has a dedicated handling: - return try databaseDidCommitEmptyDeferredTransaction() - } - - case .rollbackTransaction: - break - - case .beginSavepoint(let name): - savepointStack.savepointDidBegin(name) - - case .releaseSavepoint(let name): // 1. A RELEASE SAVEPOINT statement has been executed - savepointStack.savepointDidRelease(name) - - if case .none = self.transactionState, // 2. sqlite3_commit_hook was not triggered - !database.isInsideTransaction // 3. database is no longer inside a transaction - { - // 1+2+3 mean that an empty deferred transaction has been completed: - // - // SAVEPOINT foo; RELEASE SAVEPOINT foo - // - // This special case has a dedicated handling: - return try databaseDidCommitEmptyDeferredTransaction() - } - - if savepointStack.isEmpty { - notifyBufferedEvents() - } - - case .rollbackSavepoint(let name): - savepointStack.savepointDidRollback(name) - } - } - - // Reset transactionState before databaseDidCommit or - // databaseDidRollback eventually execute other statements. - let transactionState = self.transactionState - self.transactionState = .none - - switch transactionState { - case .commit: - databaseDidCommit() - case .rollback: - databaseDidRollback(notifyTransactionObservers: true) - default: - break - } - } - -#if SQLITE_ENABLE_PREUPDATE_HOOK - // Called from sqlite3_preupdate_hook - private func databaseWillChange(with event: DatabasePreUpdateEvent) { - if savepointStack.isEmpty { - // Notify now - for (observation, predicate) in statementObservations where predicate.evaluate(event) { - observation.databaseWillChange(with: event) - } - } else { - // Buffer - savepointStack.eventsBuffer.append((event: event.copy(), statementObservations: statementObservations)) - } - } -#endif - - // Called from sqlite3_update_hook - private func databaseDidChange(with event: DatabaseEvent) { - // We're about to call the databaseDidChange(with:) method of - // transaction observers. In this method, observers may disable - // themselves with stopObservingDatabaseChangesUntilNextTransaction() - // - // This method takes no argument, and requires access to the "current - // broker", which is a per-thread global stored in - // SchedulingWatchdog.current: - assert(SchedulingWatchdog.current?.databaseObservationBroker != nil) - - if savepointStack.isEmpty { - // Notify now - for (observation, predicate) in statementObservations where predicate.evaluate(event) { - observation.databaseDidChange(with: event) - } - } else { - // Buffer - savepointStack.eventsBuffer.append((event: event.copy(), statementObservations: statementObservations)) - } - } - - // MARK: - End of transaction - - // Called from sqlite3_commit_hook and databaseDidCommitEmptyDeferredTransaction() - private func databaseWillCommit() throws { - notifyBufferedEvents() - for observation in transactionObservations { - try observation.databaseWillCommit() - } - } - - // Called from updateStatementDidExecute - private func databaseDidCommit() { - savepointStack.clear() - - for observation in transactionObservations { - observation.databaseDidCommit(database) - } - databaseDidEndTransaction() - } - - // Called from updateStatementDidExecute - private func databaseDidCommitEmptyDeferredTransaction() throws { - // A statement that ends a transaction has been executed. But for - // SQLite, no transaction at all has started, and sqlite3_commit_hook - // was not triggered: - // - // try db.execute(sql: "BEGIN DEFERRED TRANSACTION") - // try db.execute(sql: "COMMIT") // <- no sqlite3_commit_hook callback invocation - // - // Should we tell transaction observers of this transaction, or not? - // The code says that a transaction was open, but SQLite says the - // opposite. How do we lift this ambiguity? Should we notify of - // *transactions expressed in the code*, or *SQLite transactions* only? - // - // If we would notify of SQLite transactions only, then we'd notify of - // all transactions expressed in the code, but empty deferred - // transaction. This means that we'd make an exception. And exceptions - // are the recipe for both surprise and confusion. - // - // For example, is the code below expected to print "did commit"? - // - // db.afterNextTransactionCommit { _ in print("did commit") } - // try db.inTransaction { - // performSomeTask(db) - // return .commit - // } - // - // Yes it is. And the only way to make it reliably print "did commit" is - // to behave consistently, regardless of the implementation of the - // `performSomeTask` function. Even if the `performSomeTask` is empty, - // even if we actually execute an empty deferred transaction. - // - // For better or for worse, let's simulate a transaction: - - do { - try databaseWillCommit() - databaseDidCommit() - } catch { - databaseDidRollback(notifyTransactionObservers: true) - throw error - } - } - - // Called from updateStatementDidExecute or updateStatementDidFails - private func databaseDidRollback(notifyTransactionObservers: Bool) { - savepointStack.clear() - - if notifyTransactionObservers { - for observation in transactionObservations { - observation.databaseDidRollback(database) - } - } - databaseDidEndTransaction() - } - - /// Remove transaction observers that have stopped observing transaction, - /// and uninstall SQLite update hooks if there is no remaining observers. - private func databaseDidEndTransaction() { - transactionObservations = transactionObservations.filter { $0.isObserving } - - // Undo disableUntilNextTransaction(transactionObserver:) - for observation in transactionObservations { - observation.isDisabled = false - } - } - - private func notifyBufferedEvents() { - // We're about to call the databaseDidChange(with:) method of - // transaction observers. In this method, observers may disable - // themselves with stopObservingDatabaseChangesUntilNextTransaction() - // - // This method takes no argument, and requires access to the "current - // broker", which is a per-thread global stored in - // SchedulingWatchdog.current. - // - // Normally, notifyBufferedEvents() is called as part of statement - // execution, and the current broker has been set in - // updateStatementWillExecute(). An assertion should be enough: - // - // assert(SchedulingWatchdog.current?.databaseObservationBroker != nil) - // - // But we have to deal with a particular case: - // - // let journalMode = String.fetchOne(db, sql: "PRAGMA journal_mode = wal") - // - // It runs a SelectStatement, not an UpdateStatement. But this not why - // this case is particular. What is unexpected is that it triggers - // the commit hook when the "PRAGMA journal_mode = wal" statement is - // finalized, long after it has executed: - // - // 1. Statement.deinit() - // 2. sqlite3_finalize() - // 3. commit hook - // 4. DatabaseObservationBroker.databaseWillCommit() - // 5. DatabaseObservationBroker.notifyBufferedEvents() - // - // I don't know if this behavior is something that can be relied - // upon. One would naively expect, for example, that changing the - // journal mode would trigger the commit hook in sqlite3_step(), - // not in sqlite3_finalize(). - // - // Anyway: in this scenario, updateStatementWillExecute() has not been - // called, and the current broker is nil. - // - // Let's not try to outsmart SQLite, and build a complex state machine. - // Instead, let's just make sure that the current broker is set to self - // when this method is called. - - let watchDog = SchedulingWatchdog.current! - watchDog.databaseObservationBroker = self - defer { - watchDog.databaseObservationBroker = nil - } - - // Now we can safely notify: - - let eventsBuffer = savepointStack.eventsBuffer - savepointStack.clear() - - for (event, statementObservations) in eventsBuffer { - for (observation, predicate) in statementObservations where predicate.evaluate(event) { - event.send(to: observation) - } - } - } - - // MARK: - SQLite hooks - - func installCommitAndRollbackHooks() { - let brokerPointer = Unmanaged.passUnretained(self).toOpaque() - - sqlite3_commit_hook(database.sqliteConnection, { brokerPointer in - let broker = Unmanaged.fromOpaque(brokerPointer!).takeUnretainedValue() - do { - try broker.databaseWillCommit() - broker.transactionState = .commit - // Next step: updateStatementDidExecute() - return 0 - } catch { - broker.transactionState = .cancelledCommit(error) - // Next step: sqlite3_rollback_hook callback - return 1 - } - }, brokerPointer) - - sqlite3_rollback_hook(database.sqliteConnection, { brokerPointer in - let broker = Unmanaged.fromOpaque(brokerPointer!).takeUnretainedValue() - switch broker.transactionState { - case .cancelledCommit: - // Next step: updateStatementDidFail() - break - default: - broker.transactionState = .rollback - // Next step: updateStatementDidExecute() - } - }, brokerPointer) - } - - private func installUpdateHook() { - let brokerPointer = Unmanaged.passUnretained(self).toOpaque() - - sqlite3_update_hook(database.sqliteConnection, { (brokerPointer, updateKind, databaseNameCString, tableNameCString, rowID) in - let broker = Unmanaged.fromOpaque(brokerPointer!).takeUnretainedValue() - broker.databaseDidChange(with: DatabaseEvent( - kind: DatabaseEvent.Kind(rawValue: updateKind)!, - rowID: rowID, - databaseNameCString: databaseNameCString, - tableNameCString: tableNameCString)) - }, brokerPointer) - - #if SQLITE_ENABLE_PREUPDATE_HOOK - sqlite3_preupdate_hook(database.sqliteConnection, { (brokerPointer, databaseConnection, updateKind, databaseNameCString, tableNameCString, initialRowID, finalRowID) in - let broker = Unmanaged.fromOpaque(brokerPointer!).takeUnretainedValue() - broker.databaseWillChange(with: DatabasePreUpdateEvent( - connection: databaseConnection!, - kind: DatabasePreUpdateEvent.Kind(rawValue: updateKind)!, - initialRowID: initialRowID, - finalRowID: finalRowID, - databaseNameCString: databaseNameCString, - tableNameCString: tableNameCString)) - }, brokerPointer) - #endif - } - - private func uninstallUpdateHook() { - sqlite3_update_hook(database.sqliteConnection, nil, nil) - #if SQLITE_ENABLE_PREUPDATE_HOOK - sqlite3_preupdate_hook(database.sqliteConnection, nil, nil) - #endif - } - - /// The various states of SQLite transactions - enum TransactionState { - case none - case commit - case rollback - case cancelledCommit(Error) - } -} - -// MARK: - TransactionObserver - -/// A transaction observer is notified of all changes and transactions committed -/// or rollbacked on a database. -/// -/// Adopting types must be a class. -public protocol TransactionObserver : class { - - /// Filters database changes that should be notified the the - /// databaseDidChange(with:) method. - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool - - /// Notifies a database change (insert, update, or delete). - /// - /// The change is pending until the current transaction ends. See - /// databaseWillCommit, databaseDidCommit and databaseDidRollback. - /// - /// This method is called in a protected dispatch queue, serialized will all - /// database updates. - /// - /// The event is only valid for the duration of this method call. If you - /// need to keep it longer, store a copy: `event.copy()` - /// - /// The observer has an opportunity to stop receiving further change events - /// from the current transaction by calling the - /// stopObservingDatabaseChangesUntilNextTransaction() method. - /// - /// - warning: this method must not change the database. - func databaseDidChange(with event: DatabaseEvent) - - /// When a transaction is about to be committed, the transaction observer - /// has an opportunity to rollback pending changes by throwing an error. - /// - /// This method is called on the database queue. - /// - /// - warning: this method must not change the database. - /// - /// - throws: An eventual error that rollbacks pending changes. - func databaseWillCommit() throws - - /// Database changes have been committed. - /// - /// This method is called on the database queue. It can change the database. - func databaseDidCommit(_ db: Database) - - /// Database changes have been rollbacked. - /// - /// This method is called on the database queue. It can change the database. - func databaseDidRollback(_ db: Database) - - #if SQLITE_ENABLE_PREUPDATE_HOOK - /// Notifies before a database change (insert, update, or delete) - /// with change information (initial / final values for the row's - /// columns). (Called *before* databaseDidChangeWithEvent.) - /// - /// The change is pending until the end of the current transaction, - /// and you always get a second chance to get basic event information in - /// the databaseDidChangeWithEvent callback. - /// - /// This callback is mostly useful for calculating detailed change - /// information for a row, and provides the initial / final values. - /// - /// This method is called in a protected dispatch queue, serialized will all - /// database updates. - /// - /// The event is only valid for the duration of this method call. If you - /// need to keep it longer, store a copy: `event.copy()` - /// - /// - warning: this method must not change the database. - /// - /// **Availability Info** - /// - /// Requires SQLite 3.13.0 + - /// Compiled with option SQLITE_ENABLE_PREUPDATE_HOOK - /// - /// As of OSX 10.11.5, and iOS 9.3.2, the built-in SQLite library - /// does not have this enabled, so you'll need to compile your own - /// copy using GRDBCustomSQLite. See https://github.com/groue/GRDB.swift/blob/master/Documentation/CustomSQLiteBuilds.md - /// - /// The databaseDidChangeWithEvent callback is always available, - /// and may provide most/all of what you need. - /// (For example, FetchedRecordsController is built without databaseWillChange) - func databaseWillChange(with event: DatabasePreUpdateEvent) - #endif -} - -extension TransactionObserver { - /// Default implementation does nothing - public func databaseWillCommit() throws { - } - - #if SQLITE_ENABLE_PREUPDATE_HOOK - /// Default implementation does nothing - public func databaseWillChange(with event: DatabasePreUpdateEvent) { - } - #endif - - /// After this method has been called, the `databaseDidChange(with:)` - /// method won't be called until the next transaction. - /// - /// For example: - /// - /// class PlayerObserver: TransactionObserver { - /// var playerTableWasModified = false - /// - /// func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - /// return eventKind.tableName == "player" - /// } - /// - /// func databaseDidChange(with event: DatabaseEvent) { - /// playerTableWasModified = true - /// - /// // It is pointless to keep on tracking further changes: - /// stopObservingDatabaseChangesUntilNextTransaction() - /// } - /// } - /// - /// - precondition: This method must be called from `databaseDidChange(with:)`. - public func stopObservingDatabaseChangesUntilNextTransaction() { - guard let broker = SchedulingWatchdog.current?.databaseObservationBroker else { - fatalError("stopObservingDatabaseChangesUntilNextTransaction must be called from the databaseDidChange method") - } - broker.disableUntilNextTransaction(transactionObserver: self) - } -} - -// MARK: - TransactionObservation - -/// This class manages the observation extent of a transaction observer -final class TransactionObservation { - let extent: Database.TransactionObservationExtent - - // A disabled observation is not interested in individual database changes. - // It is still interested in transactions commits & rollbacks. - var isDisabled: Bool = false - - private weak var weakObserver: TransactionObserver? - private var strongObserver: TransactionObserver? - private var observer: TransactionObserver? { return strongObserver ?? weakObserver } - - fileprivate var isObserving: Bool { - return observer != nil - } - - init(observer: TransactionObserver, extent: Database.TransactionObservationExtent) { - self.extent = extent - switch extent { - case .observerLifetime: - weakObserver = observer - case .nextTransaction: - // This strong reference will be released in databaseDidCommit() and databaseDidRollback() - strongObserver = observer - case .databaseLifetime: - strongObserver = observer - } - } - - func isWrapping(_ observer: TransactionObserver) -> Bool { - return self.observer === observer - } - - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - if isDisabled { return false } - return observer?.observes(eventsOfKind: eventKind) ?? false - } - - #if SQLITE_ENABLE_PREUPDATE_HOOK - func databaseWillChange(with event: DatabasePreUpdateEvent) { - if isDisabled { return } - observer?.databaseWillChange(with: event) - } - #endif - - func databaseDidChange(with event: DatabaseEvent) { - if isDisabled { return } - observer?.databaseDidChange(with: event) - } - - func databaseWillCommit() throws { - try observer?.databaseWillCommit() - } - - func databaseDidCommit(_ db: Database) { - switch extent { - case .observerLifetime, .databaseLifetime: - observer?.databaseDidCommit(db) - case .nextTransaction: - if let observer = self.observer { - // Observer must not get any further notification. - // So we "forget" the observer before its `databaseDidCommit` - // implementation eventually triggers another database change. - strongObserver = nil - observer.databaseDidCommit(db) - } - } - } - - func databaseDidRollback(_ db: Database) { - switch extent { - case .observerLifetime, .databaseLifetime: - observer?.databaseDidRollback(db) - case .nextTransaction: - if let observer = self.observer { - // Observer must not get any further notification. - // So we "forget" the observer before its `databaseDidRollback` - // implementation eventually triggers another database change. - strongObserver = nil - observer.databaseDidRollback(db) - } - } - } -} - -typealias StatementObservation = (TransactionObservation, DatabaseEventPredicate) - -// MARK: - Database events - -/// A kind of database event. See the TransactionObserver protocol for -/// more information. -public enum DatabaseEventKind { - /// The insertion of a row in a database table - case insert(tableName: String) - - /// The deletion of a row in a database table - case delete(tableName: String) - - /// The update of a set of columns in a database table - case update(tableName: String, columnNames: Set) - - var modifiedRegion: DatabaseRegion { - switch self { - case .delete(let tableName): - return DatabaseRegion(table: tableName) - case .insert(let tableName): - return DatabaseRegion(table: tableName) - case .update(let tableName, let updatedColumnNames): - return DatabaseRegion(table: tableName, columns: updatedColumnNames) - } - } -} - -extension DatabaseEventKind { - /// The impacted database table - public var tableName: String { - switch self { - case .insert(tableName: let tableName): return tableName - case .delete(tableName: let tableName): return tableName - case .update(tableName: let tableName, columnNames: _): return tableName - } - } -} - -protocol DatabaseEventProtocol { - func send(to observer: TransactionObservation) - func matchesKind(_ databaseEventKind: DatabaseEventKind) -> Bool -} - -/// A database event, notified to TransactionObserver. -public struct DatabaseEvent { - - /// An event kind - public enum Kind: Int32 { - /// SQLITE_INSERT - case insert = 18 - - /// SQLITE_DELETE - case delete = 9 - - /// SQLITE_UPDATE - case update = 23 - } - - private let impl: DatabaseEventImpl - - /// The event kind - public let kind: Kind - - /// The database name - public var databaseName: String { return impl.databaseName } - - /// The table name - public var tableName: String { return impl.tableName } - - /// The rowID of the changed row. - public let rowID: Int64 - - /// Returns an event that can be stored: - /// - /// class MyObserver: TransactionObserver { - /// var events: [DatabaseEvent] - /// func databaseDidChange(with event: DatabaseEvent) { - /// events.append(event.copy()) - /// } - /// } - public func copy() -> DatabaseEvent { - return impl.copy(self) - } - - fileprivate init(kind: Kind, rowID: Int64, impl: DatabaseEventImpl) { - self.kind = kind - self.rowID = rowID - self.impl = impl - } - - init(kind: Kind, rowID: Int64, databaseNameCString: UnsafePointer?, tableNameCString: UnsafePointer?) { - self.init(kind: kind, rowID: rowID, impl: MetalDatabaseEventImpl(databaseNameCString: databaseNameCString, tableNameCString: tableNameCString)) - } -} - -extension DatabaseEvent : DatabaseEventProtocol { - func send(to observer: TransactionObservation) { - observer.databaseDidChange(with: self) - } - - func matchesKind(_ databaseEventKind: DatabaseEventKind) -> Bool { - switch (kind, databaseEventKind) { - case (.insert, .insert(let tableName)): return self.tableName == tableName - case (.delete, .delete(let tableName)): return self.tableName == tableName - case (.update, .update(let tableName, _)): return self.tableName == tableName - default: - return false - } - } -} - -/// Protocol for internal implementation of DatabaseEvent -private protocol DatabaseEventImpl { - var databaseName: String { get } - var tableName: String { get } - func copy(_ event: DatabaseEvent) -> DatabaseEvent -} - -/// Optimization: MetalDatabaseEventImpl does not create Swift strings from raw -/// SQLite char* until actually asked for databaseName or tableName. -private struct MetalDatabaseEventImpl : DatabaseEventImpl { - let databaseNameCString: UnsafePointer? - let tableNameCString: UnsafePointer? - - var databaseName: String { return String(cString: databaseNameCString!) } - var tableName: String { return String(cString: tableNameCString!) } - func copy(_ event: DatabaseEvent) -> DatabaseEvent { - return DatabaseEvent(kind: event.kind, rowID: event.rowID, impl: CopiedDatabaseEventImpl(databaseName: databaseName, tableName: tableName)) - } -} - -/// Impl for DatabaseEvent that contains copies of event strings. -private struct CopiedDatabaseEventImpl : DatabaseEventImpl { - let databaseName: String - let tableName: String - func copy(_ event: DatabaseEvent) -> DatabaseEvent { - return event - } -} - -#if SQLITE_ENABLE_PREUPDATE_HOOK - - public struct DatabasePreUpdateEvent { - - /// An event kind - public enum Kind: Int32 { - /// SQLITE_INSERT - case insert = 18 - - /// SQLITE_DELETE - case delete = 9 - - /// SQLITE_UPDATE - case update = 23 - } - - /// The event kind - public let kind: Kind - - /// The database name - public var databaseName: String { return impl.databaseName } - - /// The table name - public var tableName: String { return impl.tableName } - - /// The number of columns in the row that is being inserted, updated, or deleted. - public var count: Int { return Int(impl.columnsCount) } - - /// The triggering depth of the row update - /// Returns: - /// 0 if the preupdate callback was invoked as a result of a direct insert, - // update, or delete operation; - /// 1 for inserts, updates, or deletes invoked by top-level triggers; - /// 2 for changes resulting from triggers called by top-level triggers; - /// ... and so forth - public var depth: CInt { return impl.depth } - - /// The initial rowID of the row being changed for .Update and .Delete changes, - /// and nil for .Insert changes. - public let initialRowID: Int64? - - /// The final rowID of the row being changed for .Update and .Insert changes, - /// and nil for .Delete changes. - public let finalRowID: Int64? - - /// The initial database values in the row. - /// - /// Values appear in the same order as the columns in the table. - /// - /// The result is nil if the event is an .Insert event. - public var initialDatabaseValues: [DatabaseValue]? { - guard (kind == .update || kind == .delete) else { return nil } - return impl.initialDatabaseValues - } - - /// Returns the initial `DatabaseValue` at given index. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// The result is nil if the event is an .Insert event. - public func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? { - GRDBPrecondition(index >= 0 && index < count, "row index out of range") - guard (kind == .update || kind == .delete) else { return nil } - return impl.initialDatabaseValue(atIndex: index) - } - - /// The final database values in the row. - /// - /// Values appear in the same order as the columns in the table. - /// - /// The result is nil if the event is a .Delete event. - public var finalDatabaseValues: [DatabaseValue]? { - guard (kind == .update || kind == .insert) else { return nil } - return impl.finalDatabaseValues - } - - /// Returns the final `DatabaseValue` at given index. - /// - /// Indexes span from 0 for the leftmost column to (row.count - 1) for the - /// righmost column. - /// - /// The result is nil if the event is a .Delete event. - public func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? { - GRDBPrecondition(index >= 0 && index < count, "row index out of range") - guard (kind == .update || kind == .insert) else { return nil } - return impl.finalDatabaseValue(atIndex: index) - } - - /// Returns an event that can be stored: - /// - /// class MyObserver: TransactionObserver { - /// var events: [DatabasePreUpdateEvent] - /// func databaseWillChange(with event: DatabasePreUpdateEvent) { - /// events.append(event.copy()) - /// } - /// } - public func copy() -> DatabasePreUpdateEvent { - return impl.copy(self) - } - - fileprivate init(kind: Kind, initialRowID: Int64?, finalRowID: Int64?, impl: DatabasePreUpdateEventImpl) { - self.kind = kind - self.initialRowID = (kind == .update || kind == .delete ) ? initialRowID : nil - self.finalRowID = (kind == .update || kind == .insert ) ? finalRowID : nil - self.impl = impl - } - - init(connection: SQLiteConnection, kind: Kind, initialRowID: Int64, finalRowID: Int64, databaseNameCString: UnsafePointer?, tableNameCString: UnsafePointer?) { - self.init(kind: kind, - initialRowID: (kind == .update || kind == .delete ) ? finalRowID : nil, - finalRowID: (kind == .update || kind == .insert ) ? finalRowID : nil, - impl: MetalDatabasePreUpdateEventImpl(connection: connection, kind: kind, databaseNameCString: databaseNameCString, tableNameCString: tableNameCString)) - } - - private let impl: DatabasePreUpdateEventImpl - } - - extension DatabasePreUpdateEvent : DatabaseEventProtocol { - func send(to observer: TransactionObservation) { - observer.databaseWillChange(with: self) - } - - func matchesKind(_ databaseEventKind: DatabaseEventKind) -> Bool { - switch (kind, databaseEventKind) { - case (.insert, .insert(let tableName)): return self.tableName == tableName - case (.delete, .delete(let tableName)): return self.tableName == tableName - case (.update, .update(let tableName, _)): return self.tableName == tableName - default: - return false - } - } - } - - /// Protocol for internal implementation of DatabaseEvent - private protocol DatabasePreUpdateEventImpl { - var databaseName: String { get } - var tableName: String { get } - - var columnsCount: CInt { get } - var depth: CInt { get } - var initialDatabaseValues: [DatabaseValue]? { get } - var finalDatabaseValues: [DatabaseValue]? { get } - - func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? - func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? - - func copy(_ event: DatabasePreUpdateEvent) -> DatabasePreUpdateEvent - } - - /// Optimization: MetalDatabasePreUpdateEventImpl does not create Swift strings from raw - /// SQLite char* until actually asked for databaseName or tableName, - /// nor does it request other data via the sqlite3_preupdate_* APIs - /// until asked. - private struct MetalDatabasePreUpdateEventImpl : DatabasePreUpdateEventImpl { - let connection: SQLiteConnection - let kind: DatabasePreUpdateEvent.Kind - - let databaseNameCString: UnsafePointer? - let tableNameCString: UnsafePointer? - - var databaseName: String { return String(cString: databaseNameCString!) } - var tableName: String { return String(cString: tableNameCString!) } - - var columnsCount: CInt { return sqlite3_preupdate_count(connection) } - var depth: CInt { return sqlite3_preupdate_depth(connection) } - var initialDatabaseValues: [DatabaseValue]? { - guard (kind == .update || kind == .delete) else { return nil } - return preupdate_getValues_old(connection) - } - - var finalDatabaseValues: [DatabaseValue]? { - guard (kind == .update || kind == .insert) else { return nil } - return preupdate_getValues_new(connection) - } - - func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? { - let columnCount = columnsCount - precondition(index >= 0 && index < Int(columnCount), "row index out of range") - return getValue(connection, column: CInt(index), sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in - return sqlite3_preupdate_old(connection, column, &value) - }) - } - - func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? { - let columnCount = columnsCount - precondition(index >= 0 && index < Int(columnCount), "row index out of range") - return getValue(connection, column: CInt(index), sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in - return sqlite3_preupdate_new(connection, column, &value) - }) - } - - func copy(_ event: DatabasePreUpdateEvent) -> DatabasePreUpdateEvent { - return DatabasePreUpdateEvent(kind: event.kind, initialRowID: event.initialRowID, finalRowID: event.finalRowID, impl: CopiedDatabasePreUpdateEventImpl( - databaseName: databaseName, - tableName: tableName, - columnsCount: columnsCount, - depth: depth, - initialDatabaseValues: initialDatabaseValues, - finalDatabaseValues: finalDatabaseValues)) - } - - private func preupdate_getValues(_ connection: SQLiteConnection, sqlite_func: (_ connection: SQLiteConnection, _ column: CInt, _ value: inout SQLiteValue? ) -> CInt ) -> [DatabaseValue]? { - let columnCount = sqlite3_preupdate_count(connection) - guard columnCount > 0 else { return nil } - - var columnValues = [DatabaseValue]() - - for i in 0.. CInt ) -> DatabaseValue? { - var value : SQLiteValue? = nil - guard sqlite_func(connection, column, &value) == SQLITE_OK else { return nil } - if let value = value { - return DatabaseValue(sqliteValue: value) - } - return nil - } - - private func preupdate_getValues_old(_ connection: SQLiteConnection) -> [DatabaseValue]? { - return preupdate_getValues(connection, sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in - return sqlite3_preupdate_old(connection, column, &value) - }) - } - - private func preupdate_getValues_new(_ connection: SQLiteConnection) -> [DatabaseValue]? { - return preupdate_getValues(connection, sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in - return sqlite3_preupdate_new(connection, column, &value) - }) - } - } - - /// Impl for DatabasePreUpdateEvent that contains copies of all event data. - private struct CopiedDatabasePreUpdateEventImpl : DatabasePreUpdateEventImpl { - let databaseName: String - let tableName: String - let columnsCount: CInt - let depth: CInt - let initialDatabaseValues: [DatabaseValue]? - let finalDatabaseValues: [DatabaseValue]? - - func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? { return initialDatabaseValues?[index] } - func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? { return finalDatabaseValues?[index] } - - func copy(_ event: DatabasePreUpdateEvent) -> DatabasePreUpdateEvent { - return event - } - } - -#endif - -// A predicate that filters database events -enum DatabaseEventPredicate { - // Yes filter - case `true` - // Only events that match one of those kinds - case matching([DatabaseEventKind]) - - func evaluate(_ event: DatabaseEventProtocol) -> Bool { - switch self { - case .true: - return true - case .matching(let kinds): - return kinds.contains { event.matchesKind($0) } - } - } -} - -// MARK: - SavepointStack - -/// The SQLite savepoint stack is described at -/// https://www.sqlite.org/lang_savepoint.html -/// -/// This class reimplements the SQLite stack, so that we can: -/// -/// - know if there are currently active savepoints (isEmpty) -/// - buffer database events when a savepoint is active, in order to avoid -/// notifying transaction observers of database events that could be -/// rollbacked. -class SavepointStack { - /// The buffered events (see DatabaseObservationBroker.databaseDidChange(with:)) - var eventsBuffer: [(event: DatabaseEventProtocol, statementObservations: [StatementObservation])] = [] - - /// The savepoint stack, as an array of tuples (savepointName, index in the eventsBuffer array). - /// Indexes let us drop rollbacked events from the event buffer. - private var savepoints: [(name: String, index: Int)] = [] - - /// If true, there is no current save point. - var isEmpty: Bool { return savepoints.isEmpty } - - func clear() { - eventsBuffer.removeAll() - savepoints.removeAll() - } - - func savepointDidBegin(_ name: String) { - savepoints.append((name: name.lowercased(), index: eventsBuffer.count)) - } - - // https://www.sqlite.org/lang_savepoint.html - // > The ROLLBACK command with a TO clause rolls back transactions going - // > backwards in time back to the most recent SAVEPOINT with a matching - // > name. The SAVEPOINT with the matching name remains on the transaction - // > stack, but all database changes that occurred after that SAVEPOINT was - // > created are rolled back. If the savepoint-name in a ROLLBACK TO - // > command does not match any SAVEPOINT on the stack, then the ROLLBACK - // > command fails with an error and leaves the state of the - // > database unchanged. - func savepointDidRollback(_ name: String) { - let name = name.lowercased() - while let pair = savepoints.last, pair.name != name { - savepoints.removeLast() - } - if let savepoint = savepoints.last { - eventsBuffer.removeLast(eventsBuffer.count - savepoint.index) - } - assert(!savepoints.isEmpty || eventsBuffer.isEmpty) - } - - // https://www.sqlite.org/lang_savepoint.html - // > The RELEASE command starts with the most recent addition to the - // > transaction stack and releases savepoints backwards in time until it - // > releases a savepoint with a matching savepoint-name. Prior savepoints, - // > even savepoints with matching savepoint-names, are unchanged. - func savepointDidRelease(_ name: String) { - let name = name.lowercased() - while let pair = savepoints.last, pair.name != name { - savepoints.removeLast() - } - if !savepoints.isEmpty { - savepoints.removeLast() - } - } -} - diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS3.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS3.swift deleted file mode 100755 index 4bf3abf..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS3.swift +++ /dev/null @@ -1,100 +0,0 @@ -/// FTS3 lets you define "fts3" virtual tables. -/// -/// // CREATE VIRTUAL TABLE document USING fts3(content) -/// try db.create(virtualTable: "document", using: FTS3()) { t in -/// t.column("content") -/// } -public struct FTS3 : VirtualTableModule { - /// Options for Latin script characters. Matches the raw "remove_diacritics" - /// tokenizer argument. - /// - /// See https://www.sqlite.org/fts3.html - public enum Diacritics { - /// Do not remove diacritics from Latin script characters. This - /// option matches the raw "remove_diacritics=0" tokenizer argument. - case keep - /// Remove diacritics from Latin script characters. This - /// option matches the raw "remove_diacritics=1" tokenizer argument. - case removeLegacy - #if GRDBCUSTOMSQLITE - /// Remove diacritics from Latin script characters. This - /// option matches the raw "remove_diacritics=2" tokenizer argument, - /// available from SQLite 3.27.0 - case remove - #endif - } - - /// Creates a FTS3 module suitable for the Database - /// `create(virtualTable:using:)` method. - /// - /// // CREATE VIRTUAL TABLE document USING fts3(content) - /// try db.create(virtualTable: "document", using: FTS3()) { t in - /// t.column("content") - /// } - public init() { - } - - // MARK: - VirtualTableModule Adoption - - /// The virtual table module name - public let moduleName = "fts3" - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func makeTableDefinition() -> FTS3TableDefinition { - return FTS3TableDefinition() - } - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func moduleArguments(for definition: FTS3TableDefinition, in db: Database) -> [String] { - var arguments = definition.columns - if let tokenizer = definition.tokenizer { - if tokenizer.arguments.isEmpty { - arguments.append("tokenize=\(tokenizer.name)") - } else { - arguments.append("tokenize=\(tokenizer.name) " + tokenizer.arguments.map { "\"\($0)\"" as String }.joined(separator: " ")) - } - } - return arguments - } - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func database(_ db: Database, didCreate tableName: String, using definition: FTS3TableDefinition) { - } -} - -/// The FTS3TableDefinition class lets you define columns of a FTS3 virtual table. -/// -/// You don't create instances of this class. Instead, you use the Database -/// `create(virtualTable:using:)` method: -/// -/// try db.create(virtualTable: "document", using: FTS3()) { t in // t is FTS3TableDefinition -/// t.column("content") -/// } -public final class FTS3TableDefinition { - fileprivate var columns: [String] = [] - - /// The virtual table tokenizer - /// - /// try db.create(virtualTable: "document", using: FTS3()) { t in - /// t.tokenizer = .porter - /// } - /// See https://www.sqlite.org/fts3.html#creating_and_destroying_fts_tables - public var tokenizer: FTS3TokenizerDescriptor? - - /// Appends a table column. - /// - /// try db.create(virtualTable: "document", using: FTS3()) { t in - /// t.column("content") - /// } - /// - /// - parameter name: the column name. - public func column(_ name: String) { - columns.append(name) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS3Pattern.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS3Pattern.swift deleted file mode 100755 index 69c43d0..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS3Pattern.swift +++ /dev/null @@ -1,131 +0,0 @@ -/// A full text pattern that can query FTS3 and FTS4 virtual tables. -public struct FTS3Pattern { - - /// The raw pattern string. Guaranteed to be a valid FTS3/4 pattern. - public let rawPattern: String - - /// Creates a pattern from a raw pattern string; throws DatabaseError on - /// invalid syntax. - /// - /// The pattern syntax is documented at https://www.sqlite.org/fts3.html#full_text_index_queries - /// - /// try FTS3Pattern(rawPattern: "and") // OK - /// try FTS3Pattern(rawPattern: "AND") // malformed MATCH expression: [AND] - public init(rawPattern: String) throws { - // Correctness above all: use SQLite to validate the pattern. - // - // Invalid patterns have SQLite return an error on the first - // call to sqlite3_step() on a statement that matches against - // that pattern. - do { - try DatabaseQueue().inDatabase { db in - try db.execute(sql: "CREATE VIRTUAL TABLE documents USING fts3()") - try db.execute(sql: "SELECT * FROM documents WHERE content MATCH ?", arguments: [rawPattern]) - } - } catch let error as DatabaseError { - // Remove private SQL & arguments from the thrown error - throw DatabaseError(resultCode: error.extendedResultCode, message: error.message, sql: nil, arguments: nil) - } - - // Pattern is valid - self.rawPattern = rawPattern - } - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - /// Creates a pattern that matches any token found in the input string; - /// returns nil if no pattern could be built. - /// - /// FTS3Pattern(matchingAnyTokenIn: "") // nil - /// FTS3Pattern(matchingAnyTokenIn: "foo bar") // foo OR bar - /// - /// - parameter string: The string to turn into an FTS3 pattern - public init?(matchingAnyTokenIn string: String) { - let tokens = FTS3TokenizerDescriptor.simple.tokenize(string) - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: tokens.joined(separator: " OR ")) - } - - /// Creates a pattern that matches all tokens found in the input string; - /// returns nil if no pattern could be built. - /// - /// FTS3Pattern(matchingAllTokensIn: "") // nil - /// FTS3Pattern(matchingAllTokensIn: "foo bar") // foo bar - /// - /// - parameter string: The string to turn into an FTS3 pattern - public init?(matchingAllTokensIn string: String) { - let tokens = FTS3TokenizerDescriptor.simple.tokenize(string) - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: tokens.joined(separator: " ")) - } - - /// Creates a pattern that matches a contiguous string; returns nil if no - /// pattern could be built. - /// - /// FTS3Pattern(matchingPhrase: "") // nil - /// FTS3Pattern(matchingPhrase: "foo bar") // "foo bar" - /// - /// - parameter string: The string to turn into an FTS3 pattern - public init?(matchingPhrase string: String) { - let tokens = FTS3TokenizerDescriptor.simple.tokenize(string) - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: "\"" + tokens.joined(separator: " ") + "\"") - } - #else - /// Creates a pattern that matches any token found in the input string; - /// returns nil if no pattern could be built. - /// - /// FTS3Pattern(matchingAnyTokenIn: "") // nil - /// FTS3Pattern(matchingAnyTokenIn: "foo bar") // foo OR bar - /// - /// - parameter string: The string to turn into an FTS3 pattern - @available(OSX 10.10, *) - public init?(matchingAnyTokenIn string: String) { - let tokens = FTS3TokenizerDescriptor.simple.tokenize(string) - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: tokens.joined(separator: " OR ")) - } - - /// Creates a pattern that matches all tokens found in the input string; - /// returns nil if no pattern could be built. - /// - /// FTS3Pattern(matchingAllTokensIn: "") // nil - /// FTS3Pattern(matchingAllTokensIn: "foo bar") // foo bar - /// - /// - parameter string: The string to turn into an FTS3 pattern - @available(OSX 10.10, *) - public init?(matchingAllTokensIn string: String) { - let tokens = FTS3TokenizerDescriptor.simple.tokenize(string) - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: tokens.joined(separator: " ")) - } - - /// Creates a pattern that matches a contiguous string; returns nil if no - /// pattern could be built. - /// - /// FTS3Pattern(matchingPhrase: "") // nil - /// FTS3Pattern(matchingPhrase: "foo bar") // "foo bar" - /// - /// - parameter string: The string to turn into an FTS3 pattern - @available(OSX 10.10, *) - public init?(matchingPhrase string: String) { - let tokens = FTS3TokenizerDescriptor.simple.tokenize(string) - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: "\"" + tokens.joined(separator: " ") + "\"") - } - #endif -} - -extension FTS3Pattern : DatabaseValueConvertible { - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return rawPattern.databaseValue - } - - /// Returns an FTS3Pattern initialized from *dbValue*, if it contains - /// a suitable value. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> FTS3Pattern? { - return String - .fromDatabaseValue(dbValue) - .flatMap { try? FTS3Pattern(rawPattern: $0) } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS3TokenizerDescriptor.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS3TokenizerDescriptor.swift deleted file mode 100755 index 44f667d..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS3TokenizerDescriptor.swift +++ /dev/null @@ -1,130 +0,0 @@ -/// An FTS3 tokenizer, suitable for FTS3 and FTS4 table definitions: -/// -/// db.create(virtualTable: "book", using: FTS4()) { t in -/// t.tokenizer = .simple // FTS3TokenizerDescriptor -/// } -/// -/// See https://www.sqlite.org/fts3.html#tokenizer -public struct FTS3TokenizerDescriptor { - let name: String - let arguments: [String] - - init(_ name: String, arguments: [String] = []) { - self.name = name - self.arguments = arguments - } - - /// The "simple" tokenizer. - /// - /// db.create(virtualTable: "book", using: FTS4()) { t in - /// t.tokenizer = .simple - /// } - /// - /// See https://www.sqlite.org/fts3.html#tokenizer - public static let simple = FTS3TokenizerDescriptor("simple") - - /// The "porter" tokenizer. - /// - /// db.create(virtualTable: "book", using: FTS4()) { t in - /// t.tokenizer = .porter - /// } - /// - /// See https://www.sqlite.org/fts3.html#tokenizer - public static let porter = FTS3TokenizerDescriptor("porter") - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - /// The "unicode61" tokenizer. - /// - /// db.create(virtualTable: "book", using: FTS4()) { t in - /// t.tokenizer = .unicode61() - /// } - /// - /// - parameters: - /// - diacritics: By default SQLite will strip diacritics from - /// latin characters. - /// - separators: Unless empty (the default), SQLite will consider these - /// characters as token separators. - /// - tokenCharacters: Unless empty (the default), SQLite will consider - /// these characters as token characters. - /// - /// See https://www.sqlite.org/fts3.html#tokenizer - public static func unicode61(diacritics: FTS3.Diacritics = .removeLegacy, separators: Set = [], tokenCharacters: Set = []) -> FTS3TokenizerDescriptor { - return _unicode61(diacritics: diacritics, separators: separators, tokenCharacters: tokenCharacters) - } - #else - /// The "unicode61" tokenizer. - /// - /// db.create(virtualTable: "book", using: FTS4()) { t in - /// t.tokenizer = .unicode61() - /// } - /// - /// - parameters: - /// - diacritics: By default SQLite will strip diacritics from - /// latin characters. - /// - separators: Unless empty (the default), SQLite will consider these - /// characters as token separators. - /// - tokenCharacters: Unless empty (the default), SQLite will consider - /// these characters as token characters. - /// - /// See https://www.sqlite.org/fts3.html#tokenizer - @available(OSX 10.10, *) - public static func unicode61(diacritics: FTS3.Diacritics = .removeLegacy, separators: Set = [], tokenCharacters: Set = []) -> FTS3TokenizerDescriptor { - // query_only pragma was added in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - return _unicode61(diacritics: diacritics, separators: separators, tokenCharacters: tokenCharacters) - } - #endif - - private static func _unicode61(diacritics: FTS3.Diacritics, separators: Set = [], tokenCharacters: Set = []) -> FTS3TokenizerDescriptor { - var arguments: [String] = [] - switch diacritics { - case .removeLegacy: - break - case .keep: - arguments.append("remove_diacritics=0") - #if GRDBCUSTOMSQLITE - case .remove: - arguments.append("remove_diacritics=2") - #endif - } - if !separators.isEmpty { - // TODO: test "=" and "\"", "(" and ")" as separators, with both FTS3Pattern(matchingAnyTokenIn:tokenizer:) and Database.create(virtualTable:using:) - arguments.append("separators=" + separators.sorted().map { String($0) }.joined(separator: "")) - } - if !tokenCharacters.isEmpty { - // TODO: test "=" and "\"", "(" and ")" as tokenCharacters, with both FTS3Pattern(matchingAnyTokenIn:tokenizer:) and Database.create(virtualTable:using:) - arguments.append("tokenchars=" + tokenCharacters.sorted().map { String($0) }.joined(separator: "")) - } - return FTS3TokenizerDescriptor("unicode61", arguments: arguments) - } - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - func tokenize(_ string: String) -> [String] { - return _tokenize(string) - } - #else - @available(OSX 10.10, *) - func tokenize(_ string: String) -> [String] { - return _tokenize(string) - } - #endif - - /// Returns an array of tokens found in the string argument. - /// - /// FTS3TokenizerDescriptor.simple.tokenize("foo bar") // ["foo", "bar"] - private func _tokenize(_ string: String) -> [String] { - // fts3tokenize was introduced in SQLite 3.7.17 https://www.sqlite.org/changes.html#version_3_7_17 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - return DatabaseQueue().inDatabase { db in - var tokenizerChunks: [String] = [] - tokenizerChunks.append(name) - for option in arguments { - tokenizerChunks.append("\"\(option)\"") - } - let tokenizerSQL = tokenizerChunks.joined(separator: ", ") - // Assume fts3tokenize virtual table in an in-memory database always succeeds - try! db.execute(sql: "CREATE VIRTUAL TABLE tokens USING fts3tokenize(\(tokenizerSQL))") - return try! String.fetchAll(db, sql: "SELECT token FROM tokens WHERE input = ? ORDER BY position", arguments: [string]) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS4.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS4.swift deleted file mode 100755 index 56dc6e0..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS4.swift +++ /dev/null @@ -1,328 +0,0 @@ -/// FTS4 lets you define "fts4" virtual tables. -/// -/// // CREATE VIRTUAL TABLE document USING fts4(content) -/// try db.create(virtualTable: "document", using: FTS4()) { t in -/// t.column("content") -/// } -/// -/// See https://www.sqlite.org/fts3.html -public struct FTS4 : VirtualTableModule { - - /// Creates a FTS4 module suitable for the Database - /// `create(virtualTable:using:)` method. - /// - /// // CREATE VIRTUAL TABLE document USING fts4(content) - /// try db.create(virtualTable: "document", using: FTS4()) { t in - /// t.column("content") - /// } - /// - /// See https://www.sqlite.org/fts3.html - public init() { - } - - // MARK: - VirtualTableModule Adoption - - /// The virtual table module name - public let moduleName = "fts4" - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func makeTableDefinition() -> FTS4TableDefinition { - return FTS4TableDefinition() - } - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func moduleArguments(for definition: FTS4TableDefinition, in db: Database) -> [String] { - var arguments: [String] = [] - - for column in definition.columns { - if column.isLanguageId { - arguments.append("languageid=\"\(column.name)\"") - } else { - arguments.append(column.name) - if !column.isIndexed { - arguments.append("notindexed=\(column.name)") - } - } - } - - if let tokenizer = definition.tokenizer { - if tokenizer.arguments.isEmpty { - arguments.append("tokenize=\(tokenizer.name)") - } else { - arguments.append("tokenize=\(tokenizer.name) " + tokenizer.arguments.map { "\"\($0)\"" as String }.joined(separator: " ")) - } - } - - switch definition.contentMode { - case .raw(let content): - if let content = content { - arguments.append("content=\"\(content)\"") - } - case .synchronized(let contentTable): - arguments.append("content=\"\(contentTable)\"") - } - - if let compress = definition.compress { - arguments.append("compress=\"\(compress)\"") - } - - if let uncompress = definition.uncompress { - arguments.append("uncompress=\"\(uncompress)\"") - } - - if let matchinfo = definition.matchinfo { - arguments.append("matchinfo=\"\(matchinfo)\"") - } - - if let prefixes = definition.prefixes { - arguments.append("prefix=\"\(prefixes.sorted().map { "\($0)" }.joined(separator: ","))\"") - } - - return arguments - } - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func database(_ db: Database, didCreate tableName: String, using definition: FTS4TableDefinition) throws { - switch definition.contentMode { - case .raw: - break - case .synchronized(let contentTable): - // https://www.sqlite.org/fts3.html#_external_content_fts4_tables_ - - let rowIDColumn = try db.primaryKey(contentTable).rowIDColumn ?? Column.rowID.name - let ftsTable = tableName.quotedDatabaseIdentifier - let content = contentTable.quotedDatabaseIdentifier - let indexedColumns = definition.columns.map { $0.name } - - let ftsColumns = (["docid"] + indexedColumns) - .map { $0.quotedDatabaseIdentifier } - .joined(separator: ", ") - - let newContentColumns = ([rowIDColumn] + indexedColumns) - .map { "new.\($0.quotedDatabaseIdentifier)" } - .joined(separator: ", ") - - let oldRowID = "old.\(rowIDColumn.quotedDatabaseIdentifier)" - - try db.execute(sql: """ - CREATE TRIGGER \("__\(tableName)_bu".quotedDatabaseIdentifier) BEFORE UPDATE ON \(content) BEGIN - DELETE FROM \(ftsTable) WHERE docid=\(oldRowID); - END; - CREATE TRIGGER \("__\(tableName)_bd".quotedDatabaseIdentifier) BEFORE DELETE ON \(content) BEGIN - DELETE FROM \(ftsTable) WHERE docid=\(oldRowID); - END; - CREATE TRIGGER \("__\(tableName)_au".quotedDatabaseIdentifier) AFTER UPDATE ON \(content) BEGIN - INSERT INTO \(ftsTable)(\(ftsColumns)) VALUES(\(newContentColumns)); - END; - CREATE TRIGGER \("__\(tableName)_ai".quotedDatabaseIdentifier) AFTER INSERT ON \(content) BEGIN - INSERT INTO \(ftsTable)(\(ftsColumns)) VALUES(\(newContentColumns)); - END; - """) - - // https://www.sqlite.org/fts3.html#*fts4rebuidcmd - - try db.execute(sql: "INSERT INTO \(ftsTable)(\(ftsTable)) VALUES('rebuild')") - } - } -} - -/// The FTS4TableDefinition class lets you define columns of a FTS4 virtual table. -/// -/// You don't create instances of this class. Instead, you use the Database -/// `create(virtualTable:using:)` method: -/// -/// try db.create(virtualTable: "document", using: FTS4()) { t in // t is FTS4TableDefinition -/// t.column("content") -/// } -/// -/// See https://www.sqlite.org/fts3.html -public final class FTS4TableDefinition { - enum ContentMode { - case raw(content: String?) - case synchronized(contentTable: String) - } - - fileprivate var columns: [FTS4ColumnDefinition] = [] - fileprivate var contentMode: ContentMode = .raw(content: nil) - - /// The virtual table tokenizer - /// - /// try db.create(virtualTable: "document", using: FTS4()) { t in - /// t.tokenizer = .porter - /// } - /// - /// See https://www.sqlite.org/fts3.html#creating_and_destroying_fts_tables - public var tokenizer: FTS3TokenizerDescriptor? - - /// The FTS4 `content` option - /// - /// When you want the full-text table to be synchronized with the - /// content of an external table, prefer the `synchronize(withTable:)` - /// method. - /// - /// Setting this property invalidates any synchronization previously - /// established with the `synchronize(withTable:)` method. - /// - /// See https://www.sqlite.org/fts3.html#the_content_option_ - public var content: String? { - get { - switch contentMode { - case .raw(let content): - return content - case .synchronized(let contentTable): - return contentTable - } - } - set { - contentMode = .raw(content: newValue) - } - } - - /// The FTS4 `compress` option - /// - /// See https://www.sqlite.org/fts3.html#the_compress_and_uncompress_options - public var compress: String? - - /// The FTS4 `uncompress` option - /// - /// See https://www.sqlite.org/fts3.html#the_compress_and_uncompress_options - public var uncompress: String? - - /// The FTS4 `matchinfo` option - /// - /// See https://www.sqlite.org/fts3.html#the_matchinfo_option - public var matchinfo: String? - - /// Support for the FTS5 `prefix` option - /// - /// // CREATE VIRTUAL TABLE document USING FTS4(content, prefix='2 4'); - /// db.create(virtualTable: "document", using:FTS4()) { t in - /// t.prefixes = [2, 4] - /// t.column("content") - /// } - /// - /// See https://www.sqlite.org/fts3.html#the_prefix_option - public var prefixes: Set? - - /// Appends a table column. - /// - /// try db.create(virtualTable: "document", using: FTS4()) { t in - /// t.column("content") - /// } - /// - /// - parameter name: the column name. - @discardableResult - public func column(_ name: String) -> FTS4ColumnDefinition { - let column = FTS4ColumnDefinition(name: name) - columns.append(column) - return column - } - - /// Synchronizes the full-text table with the content of an external - /// table. - /// - /// The full-text table is initially populated with the existing - /// content in the external table. SQL triggers make sure that the - /// full-text table is kept up to date with the external table. - /// - /// See https://sqlite.org/fts5.html#external_content_tables - public func synchronize(withTable tableName: String) { - contentMode = .synchronized(contentTable: tableName) - } -} - -/// The FTS4ColumnDefinition class lets you refine a column of an FTS4 -/// virtual table. -/// -/// You get instances of this class when you create an FTS4 table: -/// -/// try db.create(virtualTable: "document", using: FTS4()) { t in -/// t.column("content") // FTS4ColumnDefinition -/// } -/// -/// See https://www.sqlite.org/fts3.html -public final class FTS4ColumnDefinition { - fileprivate let name: String - fileprivate var isIndexed: Bool - fileprivate var isLanguageId: Bool - - init(name: String) { - self.name = name - self.isIndexed = true - self.isLanguageId = false - } - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - /// Excludes the column from the full-text index. - /// - /// try db.create(virtualTable: "document", using: FTS4()) { t in - /// t.column("a") - /// t.column("b").notIndexed() - /// } - /// - /// See https://www.sqlite.org/fts3.html#the_notindexed_option - /// - /// - returns: Self so that you can further refine the column definition. - /// :nodoc: - @discardableResult - public func notIndexed() -> Self { - // notindexed FTS4 option was added in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - self.isIndexed = false - return self - } - #else - /// Excludes the column from the full-text index. - /// - /// try db.create(virtualTable: "document", using: FTS4()) { t in - /// t.column("a") - /// t.column("b").notIndexed() - /// } - /// - /// See https://www.sqlite.org/fts3.html#the_notindexed_option - /// - /// - returns: Self so that you can further refine the column definition. - @available(OSX 10.10, *) - @discardableResult - public func notIndexed() -> Self { - // notindexed FTS4 option was added in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - self.isIndexed = false - return self - } - #endif - - /// Uses the column as the Int32 language id hidden column. - /// - /// try db.create(virtualTable: "document", using: FTS4()) { t in - /// t.column("a") - /// t.column("lid").asLanguageId() - /// } - /// - /// See https://www.sqlite.org/fts3.html#the_languageid_option - /// - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func asLanguageId() -> Self { - self.isLanguageId = true - return self - } -} - -extension Database { - /// Deletes the synchronization triggers for a synchronized FTS4 table - public func dropFTS4SynchronizationTriggers(forTable tableName: String) throws { - try execute(sql: """ - DROP TRIGGER IF EXISTS \("__\(tableName)_bu".quotedDatabaseIdentifier); - DROP TRIGGER IF EXISTS \("__\(tableName)_bd".quotedDatabaseIdentifier); - DROP TRIGGER IF EXISTS \("__\(tableName)_au".quotedDatabaseIdentifier); - DROP TRIGGER IF EXISTS \("__\(tableName)_ai".quotedDatabaseIdentifier); - """) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS5.swift deleted file mode 100755 index d78c52a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5.swift +++ /dev/null @@ -1,407 +0,0 @@ -#if SQLITE_ENABLE_FTS5 -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// FTS5 lets you define "fts5" virtual tables. -/// -/// // CREATE VIRTUAL TABLE document USING fts5(content) -/// try db.create(virtualTable: "document", using: FTS5()) { t in -/// t.column("content") -/// } -/// -/// See https://www.sqlite.org/fts5.html -public struct FTS5 : VirtualTableModule { - /// Options for Latin script characters. Matches the raw "remove_diacritics" - /// tokenizer argument. - /// - /// See https://www.sqlite.org/fts5.html - public enum Diacritics { - /// Do not remove diacritics from Latin script characters. This - /// option matches the raw "remove_diacritics=0" tokenizer argument. - case keep - /// Remove diacritics from Latin script characters. This - /// option matches the raw "remove_diacritics=1" tokenizer argument. - case removeLegacy - #if GRDBCUSTOMSQLITE - /// Remove diacritics from Latin script characters. This - /// option matches the raw "remove_diacritics=2" tokenizer argument, - /// available from SQLite 3.27.0 - case remove - #endif - } - - /// Creates a FTS5 module suitable for the Database - /// `create(virtualTable:using:)` method. - /// - /// // CREATE VIRTUAL TABLE document USING fts5(content) - /// try db.create(virtualTable: "document", using: FTS5()) { t in - /// t.column("content") - /// } - /// - /// See https://www.sqlite.org/fts5.html - public init() { - } - - // MARK: - VirtualTableModule Adoption - - /// The virtual table module name - public let moduleName = "fts5" - - /// Don't use this method. - public func makeTableDefinition() -> FTS5TableDefinition { - return FTS5TableDefinition() - } - - /// Don't use this method. - public func moduleArguments(for definition: FTS5TableDefinition, in db: Database) throws -> [String] { - var arguments: [String] = [] - - if definition.columns.isEmpty { - // Programmer error - fatalError("FTS5 virtual table requires at least one column.") - } - - for column in definition.columns { - if column.isIndexed { - arguments.append("\(column.name)") - } else { - arguments.append("\(column.name) UNINDEXED") - } - } - - if let tokenizer = definition.tokenizer { - arguments.append("tokenize=\(tokenizer.components.joined(separator: " ").sqlExpression.quotedSQL())") - } - - switch definition.contentMode { - case .raw(let content, let contentRowID): - if let content = content { - arguments.append("content=\(content.sqlExpression.quotedSQL())") - } - if let contentRowID = contentRowID { - arguments.append("content_rowid=\(contentRowID.sqlExpression.quotedSQL())") - } - case .synchronized(let contentTable): - arguments.append("content=\(contentTable.sqlExpression.quotedSQL())") - if let rowIDColumn = try db.primaryKey(contentTable).rowIDColumn { - arguments.append("content_rowid=\(rowIDColumn.sqlExpression.quotedSQL())") - } - } - - - if let prefixes = definition.prefixes { - arguments.append("prefix=\(prefixes.sorted().map { "\($0)" }.joined(separator: " ").sqlExpression.quotedSQL())") - } - - if let columnSize = definition.columnSize { - arguments.append("columnSize=\(columnSize)") - } - - if let detail = definition.detail { - arguments.append("detail=\(detail)") - } - - return arguments - } - - /// Reserved; part of the VirtualTableModule protocol. - /// - /// See Database.create(virtualTable:using:) - public func database(_ db: Database, didCreate tableName: String, using definition: FTS5TableDefinition) throws { - switch definition.contentMode { - case .raw: - break - case .synchronized(let contentTable): - // https://sqlite.org/fts5.html#external_content_tables - - let rowIDColumn = try db.primaryKey(contentTable).rowIDColumn ?? Column.rowID.name - let ftsTable = tableName.quotedDatabaseIdentifier - let content = contentTable.quotedDatabaseIdentifier - let indexedColumns = definition.columns.map { $0.name } - - let ftsColumns = (["rowid"] + indexedColumns) - .map { $0.quotedDatabaseIdentifier } - .joined(separator: ", ") - - let newContentColumns = ([rowIDColumn] + indexedColumns) - .map { "new.\($0.quotedDatabaseIdentifier)" } - .joined(separator: ", ") - - let oldContentColumns = ([rowIDColumn] + indexedColumns) - .map { "old.\($0.quotedDatabaseIdentifier)" } - .joined(separator: ", ") - - try db.execute(sql: """ - CREATE TRIGGER \("__\(tableName)_ai".quotedDatabaseIdentifier) AFTER INSERT ON \(content) BEGIN - INSERT INTO \(ftsTable)(\(ftsColumns)) VALUES (\(newContentColumns)); - END; - CREATE TRIGGER \("__\(tableName)_ad".quotedDatabaseIdentifier) AFTER DELETE ON \(content) BEGIN - INSERT INTO \(ftsTable)(\(ftsTable), \(ftsColumns)) VALUES('delete', \(oldContentColumns)); - END; - CREATE TRIGGER \("__\(tableName)_au".quotedDatabaseIdentifier) AFTER UPDATE ON \(content) BEGIN - INSERT INTO \(ftsTable)(\(ftsTable), \(ftsColumns)) VALUES('delete', \(oldContentColumns)); - INSERT INTO \(ftsTable)(\(ftsColumns)) VALUES (\(newContentColumns)); - END; - """) - - // https://sqlite.org/fts5.html#the_rebuild_command - - try db.execute(sql: "INSERT INTO \(ftsTable)(\(ftsTable)) VALUES('rebuild')") - } - } - - static func api(_ db: Database) -> UnsafePointer { - // Access to FTS5 is one of the rare SQLite api which was broken in - // SQLite 3.20.0+, for security reasons: - // - // Starting SQLite 3.20.0+, we need to use the new sqlite3_bind_pointer api. - // The previous way to access FTS5 does not work any longer. - // - // So let's see which SQLite version we are linked against: - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - // GRDB is linked against SQLCipher or a custom SQLite build: SQLite 3.20.0 or more. - return api_v2(db, sqlite3_prepare_v3, sqlite3_bind_pointer) - #else - // GRDB is linked against the system SQLite. - // - // Do we use SQLite 3.19.3 (iOS 11.4), or SQLite 3.24.0 (iOS 12.0)? - if #available(iOS 12.0, OSX 10.14, watchOS 5.0, *) { - // SQLite 3.24.0 or more - return api_v2(db, sqlite3_prepare_v3, sqlite3_bind_pointer) - } else { - // SQLite 3.19.3 or less - return api_v1(db) - } - #endif - } - - private static func api_v1(_ db: Database) -> UnsafePointer { - guard let data = try! Data.fetchOne(db, sql: "SELECT fts5()") else { - fatalError("FTS5 is not available") - } - #if swift(>=5.0) - return data.withUnsafeBytes { - $0.bindMemory(to: UnsafePointer.self).first! - } - #else - return data.withUnsafeBytes { - $0.pointee - } - #endif - } - - // Technique given by Jordan Rose: - // https://forums.swift.org/t/c-interoperability-combinations-of-library-and-os-versions/14029/4 - private static func api_v2( - _ db: Database, - _ sqlite3_prepare_v3: @convention(c) (OpaquePointer?, UnsafePointer?, Int32, UInt32, UnsafeMutablePointer?, UnsafeMutablePointer?>?) -> Int32, - _ sqlite3_bind_pointer: @convention(c) (OpaquePointer?, Int32, UnsafeMutableRawPointer?, UnsafePointer?, (@convention(c) (UnsafeMutableRawPointer?) -> Void)?) -> Int32) - -> UnsafePointer - { - let sqliteConnection = db.sqliteConnection - var statement: SQLiteStatement? = nil - var api: UnsafePointer? = nil - let type: StaticString = "fts5_api_ptr" - - let code = sqlite3_prepare_v3(db.sqliteConnection, "SELECT fts5(?)", -1, 0, &statement, nil) - guard code == SQLITE_OK else { - fatalError("FTS5 is not available") - } - defer { sqlite3_finalize(statement) } - type.utf8Start.withMemoryRebound(to: Int8.self, capacity: type.utf8CodeUnitCount) { typePointer in - _ = sqlite3_bind_pointer(statement, 1, &api, typePointer, nil) - } - sqlite3_step(statement) - guard let result = api else { - fatalError("FTS5 is not available") - } - return result - } -} - -/// The FTS5TableDefinition class lets you define columns of a FTS5 virtual table. -/// -/// You don't create instances of this class. Instead, you use the Database -/// `create(virtualTable:using:)` method: -/// -/// try db.create(virtualTable: "document", using: FTS5()) { t in // t is FTS5TableDefinition -/// t.column("content") -/// } -/// -/// See https://www.sqlite.org/fts5.html -public final class FTS5TableDefinition { - enum ContentMode { - case raw(content: String?, contentRowID: String?) - case synchronized(contentTable: String) - } - - fileprivate var columns: [FTS5ColumnDefinition] = [] - fileprivate var contentMode: ContentMode = .raw(content: nil, contentRowID: nil) - - /// The virtual table tokenizer - /// - /// try db.create(virtualTable: "document", using: FTS5()) { t in - /// t.tokenizer = .porter() - /// } - /// - /// See https://www.sqlite.org/fts5.html#fts5_table_creation_and_initialization - public var tokenizer: FTS5TokenizerDescriptor? - - /// The FTS5 `content` option - /// - /// When you want the full-text table to be synchronized with the - /// content of an external table, prefer the `synchronize(withTable:)` - /// method. - /// - /// Setting this property invalidates any synchronization previously - /// established with the `synchronize(withTable:)` method. - /// - /// See https://www.sqlite.org/fts5.html#external_content_and_contentless_tables - public var content: String? { - get { - switch contentMode { - case .raw(let content, _): - return content - case .synchronized(let contentTable): - return contentTable - } - } - set { - switch contentMode { - case .raw(_, let contentRowID): - contentMode = .raw(content: newValue, contentRowID: contentRowID) - case .synchronized: - contentMode = .raw(content: newValue, contentRowID: nil) - } - } - } - - /// The FTS5 `content_rowid` option - /// - /// When you want the full-text table to be synchronized with the - /// content of an external table, prefer the `synchronize(withTable:)` - /// method. - /// - /// Setting this property invalidates any synchronization previously - /// established with the `synchronize(withTable:)` method. - /// - /// See https://sqlite.org/fts5.html#external_content_tables - public var contentRowID: String? { - get { - switch contentMode { - case .raw(_, let contentRowID): - return contentRowID - case .synchronized: - return nil - } - } - set { - switch contentMode { - case .raw(let content, _): - contentMode = .raw(content: content, contentRowID: newValue) - case .synchronized: - contentMode = .raw(content: nil, contentRowID: newValue) - } - } - } - - /// Support for the FTS5 `prefix` option - /// - /// See https://www.sqlite.org/fts5.html#prefix_indexes - public var prefixes: Set? - - /// Support for the FTS5 `columnsize` option - /// - /// https://www.sqlite.org/fts5.html#the_columnsize_option - public var columnSize: Int? - - /// Support for the FTS5 `detail` option - /// - /// https://www.sqlite.org/fts5.html#the_detail_option - public var detail: String? - - /// Appends a table column. - /// - /// try db.create(virtualTable: "document", using: FTS5()) { t in - /// t.column("content") - /// } - /// - /// - parameter name: the column name. - @discardableResult - public func column(_ name: String) -> FTS5ColumnDefinition { - let column = FTS5ColumnDefinition(name: name) - columns.append(column) - return column - } - - /// Synchronizes the full-text table with the content of an external - /// table. - /// - /// The full-text table is initially populated with the existing - /// content in the external table. SQL triggers make sure that the - /// full-text table is kept up to date with the external table. - /// - /// See https://sqlite.org/fts5.html#external_content_tables - public func synchronize(withTable tableName: String) { - contentMode = .synchronized(contentTable: tableName) - } -} - -/// The FTS5ColumnDefinition class lets you refine a column of an FTS5 -/// virtual table. -/// -/// You get instances of this class when you create an FTS5 table: -/// -/// try db.create(virtualTable: "document", using: FTS5()) { t in -/// t.column("content") // FTS5ColumnDefinition -/// } -/// -/// See https://www.sqlite.org/fts5.html -public final class FTS5ColumnDefinition { - fileprivate let name: String - fileprivate var isIndexed: Bool - - init(name: String) { - self.name = name - self.isIndexed = true - } - - /// Excludes the column from the full-text index. - /// - /// try db.create(virtualTable: "document", using: FTS5()) { t in - /// t.column("a") - /// t.column("b").notIndexed() - /// } - /// - /// See https://www.sqlite.org/fts5.html#the_unindexed_column_option - /// - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func notIndexed() -> Self { - self.isIndexed = false - return self - } -} - -extension Column { - /// The FTS5 rank column - public static let rank = Column("rank") -} - -extension Database { - /// Deletes the synchronization triggers for a synchronized FTS5 table - public func dropFTS5SynchronizationTriggers(forTable tableName: String) throws { - try execute(sql: """ - DROP TRIGGER IF EXISTS \("__\(tableName)_ai".quotedDatabaseIdentifier); - DROP TRIGGER IF EXISTS \("__\(tableName)_ad".quotedDatabaseIdentifier); - DROP TRIGGER IF EXISTS \("__\(tableName)_au".quotedDatabaseIdentifier); - """) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5CustomTokenizer.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS5CustomTokenizer.swift deleted file mode 100755 index 9d3a545..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5CustomTokenizer.swift +++ /dev/null @@ -1,161 +0,0 @@ -#if SQLITE_ENABLE_FTS5 -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// The protocol for custom FTS5 tokenizers. -public protocol FTS5CustomTokenizer : FTS5Tokenizer { - /// The name of the tokenizer; should uniquely identify your custom - /// tokenizer. - static var name: String { get } - - /// Creates a custom tokenizer. - /// - /// The arguments parameter is an array of String built from the CREATE - /// VIRTUAL TABLE statement. In the example below, the arguments will - /// be `["arg1", "arg2"]`. - /// - /// CREATE VIRTUAL TABLE document USING fts5( - /// tokenize='custom arg1 arg2' - /// ) - /// - /// - parameter db: A Database connection - /// - parameter arguments: An array of string arguments - init(db: Database, arguments: [String]) throws -} - -extension FTS5CustomTokenizer { - - /// Creates an FTS5 tokenizer descriptor. - /// - /// class MyTokenizer : FTS5CustomTokenizer { ... } - /// - /// db.create(virtualTable: "book", using: FTS5()) { t in - /// let tokenizer = MyTokenizer.tokenizerDescriptor(arguments: ["unicode61", "remove_diacritics", "0"]) - /// t.tokenizer = tokenizer - /// } - public static func tokenizerDescriptor(arguments: [String] = []) -> FTS5TokenizerDescriptor { - return FTS5TokenizerDescriptor(components: [name] + arguments) - } - -} - -extension Database { - - // MARK: - FTS5 - - private class FTS5TokenizerConstructor { - let db: Database - let constructor: (Database, [String], UnsafeMutablePointer?) -> Int32 - - init(db: Database, constructor: @escaping (Database, [String], UnsafeMutablePointer?) -> Int32) { - self.db = db - self.constructor = constructor - } - } - - /// Add a custom FTS5 tokenizer. - /// - /// class MyTokenizer : FTS5CustomTokenizer { ... } - /// db.add(tokenizer: MyTokenizer.self) - public func add(tokenizer: Tokenizer.Type) { - let api = FTS5.api(self) - - // Swift won't let the @convention(c) xCreate() function below create - // an instance of the generic Tokenizer type. - // - // We thus hide the generic Tokenizer type inside a neutral type: - // FTS5TokenizerConstructor - let constructor = FTS5TokenizerConstructor( - db: self, - constructor: { (db, arguments, tokenizerHandle) in - guard let tokenizerHandle = tokenizerHandle else { - return SQLITE_ERROR - } - do { - let tokenizer = try Tokenizer(db: db, arguments: arguments) - - // Tokenizer must remain alive until xDeleteTokenizer() - // is called, as the xDelete member of xTokenizer - let tokenizerPointer = OpaquePointer(Unmanaged.passRetained(tokenizer).toOpaque()) - - tokenizerHandle.pointee = tokenizerPointer - return SQLITE_OK - } catch let error as DatabaseError { - return error.extendedResultCode.rawValue - } catch { - return SQLITE_ERROR - } - }) - - // Constructor must remain alive until deleteConstructor() is - // called, as the last argument of the xCreateTokenizer() function. - let constructorPointer = Unmanaged.passRetained(constructor).toOpaque() - - func deleteConstructor(constructorPointer: UnsafeMutableRawPointer?) { - guard let constructorPointer = constructorPointer else { return } - Unmanaged.fromOpaque(constructorPointer).release() - } - - func xCreateTokenizer(constructorPointer: UnsafeMutableRawPointer?, azArg: UnsafeMutablePointer?>?, nArg: Int32, tokenizerHandle: UnsafeMutablePointer?) -> Int32 { - guard let constructorPointer = constructorPointer else { - return SQLITE_ERROR - } - let constructor = Unmanaged.fromOpaque(constructorPointer).takeUnretainedValue() - var arguments: [String] = [] - if let azArg = azArg { - for i in 0...fromOpaque(UnsafeMutableRawPointer(tokenizerPointer)).release() - } - - func xTokenize(tokenizerPointer: OpaquePointer?, context: UnsafeMutableRawPointer?, flags: Int32, pText: UnsafePointer?, nText: Int32, tokenCallback: (@convention(c) (UnsafeMutableRawPointer?, Int32, UnsafePointer?, Int32, Int32, Int32) -> Int32)?) -> Int32 { - guard let tokenizerPointer = tokenizerPointer else { - return SQLITE_ERROR - } - let object = Unmanaged.fromOpaque(UnsafeMutableRawPointer(tokenizerPointer)).takeUnretainedValue() - guard let tokenizer = object as? FTS5Tokenizer else { - return SQLITE_ERROR - } - return tokenizer.tokenize(context: context, tokenization: FTS5Tokenization(rawValue: flags), pText: pText, nText: nText, tokenCallback: tokenCallback!) - } - - var xTokenizer = fts5_tokenizer(xCreate: xCreateTokenizer, xDelete: xDeleteTokenizer, xTokenize: xTokenize) - let code = withUnsafeMutablePointer(to: &xTokenizer) { xTokenizerPointer in - api.pointee.xCreateTokenizer(UnsafeMutablePointer(mutating: api), Tokenizer.name, constructorPointer, xTokenizerPointer, deleteConstructor) - } - guard code == SQLITE_OK else { - // Assume a GRDB bug: there is no point throwing any error. - fatalError(DatabaseError(resultCode: code, message: lastErrorMessage).description) - } - } -} - -extension DatabaseQueue { - - // MARK: - Custom FTS5 Tokenizers - - /// Add a custom FTS5 tokenizer. - /// - /// class MyTokenizer : FTS5CustomTokenizer { ... } - /// dbQueue.add(tokenizer: MyTokenizer.self) - public func add(tokenizer: Tokenizer.Type) { - inDatabase { db in - db.add(tokenizer: Tokenizer.self) - } - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5Pattern.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS5Pattern.swift deleted file mode 100755 index c67558f..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5Pattern.swift +++ /dev/null @@ -1,121 +0,0 @@ -#if SQLITE_ENABLE_FTS5 -/// A full text pattern that can query FTS5 virtual tables. -public struct FTS5Pattern { - - /// The raw pattern string. Guaranteed to be a valid FTS5 pattern. - public let rawPattern: String - - /// Creates a pattern that matches any token found in the input string; - /// returns nil if no pattern could be built. - /// - /// FTS5Pattern(matchingAnyTokenIn: "") // nil - /// FTS5Pattern(matchingAnyTokenIn: "foo bar") // foo OR bar - /// - /// - parameter string: The string to turn into an FTS5 pattern - public init?(matchingAnyTokenIn string: String) { - guard let tokens = try? DatabaseQueue().inDatabase({ db in try db.makeTokenizer(.ascii()).nonSynonymTokens(in: string, for: .query) }) else { return nil } - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: tokens.joined(separator: " OR ")) - } - - /// Creates a pattern that matches all tokens found in the input string; - /// returns nil if no pattern could be built. - /// - /// FTS5Pattern(matchingAllTokensIn: "") // nil - /// FTS5Pattern(matchingAllTokensIn: "foo bar") // foo bar - /// - /// - parameter string: The string to turn into an FTS5 pattern - public init?(matchingAllTokensIn string: String) { - guard let tokens = try? DatabaseQueue().inDatabase({ db in try db.makeTokenizer(.ascii()).nonSynonymTokens(in: string, for: .query) }) else { return nil } - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: tokens.joined(separator: " ")) - } - - /// Creates a pattern that matches a contiguous string; returns nil if no - /// pattern could be built. - /// - /// FTS5Pattern(matchingPhrase: "") // nil - /// FTS5Pattern(matchingPhrase: "foo bar") // "foo bar" - /// - /// - parameter string: The string to turn into an FTS5 pattern - public init?(matchingPhrase string: String) { - guard let tokens = try? DatabaseQueue().inDatabase({ db in try db.makeTokenizer(.ascii()).nonSynonymTokens(in: string, for: .query) }) else { return nil } - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: "\"" + tokens.joined(separator: " ") + "\"") - } - - /// Creates a pattern that matches a contiguous string prefix; returns - /// nil if no pattern could be built. - /// - /// FTS5Pattern(matchingPrefixPhrase: "") // nil - /// FTS5Pattern(matchingPrefixPhrase: "foo bar") // ^"foo bar" - /// - /// - parameter string: The string to turn into an FTS5 pattern - public init?(matchingPrefixPhrase string: String) { - guard let tokens = try? DatabaseQueue().inDatabase({ db in try db.makeTokenizer(.ascii()).nonSynonymTokens(in: string, for: .query) }) else { return nil } - guard !tokens.isEmpty else { return nil } - try? self.init(rawPattern: "^\"" + tokens.joined(separator: " ") + "\"") - } - - init(rawPattern: String, allowedColumns: [String] = []) throws { - // Correctness above all: use SQLite to validate the pattern. - // - // Invalid patterns have SQLite return an error on the first - // call to sqlite3_step() on a statement that matches against - // that pattern. - do { - try DatabaseQueue().inDatabase { db in - try db.create(virtualTable: "document", using: FTS5()) { t in - if allowedColumns.isEmpty { - t.column("__grdb__") - } else { - for column in allowedColumns { - t.column(column) - } - } - } - try db.makeSelectStatement(sql: "SELECT * FROM document WHERE document MATCH ?") - .makeCursor(arguments: [rawPattern]) - .next() // error on next() for invalid patterns - } - } catch let error as DatabaseError { - // Remove private SQL & arguments from the thrown error - throw DatabaseError(resultCode: error.extendedResultCode, message: error.message, sql: nil, arguments: nil) - } - - // Pattern is valid - self.rawPattern = rawPattern - } -} - -extension Database { - - // MARK: - FTS5 - - /// Creates a pattern from a raw pattern string; throws DatabaseError on - /// invalid syntax. - /// - /// The pattern syntax is documented at https://www.sqlite.org/fts5.html#full_text_query_syntax - /// - /// try db.makeFTS5Pattern(rawPattern: "and", forTable: "document") // OK - /// try db.makeFTS5Pattern(rawPattern: "AND", forTable: "document") // malformed MATCH expression: [AND] - public func makeFTS5Pattern(rawPattern: String, forTable table: String) throws -> FTS5Pattern { - return try FTS5Pattern(rawPattern: rawPattern, allowedColumns: columns(in: table).map { $0.name }) - } -} - -extension FTS5Pattern : DatabaseValueConvertible { - /// Returns a value that can be stored in the database. - public var databaseValue: DatabaseValue { - return rawPattern.databaseValue - } - - /// Returns an FTS5Pattern initialized from *dbValue*, if it - /// contains a suitable value. - public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> FTS5Pattern? { - return String - .fromDatabaseValue(dbValue) - .flatMap { try? FTS5Pattern(rawPattern: $0) } - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5Tokenizer.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS5Tokenizer.swift deleted file mode 100755 index d5dd44d..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5Tokenizer.swift +++ /dev/null @@ -1,222 +0,0 @@ -#if SQLITE_ENABLE_FTS5 -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// A low-level SQLite function that lets FTS5Tokenizer notify tokens. -/// -/// See FTS5Tokenizer.tokenize(context:flags:pText:nText:tokenCallback:) -public typealias FTS5TokenCallback = @convention(c) (_ context: UnsafeMutableRawPointer?, _ flags: Int32, _ pToken: UnsafePointer?, _ nToken: Int32, _ iStart: Int32, _ iEnd: Int32) -> Int32 - -/// The reason why FTS5 is requesting tokenization. -/// -/// See https://www.sqlite.org/fts5.html#custom_tokenizers -public struct FTS5Tokenization : OptionSet { - public let rawValue: Int32 - - public init(rawValue: Int32) { - self.rawValue = rawValue - } - - /// FTS5_TOKENIZE_QUERY - public static let query = FTS5Tokenization(rawValue: FTS5_TOKENIZE_QUERY) - - /// FTS5_TOKENIZE_PREFIX - public static let prefix = FTS5Tokenization(rawValue: FTS5_TOKENIZE_PREFIX) - - /// FTS5_TOKENIZE_DOCUMENT - public static let document = FTS5Tokenization(rawValue: FTS5_TOKENIZE_DOCUMENT) - - /// FTS5_TOKENIZE_AUX - public static let aux = FTS5Tokenization(rawValue: FTS5_TOKENIZE_AUX) -} - -/// The protocol for FTS5 tokenizers -public protocol FTS5Tokenizer : class { - /// Tokenizes the text described by `pText` and `nText`, and - /// notifies found tokens to the `tokenCallback` function. - /// - /// It matches the `xTokenize` function documented at https://www.sqlite.org/fts5.html#custom_tokenizers - /// - /// - parameters: - /// - context: An opaque pointer that is the first argument to - /// the `tokenCallback` function - /// - tokenization: The reason why FTS5 is requesting tokenization. - /// - pText: The tokenized text bytes. May or may not be - /// nul-terminated. - /// - nText: The number of bytes in the tokenized text. - /// - tokenCallback: The function to call for each found token. - /// It matches the `xToken` callback at https://www.sqlite.org/fts5.html#custom_tokenizers - func tokenize(context: UnsafeMutableRawPointer?, tokenization: FTS5Tokenization, pText: UnsafePointer?, nText: Int32, tokenCallback: @escaping FTS5TokenCallback) -> Int32 -} - -private class TokenizeContext { - var tokens: [(String, FTS5TokenFlags)] = [] -} - -extension FTS5Tokenizer { - - /// Tokenizes the string argument into an array of - /// (String, FTS5TokenFlags) pairs. - /// - /// let tokenizer = try db.makeTokenizer(.ascii()) - /// try tokenizer.tokenize("foo bar", for: .document) // [("foo", flags), ("bar", flags)] - /// - /// - parameter string: The string to tokenize - /// - parameter tokenization: The reason why tokenization is requested: - /// - .document: Tokenize like a document being inserted into an FTS table. - /// - .query: Tokenize like the search pattern of the MATCH operator. - /// - parameter tokenizer: A FTS5TokenizerDescriptor such as .ascii() - func tokenize(_ string: String, for tokenization: FTS5Tokenization) throws -> [(String, FTS5TokenFlags)] { - return try ContiguousArray(string.utf8).withUnsafeBufferPointer { buffer -> [(String, FTS5TokenFlags)] in - guard let addr = buffer.baseAddress else { - return [] - } - let pText = UnsafeMutableRawPointer(mutating: addr).assumingMemoryBound(to: Int8.self) - let nText = Int32(buffer.count) - - var context = TokenizeContext() - try withUnsafeMutablePointer(to: &context) { contextPointer in - let code = tokenize(context: UnsafeMutableRawPointer(contextPointer), tokenization: tokenization, pText: pText, nText: nText, tokenCallback: { (contextPointer, flags, pToken, nToken, iStart, iEnd) -> Int32 in - guard let contextPointer = contextPointer else { return SQLITE_ERROR } - - // Extract token - guard let token = pToken.flatMap({ String(data: Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: $0), count: Int(nToken), deallocator: .none), encoding: .utf8) }) else { - return SQLITE_OK - } - - let context = contextPointer.assumingMemoryBound(to: TokenizeContext.self).pointee - context.tokens.append((token, FTS5TokenFlags(rawValue: flags))) - - return SQLITE_OK - }) - if (code != SQLITE_OK) { - throw DatabaseError(resultCode: code) - } - } - return context.tokens - } - } - - func nonSynonymTokens(in string: String, for tokenization: FTS5Tokenization) throws -> [String] { - var tokens: [String] = [] - for (token, flags) in try tokenize(string, for: tokenization) { - if !flags.contains(.colocated) { - tokens.append(token) - } - } - return tokens - } -} - -extension Database { - - // MARK: - FTS5 - - /// Private type that makes a pre-registered FTS5 tokenizer available - /// through the FTS5Tokenizer protocol. - private final class FTS5RegisteredTokenizer : FTS5Tokenizer { - let xTokenizer: fts5_tokenizer - let tokenizerPointer: OpaquePointer - - init(xTokenizer: fts5_tokenizer, contextPointer: UnsafeMutableRawPointer?, arguments: [String]) throws { - guard let xCreate = xTokenizer.xCreate else { - throw DatabaseError(resultCode: .SQLITE_ERROR, message: "nil fts5_tokenizer.xCreate") - } - - self.xTokenizer = xTokenizer - - var tokenizerPointer: OpaquePointer? = nil - let code: Int32 - if let argument = arguments.first { - // Turn [String] into ContiguousArray> - // (for an alternative implementation see https://oleb.net/blog/2016/10/swift-array-of-c-strings/) - func convertArguments(_ array: inout ContiguousArray>, _ car: String, _ cdr: [String], _ body: (ContiguousArray>) -> Result) -> Result { - return car.withCString { cString in - if let car = cdr.first { - array.append(cString) - return convertArguments(&array, car, Array(cdr.suffix(from: 1)), body) - } else { - return body(array) - } - } - } - var cStrings = ContiguousArray>() - code = convertArguments(&cStrings, argument, Array(arguments.suffix(from: 1))) { cStrings in - cStrings.withUnsafeBufferPointer { azArg in - xCreate(contextPointer, UnsafeMutablePointer(OpaquePointer(azArg.baseAddress!)), Int32(cStrings.count), &tokenizerPointer) - } - } - } else { - code = xCreate(contextPointer, nil, 0, &tokenizerPointer) - } - - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: "failed fts5_tokenizer.xCreate") - } - - if let tokenizerPointer = tokenizerPointer { - self.tokenizerPointer = tokenizerPointer - } else { - throw DatabaseError(resultCode: code, message: "nil tokenizer") - } - } - - deinit { - if let delete = xTokenizer.xDelete { - delete(tokenizerPointer) - } - } - - func tokenize(context: UnsafeMutableRawPointer?, tokenization: FTS5Tokenization, pText: UnsafePointer?, nText: Int32, tokenCallback: @escaping FTS5TokenCallback) -> Int32 { - guard let xTokenize = xTokenizer.xTokenize else { - return SQLITE_ERROR - } - return xTokenize(tokenizerPointer, context, tokenization.rawValue, pText, nText, tokenCallback) - } - } - - /// Creates an FTS5 tokenizer, given its descriptor. - /// - /// let unicode61 = try db.makeTokenizer(.unicode61()) - /// - /// It is a programmer error to use the tokenizer outside of a protected - /// database queue, or after the database has been closed. - /// - /// Use this method when you implement a custom wrapper tokenizer: - /// - /// final class MyTokenizer : FTS5WrapperTokenizer { - /// var wrappedTokenizer: FTS5Tokenizer - /// - /// init(db: Database, arguments: [String]) throws { - /// wrappedTokenizer = try db.makeTokenizer(.unicode61()) - /// } - /// } - public func makeTokenizer(_ descriptor: FTS5TokenizerDescriptor) throws -> FTS5Tokenizer { - let api = FTS5.api(self) - - let xTokenizerPointer: UnsafeMutablePointer = .allocate(capacity: 1) - defer { xTokenizerPointer.deallocate() } - - let contextHandle: UnsafeMutablePointer = .allocate(capacity: 1) - defer { contextHandle.deallocate() } - - let code = api.pointee.xFindTokenizer!( - UnsafeMutablePointer(mutating: api), - descriptor.name, - contextHandle, - xTokenizerPointer) - - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code) - } - - let contextPointer = contextHandle.pointee - return try FTS5RegisteredTokenizer(xTokenizer: xTokenizerPointer.pointee, contextPointer: contextPointer, arguments: descriptor.arguments) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5TokenizerDescriptor.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS5TokenizerDescriptor.swift deleted file mode 100755 index 618392e..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5TokenizerDescriptor.swift +++ /dev/null @@ -1,138 +0,0 @@ -#if SQLITE_ENABLE_FTS5 -/// An FTS5 tokenizer, suitable for FTS5 table definitions: -/// -/// db.create(virtualTable: "book", using: FTS5()) { t in -/// t.tokenizer = .unicode61() // FTS5TokenizerDescriptor -/// } -/// -/// See https://www.sqlite.org/fts5.html#tokenizers -public struct FTS5TokenizerDescriptor { - /// The tokenizer components - /// - /// // ["unicode61"] - /// FTS5TokenizerDescriptor.unicode61().components - /// - /// // ["unicode61", "remove_diacritics", "0"] - /// FTS5TokenizerDescriptor.unicode61(removeDiacritics: false)).components - public let components: [String] - - /// The tokenizer name - /// - /// // "unicode61" - /// FTS5TokenizerDescriptor.unicode61().name - /// - /// // "unicode61" - /// FTS5TokenizerDescriptor.unicode61(removeDiacritics: false)).name - var name: String { - return components[0] - } - - var arguments: [String] { - return Array(components.suffix(from: 1)) - } - - /// Creates an FTS5 tokenizer descriptor. - /// - /// db.create(virtualTable: "book", using: FTS5()) { t in - /// let tokenizer = FTS5TokenizerDescriptor(components: ["porter", "unicode61", "remove_diacritics", "0"]) - /// t.tokenizer = tokenizer - /// } - /// - /// - precondition: Components is not empty - public init(components: [String]) { - GRDBPrecondition(!components.isEmpty, "FTS5TokenizerDescriptor requires at least one component") - assert(!components.isEmpty) - self.components = components - } - - /// The "ascii" tokenizer - /// - /// db.create(virtualTable: "book", using: FTS5()) { t in - /// t.tokenizer = .ascii() - /// } - /// - /// - parameters: - /// - separators: Unless empty (the default), SQLite will consider - /// these characters as token separators. - /// - /// See https://www.sqlite.org/fts5.html#ascii_tokenizer - public static func ascii(separators: Set = []) -> FTS5TokenizerDescriptor { - if separators.isEmpty { - return FTS5TokenizerDescriptor(components: ["ascii"]) - } else { - return FTS5TokenizerDescriptor(components: ["ascii", "separators", separators.map { String($0) }.joined(separator: "").sqlExpression.quotedSQL()]) - } - } - - /// The "porter" tokenizer - /// - /// db.create(virtualTable: "book", using: FTS5()) { t in - /// t.tokenizer = .porter() - /// } - /// - /// - parameters: - /// - base: An eventual wrapping tokenizer which replaces the - // default unicode61() base tokenizer. - /// - /// See https://www.sqlite.org/fts5.html#porter_tokenizer - public static func porter(wrapping base: FTS5TokenizerDescriptor? = nil) -> FTS5TokenizerDescriptor { - if let base = base { - return FTS5TokenizerDescriptor(components: ["porter"] + base.components) - } else { - return FTS5TokenizerDescriptor(components: ["porter"]) - } - } - - /// An "unicode61" tokenizer - /// - /// db.create(virtualTable: "book", using: FTS5()) { t in - /// t.tokenizer = .unicode61() - /// } - /// - /// - parameters: - /// - diacritics: By default SQLite will strip diacritics from - /// latin characters. - /// - separators: Unless empty (the default), SQLite will consider - /// these characters as token separators. - /// - tokenCharacters: Unless empty (the default), SQLite will - /// consider these characters as token characters. - /// - /// See https://www.sqlite.org/fts5.html#unicode61_tokenizer - public static func unicode61(diacritics: FTS5.Diacritics = .removeLegacy, separators: Set = [], tokenCharacters: Set = []) -> FTS5TokenizerDescriptor { - var components: [String] = ["unicode61"] - switch diacritics { - case .removeLegacy: - break - case .keep: - components.append(contentsOf: ["remove_diacritics", "0"]) - #if GRDBCUSTOMSQLITE - case .remove: - components.append(contentsOf: ["remove_diacritics", "2"]) - #endif - } - if !separators.isEmpty { - // TODO: test "=" and "\"", "(" and ")" as separators, with both FTS3Pattern(matchingAnyTokenIn:tokenizer:) and Database.create(virtualTable:using:) - components.append(contentsOf: [ - "separators", - separators - .sorted() - .map { String($0) } - .joined(separator: "") - .sqlExpression - .quotedSQL()]) - } - if !tokenCharacters.isEmpty { - // TODO: test "=" and "\"", "(" and ")" as tokenCharacters, with both FTS3Pattern(matchingAnyTokenIn:tokenizer:) and Database.create(virtualTable:using:) - components.append(contentsOf: [ - "tokenchars", - tokenCharacters - .sorted() - .map { String($0) } - .joined(separator: "") - .sqlExpression - .quotedSQL()]) - } - return FTS5TokenizerDescriptor(components: components) - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5WrapperTokenizer.swift b/Example/Pods/GRDB.swift/GRDB/FTS/FTS5WrapperTokenizer.swift deleted file mode 100755 index a217176..0000000 --- a/Example/Pods/GRDB.swift/GRDB/FTS/FTS5WrapperTokenizer.swift +++ /dev/null @@ -1,149 +0,0 @@ -#if SQLITE_ENABLE_FTS5 -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// Flags that tell SQLite how to register a token. -/// -/// See https://www.sqlite.org/fts5.html#custom_tokenizers -public struct FTS5TokenFlags : OptionSet { - public let rawValue: Int32 - - public init(rawValue: Int32) { - self.rawValue = rawValue - } - - /// FTS5_TOKEN_COLOCATED - public static let colocated = FTS5TokenFlags(rawValue: FTS5_TOKEN_COLOCATED) -} - -/// A function that lets FTS5WrapperTokenizer notify tokens. -/// -/// See FTS5WrapperTokenizer.accept(token:flags:tokenCallback:) -public typealias FTS5WrapperTokenCallback = (_ token: String, _ flags: FTS5TokenFlags) throws -> () - -/// The protocol for custom FTS5 tokenizers that wrap another tokenizer. -/// -/// Types that adopt FTS5WrapperTokenizer don't have to implement the -/// low-level FTS5Tokenizer.tokenize(context:flags:pText:nText:tokenCallback:). -/// -/// Instead, they process regular Swift strings. -/// -/// Here is the implementation for a trivial tokenizer that wraps the -/// built-in ascii tokenizer without any custom processing: -/// -/// class TrivialAsciiTokenizer : FTS5WrapperTokenizer { -/// static let name = "trivial" -/// let wrappedTokenizer: FTS5Tokenizer -/// -/// init(db: Database, arguments: [String]) throws { -/// wrappedTokenizer = try db.makeTokenizer(.ascii()) -/// } -/// -/// func accept(token: String, flags: FTS5TokenFlags, for tokenization: FTS5Tokenization, tokenCallback: FTS5WrapperTokenCallback) throws { -/// try tokenCallback(token, flags) -/// } -/// } -public protocol FTS5WrapperTokenizer : FTS5CustomTokenizer { - /// The wrapped tokenizer - var wrappedTokenizer: FTS5Tokenizer { get } - - /// Given a token produced by the wrapped tokenizer, notifies customized - /// tokens to the `tokenCallback` function. - /// - /// For example: - /// - /// func accept(token: String, flags: FTS5TokenFlags, for tokenization: FTS5Tokenization, tokenCallback: FTS5WrapperTokenCallback) throws { - /// // pass through: - /// try tokenCallback(token, flags) - /// } - /// - /// When implementing the accept method, there are a two rules - /// to observe: - /// - /// 1. Errors thrown by the tokenCallback function must not be caught. - /// - /// 2. The input `flags` should be given unmodified to the tokenCallback - /// function, unless you union it with the .colocated flag when the - /// tokenizer produces synonyms (see - /// https://www.sqlite.org/fts5.html#synonym_support). - /// - /// - parameters: - /// - token: A token produced by the wrapped tokenizer - /// - flags: Flags that tell SQLite how to register a token. - /// - tokenization: The reason why FTS5 is requesting tokenization. - /// - tokenCallback: The function to call for each customized token. - func accept(token: String, flags: FTS5TokenFlags, for tokenization: FTS5Tokenization, tokenCallback: FTS5WrapperTokenCallback) throws -} - -private struct FTS5WrapperContext { - let tokenizer: FTS5WrapperTokenizer - let context: UnsafeMutableRawPointer? - let tokenization: FTS5Tokenization - let tokenCallback: FTS5TokenCallback -} - -extension FTS5WrapperTokenizer { - /// Default implementation - public func tokenize(context: UnsafeMutableRawPointer?, tokenization: FTS5Tokenization, pText: UnsafePointer?, nText: Int32, tokenCallback: @escaping FTS5TokenCallback) -> Int32 { - // `tokenCallback` is @convention(c). This requires a little setup - // in order to transfer context. - var customContext = FTS5WrapperContext( - tokenizer: self, - context: context, - tokenization: tokenization, - tokenCallback: tokenCallback) - return withUnsafeMutablePointer(to: &customContext) { customContextPointer in - // Invoke wrappedTokenizer - return wrappedTokenizer.tokenize(context: customContextPointer, tokenization: tokenization, pText: pText, nText: nText) { (customContextPointer, tokenFlags, pToken, nToken, iStart, iEnd) in - - // Extract token produced by wrapped tokenizer - guard let token = pToken.flatMap({ String(data: Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: $0), count: Int(nToken), deallocator: .none), encoding: .utf8) }) else { - return 0 // SQLITE_OK - } - - // Extract context - let customContext = customContextPointer!.assumingMemoryBound(to: FTS5WrapperContext.self).pointee - let tokenizer = customContext.tokenizer - let context = customContext.context - let tokenization = customContext.tokenization - let tokenCallback = customContext.tokenCallback - - // Process token produced by wrapped tokenizer - do { - try tokenizer.accept( - token: token, - flags: FTS5TokenFlags(rawValue: tokenFlags), - for: tokenization, - tokenCallback: { (token, flags) in - // Turn token into bytes - return try ContiguousArray(token.utf8).withUnsafeBufferPointer { buffer in - guard let addr = buffer.baseAddress else { - return - } - let pToken = UnsafeMutableRawPointer(mutating: addr).assumingMemoryBound(to: Int8.self) - let nToken = Int32(buffer.count) - - // Inject token bytes into SQLite - let code = tokenCallback(context, flags.rawValue, pToken, nToken, iStart, iEnd) - guard code == SQLITE_OK else { - throw DatabaseError(resultCode: code, message: "token callback failed") - } - } - }) - - return SQLITE_OK - } catch let error as DatabaseError { - return error.extendedResultCode.rawValue - } catch { - return SQLITE_ERROR - } - } - } - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Fixit/GRDB-4.0.swift b/Example/Pods/GRDB.swift/GRDB/Fixit/GRDB-4.0.swift deleted file mode 100755 index 048ed5d..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Fixit/GRDB-4.0.swift +++ /dev/null @@ -1,157 +0,0 @@ -import Dispatch - -// Fixits for changes introduced by GRDB 4.0.0 - -extension Cursor { - @available(*, unavailable, renamed: "compactMap") - public func flatMap(_ transform: @escaping (Element) throws -> ElementOfResult?) -> MapCursor>, ElementOfResult> { preconditionFailure() } -} - -extension DatabaseWriter { - @available(*, unavailable, message: "Use concurrentRead instead") - public func readFromCurrentState(_ block: @escaping (Database) -> Void) throws { preconditionFailure() } -} - -extension ValueObservation { - @available(*, unavailable, message: "Provide the reducer in a (Database) -> Reducer closure") - public static func tracking(_ regions: DatabaseRegionConvertible..., reducer: Reducer) -> ValueObservation { preconditionFailure() } - - @available(*, unavailable, message: "Use distinctUntilChanged() instead") - public static func tracking(_ regions: DatabaseRegionConvertible..., fetchDistinct fetch: @escaping (Database) throws -> Value) -> ValueObservation>> where Value: Equatable { preconditionFailure() } -} - -@available(*, unavailable, renamed: "FastDatabaseValueCursor") -public typealias ColumnCursor = FastDatabaseValueCursor - -@available(*, unavailable, renamed: "FastNullableDatabaseValueCursor") -public typealias NullableColumnCursor = FastNullableDatabaseValueCursor - -extension Database { - @available(*, unavailable, renamed: "execute(sql:arguments:)") - public func execute(_ sql: String, arguments: StatementArguments? = nil) throws { preconditionFailure() } - - @available(*, unavailable, renamed: "makeSelectStatement(sql:)") - public func makeSelectStatement(_ sql: String) throws -> SelectStatement { preconditionFailure() } - - @available(*, unavailable, renamed: "cachedSelectStatement(sql:)") - public func cachedSelectStatement(_ sql: String) throws -> SelectStatement { preconditionFailure() } - - @available(*, unavailable, renamed: "makeUpdateStatement(sql:)") - public func makeUpdateStatement(_ sql: String) throws -> UpdateStatement { preconditionFailure() } - - @available(*, unavailable, renamed: "cachedUpdateStatement(sql:)") - public func cachedUpdateStatement(_ sql: String) throws -> UpdateStatement { preconditionFailure() } -} - -extension DatabaseValueConvertible { - @available(*, unavailable, renamed: "fetchCursor(_:sql:arguments:adapter:)") - public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> DatabaseValueCursor { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchAll(_:sql:arguments:adapter:)") - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchOne(_:sql:arguments:adapter:)") - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { preconditionFailure() } -} - -extension Optional where Wrapped: DatabaseValueConvertible { - @available(*, unavailable, renamed: "fetchCursor(_:sql:arguments:adapter:)") - public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> NullableDatabaseValueCursor { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchAll(_:sql:arguments:adapter:)") - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Wrapped?] { preconditionFailure() } -} - -extension Row { - @available(*, unavailable, renamed: "fetchCursor(_:sql:arguments:adapter:)") - public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> RowCursor { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchAll(_:sql:arguments:adapter:)") - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Row] { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchOne(_:sql:arguments:adapter:)") - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Row? { preconditionFailure() } -} - -extension DatabaseValueConvertible where Self: StatementColumnConvertible { - @available(*, unavailable, renamed: "fetchCursor(_:sql:arguments:adapter:)") - public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> FastDatabaseValueCursor { preconditionFailure() } -} - -extension Optional where Wrapped: DatabaseValueConvertible & StatementColumnConvertible { - @available(*, unavailable, renamed: "fetchCursor(_:sql:arguments:adapter:)") - public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> FastNullableDatabaseValueCursor { preconditionFailure() } -} - -extension FetchableRecord { - @available(*, unavailable, renamed: "fetchCursor(_:sql:arguments:adapter:)") - public static func fetchCursor(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> RecordCursor { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchAll(_:sql:arguments:adapter:)") - public static func fetchAll(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { preconditionFailure() } - - @available(*, unavailable, renamed: "fetchOne(_:sql:arguments:adapter:)") - public static func fetchOne(_ db: Database, _ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { preconditionFailure() } -} - -extension SQLRequest { - @available(*, unavailable, renamed: "init(sql:arguments:adapter:cached:)") - public init(_ sql: String, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil, cached: Bool = false) { preconditionFailure() } -} - -extension SQLExpressionLiteral { - @available(*, unavailable, renamed: "init(sql:arguments:)") - public init(_ sql: String, arguments: StatementArguments? = nil) { preconditionFailure() } -} - -extension SQLExpression { - @available(*, unavailable, message: "Use sqlLiteral property instead") - public var literal: SQLExpressionLiteral { preconditionFailure() } -} - -extension FTS3TokenizerDescriptor { - @available(*, unavailable, renamed: "unicode61(diacritics:separators:tokenCharacters:)") - public static func unicode61(removeDiacritics: Bool, separators: Set = [], tokenCharacters: Set = []) -> FTS3TokenizerDescriptor { preconditionFailure() } -} - -#if SQLITE_ENABLE_FTS5 -extension FTS5TokenizerDescriptor { - @available(*, unavailable, renamed: "unicode61(diacritics:separators:tokenCharacters:)") - public static func unicode61(removeDiacritics: Bool = true, separators: Set = [], tokenCharacters: Set = []) -> FTS5TokenizerDescriptor { preconditionFailure() } -} -#endif - -extension DatabaseValue { - @available(*, unavailable) - public func losslessConvert(sql: String? = nil, arguments: StatementArguments? = nil) -> T where T: DatabaseValueConvertible { preconditionFailure() } - - @available(*, unavailable) - public func losslessConvert(sql: String? = nil, arguments: StatementArguments? = nil) -> T? where T: DatabaseValueConvertible { preconditionFailure() } -} - -extension ValueScheduling { - @available(*, unavailable, renamed: "async(onQueue:startImmediately:)") - public static func onQueue(_ queue: DispatchQueue, startImmediately: Bool) -> ValueScheduling { preconditionFailure() } -} - -extension ValueObservation { - @available(*, unavailable, message: "Observation extent is controlled by the lifetime of observers returned by the start() method.") - public var extent: Database.TransactionObservationExtent { - get { preconditionFailure() } - set { preconditionFailure() } - } -} - -extension Configuration { - @available(*, unavailable, message: "Run the PRAGMA cipher_page_size in Configuration.prepareDatabase instead.") - public var cipherPageSize: Int { - get { preconditionFailure() } - set { preconditionFailure() } - } - - @available(*, unavailable, message: "Run the PRAGMA kdf_iter in Configuration.prepareDatabase instead.") - public var kdfIterations: Int { - get { preconditionFailure() } - set { preconditionFailure() } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Migration/DatabaseMigrator.swift b/Example/Pods/GRDB.swift/GRDB/Migration/DatabaseMigrator.swift deleted file mode 100755 index b8d18d1..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Migration/DatabaseMigrator.swift +++ /dev/null @@ -1,270 +0,0 @@ -/// A DatabaseMigrator registers and applies database migrations. -/// -/// Migrations are named blocks of SQL statements that are guaranteed to be -/// applied in order, once and only once. -/// -/// When a user upgrades your application, only non-applied migration are run. -/// -/// Usage: -/// -/// var migrator = DatabaseMigrator() -/// -/// // 1st migration -/// migrator.registerMigration("createLibrary") { db in -/// try db.create(table: "author") { t in -/// t.autoIncrementedPrimaryKey("id") -/// t.column("creationDate", .datetime) -/// t.column("name", .text).notNull() -/// } -/// -/// try db.create(table: "book") { t in -/// t.autoIncrementedPrimaryKey("id") -/// t.column("authorId", .integer) -/// .notNull() -/// .references("author", onDelete: .cascade) -/// t.column("title", .text).notNull() -/// } -/// } -/// -/// // 2nd migration -/// migrator.registerMigration("AddBirthYearToAuthors") { db in -/// try db.alter(table: "author") { t -/// t.add(column: "birthYear", .integer) -/// } -/// } -/// -/// // Migrations for future versions will be inserted here: -/// // -/// // // 3rd migration -/// // migrator.registerMigration("...") { db in -/// // ... -/// // } -/// -/// try migrator.migrate(dbQueue) -public struct DatabaseMigrator { - /// When the `eraseDatabaseOnSchemaChange` flag is true, the migrator will - /// automatically wipe out the full database content, and recreate the whole - /// database from scratch, if it detects that a migration has changed its - /// definition. - /// - /// This flag can destroy your precious users' data! - /// - /// But it may be useful in two situations: - /// - /// 1. During application development, as you are still designing - /// migrations, and the schema changes often. - /// - /// In this case, it is recommended that you make sure this flag does - /// not ship in the distributed application, in order to avoid undesired - /// data loss: - /// - /// var migrator = DatabaseMigrator() - /// #if DEBUG - /// // Speed up development by nuking the database when migrations change - /// migrator.eraseDatabaseOnSchemaChange = true - /// #endif - /// - /// 2. When the database content can easily be recreated, such as a cache - /// for some downloaded data. - public var eraseDatabaseOnSchemaChange = false - private var migrations: [Migration] = [] - - /// A new migrator. - public init() { - } - - /// Registers a migration. - /// - /// migrator.registerMigration("createAuthors") { db in - /// try db.create(table: "author") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("creationDate", .datetime) - /// t.column("name", .text).notNull() - /// } - /// } - /// - /// - parameters: - /// - identifier: The migration identifier. - /// - block: The migration block that performs SQL statements. - /// - precondition: No migration with the same same as already been registered. - public mutating func registerMigration(_ identifier: String, migrate: @escaping (Database) throws -> Void) { - registerMigration(Migration(identifier: identifier, migrate: migrate)) - } - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - /// Registers an advanced migration, as described at https://www.sqlite.org/lang_altertable.html#otheralter - /// - /// // Add a NOT NULL constraint on players.name: - /// migrator.registerMigrationWithDeferredForeignKeyCheck("AddNotNullCheckOnName") { db in - /// try db.create(table: "new_player") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("name", .text).notNull() - /// } - /// try db.execute(sql: "INSERT INTO new_player SELECT * FROM player") - /// try db.drop(table: "player") - /// try db.rename(table: "new_player", to: "player") - /// } - /// - /// While your migration code runs with disabled foreign key checks, those - /// are re-enabled and checked at the end of the migration, regardless of - /// eventual errors. - /// - /// - parameters: - /// - identifier: The migration identifier. - /// - block: The migration block that performs SQL statements. - /// - precondition: No migration with the same same as already been registered. - /// - /// :nodoc: - public mutating func registerMigrationWithDeferredForeignKeyCheck(_ identifier: String, migrate: @escaping (Database) throws -> Void) { - registerMigration(Migration(identifier: identifier, disabledForeignKeyChecks: true, migrate: migrate)) - } - #else - @available(OSX 10.10, *) - /// Registers an advanced migration, as described at https://www.sqlite.org/lang_altertable.html#otheralter - /// - /// // Add a NOT NULL constraint on players.name: - /// migrator.registerMigrationWithDeferredForeignKeyCheck("AddNotNullCheckOnName") { db in - /// try db.create(table: "new_player") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("name", .text).notNull() - /// } - /// try db.execute(sql: "INSERT INTO new_player SELECT * FROM player") - /// try db.drop(table: "player") - /// try db.rename(table: "new_player", to: "player") - /// } - /// - /// While your migration code runs with disabled foreign key checks, those - /// are re-enabled and checked at the end of the migration, regardless of - /// eventual errors. - /// - /// - parameters: - /// - identifier: The migration identifier. - /// - block: The migration block that performs SQL statements. - /// - precondition: No migration with the same same as already been registered. - public mutating func registerMigrationWithDeferredForeignKeyCheck(_ identifier: String, migrate: @escaping (Database) throws -> Void) { - registerMigration(Migration(identifier: identifier, disabledForeignKeyChecks: true, migrate: migrate)) - } - #endif - - /// Iterate migrations in the same order as they were registered. If a - /// migration has not yet been applied, its block is executed in - /// a transaction. - /// - /// - parameter db: A DatabaseWriter (DatabaseQueue or DatabasePool) where - /// migrations should apply. - /// - throws: An eventual error thrown by the registered migration blocks. - public func migrate(_ writer: DatabaseWriter) throws { - guard let lastMigration = migrations.last else { - return - } - try migrate(writer, upTo: lastMigration.identifier) - } - - /// Iterate migrations in the same order as they were registered, up to the - /// provided target. If a migration has not yet been applied, its block is - /// executed in a transaction. - /// - /// - parameter db: A DatabaseWriter (DatabaseQueue or DatabasePool) where - /// migrations should apply. - /// - targetIdentifier: The identifier of a registered migration. - /// - throws: An eventual error thrown by the registered migration blocks. - public func migrate(_ writer: DatabaseWriter, upTo targetIdentifier: String) throws { - if eraseDatabaseOnSchemaChange { - let witness = try DatabaseQueue(path: "") - - // Erase database if we detect a change in the *current* schema. - let (currentIdentifier, currentSchema) = try writer.writeWithoutTransaction { db -> (String?, SchemaInfo) in - try setupMigrations(db) - let identifiers = try appliedIdentifiers(db) - let currentIdentifier = migrations - .reversed() - .first { identifiers.contains($0.identifier) }? - .identifier - return try (currentIdentifier, db.schema()) - } - if let currentIdentifier = currentIdentifier { - let witnessSchema: SchemaInfo = try witness.writeWithoutTransaction { db in - try setupMigrations(db) - try runMigrations(db, upTo: currentIdentifier) - return try db.schema() - } - - if currentSchema != witnessSchema { - try writer.erase() - } - } - - // Migrate to *target* schema - let schema: SchemaInfo = try writer.writeWithoutTransaction { db in - try setupMigrations(db) - try runMigrations(db, upTo: targetIdentifier) - return try db.schema() - } - - // Erase database if we detect a change in the *target* schema. - let witnessSchema: SchemaInfo = try witness.writeWithoutTransaction { db in - try setupMigrations(db) - try runMigrations(db, upTo: targetIdentifier) - return try db.schema() - } - - if schema != witnessSchema { - try writer.erase() - try writer.writeWithoutTransaction { db in - try setupMigrations(db) - try runMigrations(db, upTo: targetIdentifier) - } - } - } else { - try writer.writeWithoutTransaction { db in - try setupMigrations(db) - try runMigrations(db, upTo: targetIdentifier) - } - } - } - - /// Returns the set of applied migration identifiers. - public func appliedMigrations(in reader: DatabaseReader) throws -> Set { - return try reader.read { try appliedIdentifiers($0) } - } - - - // MARK: - Non public - - private mutating func registerMigration(_ migration: Migration) { - GRDBPrecondition(!migrations.map({ $0.identifier }).contains(migration.identifier), "already registered migration: \(String(reflecting: migration.identifier))") - migrations.append(migration) - } - - private func setupMigrations(_ db: Database) throws { - try db.execute(sql: "CREATE TABLE IF NOT EXISTS grdb_migrations (identifier TEXT NOT NULL PRIMARY KEY)") - } - - private func appliedIdentifiers(_ db: Database) throws -> Set { - return try Set(String.fetchAll(db, sql: "SELECT identifier FROM grdb_migrations")) - } - - private func runMigrations(_ db: Database, upTo targetIdentifier: String) throws { - var prefixMigrations: [Migration] = [] - for migration in migrations { - prefixMigrations.append(migration) - if migration.identifier == targetIdentifier { - break - } - } - - // targetIdentifier must refer to a registered migration - GRDBPrecondition(prefixMigrations.last?.identifier == targetIdentifier, "undefined migration: \(String(reflecting: targetIdentifier))") - - // Subsequent migration must not be applied - let appliedIdentifiers = try self.appliedIdentifiers(db) - if prefixMigrations.count < migrations.count { - let nextIdentifier = migrations[prefixMigrations.count].identifier - GRDBPrecondition(!appliedIdentifiers.contains(nextIdentifier), "database is already migrated beyond migration \(String(reflecting: targetIdentifier))") - } - - for migration in prefixMigrations where !appliedIdentifiers.contains(migration.identifier) { - try migration.run(db) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Migration/Migration.swift b/Example/Pods/GRDB.swift/GRDB/Migration/Migration.swift deleted file mode 100755 index 17d60de..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Migration/Migration.swift +++ /dev/null @@ -1,85 +0,0 @@ -/// An internal struct that defines a migration. -struct Migration { - let identifier: String - let disabledForeignKeyChecks: Bool - let migrate: (Database) throws -> Void - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - init(identifier: String, disabledForeignKeyChecks: Bool = false, migrate: @escaping (Database) throws -> Void) { - self.identifier = identifier - self.disabledForeignKeyChecks = disabledForeignKeyChecks - self.migrate = migrate - } - #else - init(identifier: String, migrate: @escaping (Database) throws -> Void) { - self.identifier = identifier - self.disabledForeignKeyChecks = false - self.migrate = migrate - } - - @available(OSX 10.10, *) - // PRAGMA foreign_key_check was introduced in SQLite 3.7.16 http://www.sqlite.org/changes.html#version_3_7_16 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - init(identifier: String, disabledForeignKeyChecks: Bool, migrate: @escaping (Database) throws -> Void) { - self.identifier = identifier - self.disabledForeignKeyChecks = disabledForeignKeyChecks - self.migrate = migrate - } - #endif - - func run(_ db: Database) throws { - if try disabledForeignKeyChecks && (Bool.fetchOne(db, sql: "PRAGMA foreign_keys") ?? false) { - try runWithDisabledForeignKeys(db) - } else { - try runWithoutDisabledForeignKeys(db) - } - } - - - private func runWithoutDisabledForeignKeys(_ db: Database) throws { - try db.inTransaction(.immediate) { - try migrate(db) - try insertAppliedIdentifier(db) - return .commit - } - } - - private func runWithDisabledForeignKeys(_ db: Database) throws { - // Support for database alterations described at - // https://www.sqlite.org/lang_altertable.html#otheralter - // - // > 1. If foreign key constraints are enabled, disable them using - // > PRAGMA foreign_keys=OFF. - try db.execute(sql: "PRAGMA foreign_keys = OFF") - - // > 2. Start a transaction. - try db.inTransaction(.immediate) { - try migrate(db) - try insertAppliedIdentifier(db) - - // > 10. If foreign key constraints were originally enabled then run PRAGMA - // > foreign_key_check to verify that the schema change did not break any foreign key - // > constraints. - if try Row.fetchOne(db, sql: "PRAGMA foreign_key_check") != nil { - // https://www.sqlite.org/pragma.html#pragma_foreign_key_check - // - // PRAGMA foreign_key_check does not return an error, - // but the list of violated foreign key constraints. - // - // Let's turn any violation into an SQLITE_CONSTRAINT - // error, and rollback the transaction. - throw DatabaseError(resultCode: .SQLITE_CONSTRAINT, message: "FOREIGN KEY constraint failed") - } - - // > 11. Commit the transaction started in step 2. - return .commit - } - - // > 12. If foreign keys constraints were originally enabled, reenable them now. - try db.execute(sql: "PRAGMA foreign_keys = ON") - } - - private func insertAppliedIdentifier(_ db: Database) throws { - try db.execute(sql: "INSERT INTO grdb_migrations (identifier) VALUES (?)", arguments: [identifier]) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/FTS3+QueryInterface.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/FTS3+QueryInterface.swift deleted file mode 100755 index 7bdfaad..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/FTS3+QueryInterface.swift +++ /dev/null @@ -1,55 +0,0 @@ -extension QueryInterfaceRequest { - - // MARK: Full Text Search - - /// Creates a request with a full-text predicate added to the eventual - /// set of already applied predicates. - /// - /// // SELECT * FROM book WHERE book MATCH '...' - /// var request = Book.all() - /// request = request.matching(pattern) - /// - /// If the search pattern is nil, the request does not match any - /// database row. - public func matching(_ pattern: FTS3Pattern?) -> QueryInterfaceRequest { - guard let pattern = pattern else { - return none() - } - let alias = TableAlias() - let matchExpression = TableMatchExpression(alias: alias, pattern: pattern.databaseValue) - return self.aliased(alias).filter(matchExpression) - } -} - -extension TableRecord { - - // MARK: Full Text Search - - /// Returns a QueryInterfaceRequest with a matching predicate. - /// - /// // SELECT * FROM book WHERE book MATCH '...' - /// var request = Book.matching(pattern) - /// - /// If the search pattern is nil, the request does not match any - /// database row. - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func matching(_ pattern: FTS3Pattern?) -> QueryInterfaceRequest { - return all().matching(pattern) - } -} - -extension ColumnExpression { - /// A matching SQL expression with the `MATCH` SQL operator. - /// - /// // content MATCH '...' - /// Column("content").match(pattern) - /// - /// If the search pattern is nil, SQLite will evaluate the expression - /// to false. - public func match(_ pattern: FTS3Pattern?) -> SQLExpression { - return SQLExpressionBinary(.match, self, pattern ?? DatabaseValue.null) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/FTS5+QueryInterface.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/FTS5+QueryInterface.swift deleted file mode 100755 index ab3bb55..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/FTS5+QueryInterface.swift +++ /dev/null @@ -1,44 +0,0 @@ -#if SQLITE_ENABLE_FTS5 - extension QueryInterfaceRequest { - - // MARK: Full Text Search - - /// Creates a request with a full-text predicate added to the eventual - /// set of already applied predicates. - /// - /// // SELECT * FROM book WHERE book MATCH '...' - /// var request = Book.all() - /// request = request.matching(pattern) - /// - /// If the search pattern is nil, the request does not match any - /// database row. - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public func matching(_ pattern: FTS5Pattern?) -> QueryInterfaceRequest { - guard let pattern = pattern else { - return none() - } - let alias = TableAlias() - let matchExpression = TableMatchExpression(alias: alias, pattern: pattern.databaseValue) - return self.aliased(alias).filter(matchExpression) - } - } - - extension TableRecord { - - // MARK: Full Text Search - - /// Returns a QueryInterfaceRequest with a matching predicate. - /// - /// // SELECT * FROM book WHERE book MATCH '...' - /// var request = Book.matching(pattern) - /// - /// If the search pattern is nil, the request does not match any - /// database row. - public static func matching(_ pattern: FTS5Pattern?) -> QueryInterfaceRequest { - return all().matching(pattern) - } - } -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/FetchableRecord+QueryInterfaceRequest.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/FetchableRecord+QueryInterfaceRequest.swift deleted file mode 100755 index c600e87..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/FetchableRecord+QueryInterfaceRequest.swift +++ /dev/null @@ -1,120 +0,0 @@ -extension QueryInterfaceRequest where RowDecoder: FetchableRecord { - /// A cursor over fetched records. - /// - /// let request: QueryInterfaceRequest = ... - /// let players = try request.fetchCursor(db) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> RecordCursor { - return try RowDecoder.fetchCursor(db, self) - } - - /// An array of fetched records. - /// - /// let request: QueryInterfaceRequest = ... - /// let players = try request.fetchAll(db) // [Player] - /// - /// - parameter db: A database connection. - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [RowDecoder] { - return try RowDecoder.fetchAll(db, self) - } - - /// The first fetched record. - /// - /// let request: QueryInterfaceRequest = ... - /// let player = try request.fetchOne(db) // Player? - /// - /// - parameter db: A database connection. - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchOne(_ db: Database) throws -> RowDecoder? { - return try RowDecoder.fetchOne(db, self) - } -} - -extension FetchableRecord { - - // MARK: Fetching From QueryInterfaceRequest - - /// Returns a cursor over records fetched from a fetch request. - /// - /// let request = try Player.all() - /// let players = try Player.fetchCursor(db, request) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: a FetchRequest. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: QueryInterfaceRequest) throws -> RecordCursor { - precondition(request.prefetchedAssociations.isEmpty, "Not implemented: fetchCursor with prefetched associations") - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of records fetched from a query interface request. - /// - /// let request = try Player.all() - /// let players = try Player.fetchAll(db, request) // [Player] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: a FetchRequest. - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: QueryInterfaceRequest) throws -> [Self] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - let associations = request.prefetchedAssociations - if associations.isEmpty { - return try fetchAll(statement, adapter: adapter) - } else { - let rows = try Row.fetchAll(statement, adapter: adapter) - try Row.prefetch(db, associations: associations, in: rows) - return rows.map(Self.init(row:)) - } - } - - /// Returns a single record fetched from a query interface request. - /// - /// let request = try Player.filter(key: 1) - /// let player = try Player.fetchOne(db, request) // Player? - /// - /// - parameters: - /// - db: A database connection. - /// - sql: a FetchRequest. - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, _ request: QueryInterfaceRequest) throws -> Self? { - let (statement, adapter) = try request.prepare(db, forSingleResult: true) - let associations = request.prefetchedAssociations - if associations.isEmpty { - return try fetchOne(statement, adapter: adapter) - } else { - guard let row = try Row.fetchOne(statement, adapter: adapter) else { - return nil - } - try Row.prefetch(db, associations: associations, in: [row]) - return Self.init(row: row) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/Association.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/Association.swift deleted file mode 100755 index 2ff915f..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/Association.swift +++ /dev/null @@ -1,514 +0,0 @@ -import Foundation - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The base protocol for all associations that define a connection between two -/// record types. -public protocol Association: DerivableRequest { - // OriginRowDecoder and RowDecoder provide type safety: - // - // Book.including(required: Book.author) // compiles - // Fruit.including(required: Book.author) // does not compile - - /// The record type at the origin of the association. - /// - /// In the `belongsTo` association below, it is Book: - /// - /// struct Book: TableRecord { - /// // BelongsToAssociation - /// static let author = belongsTo(Author.self) - /// } - associatedtype OriginRowDecoder - - /// The associated record type. - /// - /// In the `belongsTo` association below, it is Author: - /// - /// struct Book: TableRecord { - /// // BelongsToAssociation - /// static let author = belongsTo(Author.self) - /// } - associatedtype RowDecoder - - /// :nodoc: - var sqlAssociation: SQLAssociation { get } - - /// Creates an association with the given key. - /// - /// This new key impacts how rows fetched from the resulting association - /// should be consumed: - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // Consume rows: - /// let request = Player.including(required: Player.team.forKey("custom")) - /// for row in Row.fetchAll(db, request) { - /// let team: Team = row["custom"] - /// } - func forKey(_ key: String) -> Self - - /// :nodoc: - init(sqlAssociation: SQLAssociation) -} - -extension Association { - private func mapDestinationRelation(_ transform: (SQLRelation) -> SQLRelation) -> Self { - return Self.init(sqlAssociation: sqlAssociation.mapDestinationRelation(transform)) - } -} - -extension Association { - - /// The association key defines how rows fetched from this association - /// should be consumed. - /// - /// For example: - /// - /// struct Player: TableRecord { - /// // The default key of this association is the name of the - /// // database table for teams, let's say "team": - /// static let team = belongsTo(Team.self) - /// } - /// print(Player.team.key) // Prints "team" - /// - /// // Consume rows: - /// let request = Player.including(required: Player.team) - /// for row in Row.fetchAll(db, request) { - /// let team: Team = row["team"] // the association key - /// } - /// - /// The key can be redefined with the `forKey` method: - /// - /// let request = Player.including(required: Player.team.forKey("custom")) - /// for row in Row.fetchAll(db, request) { - /// let team: Team = row["custom"] - /// } - var key: SQLAssociationKey { - return sqlAssociation.destination.key - } - - /// Creates an association which selects *selection*. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.color - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// let association = Player.team.select([Column("color")]) - /// var request = Player.including(required: association) - /// - /// Any previous selection is replaced: - /// - /// // SELECT player.*, team.color - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// let association = Player.team - /// .select([Column("id")]) - /// .select([Column("color")]) - /// var request = Player.including(required: association) - public func select(_ selection: [SQLSelectable]) -> Self { - return mapDestinationRelation { $0.select(selection) } - } - - /// Creates an association which appends *selection*. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.color, team.name - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// let association = Player.team - /// .select([Column("color")]) - /// .annotated(with: [Column("name")]) - /// var request = Player.including(required: association) - public func annotated(with selection: [SQLSelectable]) -> Self { - return mapDestinationRelation { $0.annotated(with: selection) } - } - - /// Creates an association with the provided *predicate promise* added to - /// the eventual set of already applied predicates. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // FROM player - /// // JOIN team ON team.id = player.teamId AND 1 - /// let association = Player.team.filter { db in true } - /// var request = Player.including(required: association) - public func filter(_ predicate: @escaping (Database) throws -> SQLExpressible) -> Self { - return mapDestinationRelation { $0.filter(predicate) } - } - - /// Creates an association with the provided *orderings promise*. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// // ORDER BY team.name - /// let association = Player.team.order { _ in [Column("name")] } - /// var request = Player.including(required: association) - /// - /// Any previous ordering is replaced: - /// - /// // SELECT player.*, team.* - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// // ORDER BY team.name - /// let association = Player.team - /// .order{ _ in [Column("color")] } - /// .reversed() - /// .order{ _ in [Column("name")] } - /// var request = Player.including(required: association) - public func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> Self { - return mapDestinationRelation { $0.order(orderings) } - } - - /// Creates an association that reverses applied orderings. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// // ORDER BY team.name DESC - /// let association = Player.team.order(Column("name")).reversed() - /// var request = Player.including(required: association) - /// - /// If no ordering was applied, the returned association is identical. - /// - /// // SELECT player.*, team.* - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// let association = Player.team.reversed() - /// var request = Player.including(required: association) - public func reversed() -> Self { - return mapDestinationRelation { $0.reversed() } - } - - /// Creates an association without any ordering. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // FROM player - /// // JOIN team ON team.id = player.teamId - /// let association = Player.team.order(Column("name")).unordered() - /// var request = Player.including(required: association) - public func unordered() -> Self { - return mapDestinationRelation { $0.unordered() } - } - - /// Creates an association with the given key. - /// - /// This new key helps Decodable records decode rows fetched from the - /// resulting association: - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// struct PlayerInfo: FetchableRecord, Decodable { - /// let player: Player - /// let team: Team - /// - /// static func all() -> QueryInterfaceRequest { - /// return Player - /// .including(required: Player.team.forKey(CodingKeys.team)) - /// .asRequest(of: PlayerInfo.self) - /// } - /// } - /// - /// let playerInfos = PlayerInfo.all().fetchAll(db) - /// print(playerInfos.first?.team) - public func forKey(_ codingKey: CodingKey) -> Self { - return forKey(codingKey.stringValue) - } - - /// Creates an association that allows you to define expressions that target - /// a specific database table. - /// - /// In the example below, the "team.color = 'red'" condition in the where - /// clause could be not achieved without table aliases. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // JOIN team ON ... - /// // WHERE team.color = 'red' - /// let teamAlias = TableAlias() - /// let request = Player - /// .including(required: Player.team.aliased(teamAlias)) - /// .filter(teamAlias[Column("color")] == "red") - /// - /// When you give a name to a table alias, you can reliably inject sql - /// snippets in your requests: - /// - /// // SELECT player.*, custom.* - /// // JOIN team custom ON ... - /// // WHERE custom.color = 'red' - /// let teamAlias = TableAlias(name: "custom") - /// let request = Player - /// .including(required: Player.team.aliased(teamAlias)) - /// .filter(sql: "custom.color = ?", arguments: ["red"]) - public func aliased(_ alias: TableAlias) -> Self { - return mapDestinationRelation { $0.qualified(with: alias) } - } -} - -extension Association { - /// Creates an association that prefetches another one. - public func including(all association: A) -> Self where A.OriginRowDecoder == RowDecoder { - return mapDestinationRelation { - $0.including(all: association.sqlAssociation) - } - } - - /// Creates an association that includes another one. The columns of the - /// associated record are selected. The returned association does not - /// require that the associated database table contains a matching row. - public func including(optional association: A) -> Self where A.OriginRowDecoder == RowDecoder { - return mapDestinationRelation { - $0.including(optional: association.sqlAssociation) - } - } - - /// Creates an association that includes another one. The columns of the - /// associated record are selected. The returned association requires - /// that the associated database table contains a matching row. - public func including(required association: A) -> Self where A.OriginRowDecoder == RowDecoder { - return mapDestinationRelation { - $0.including(required: association.sqlAssociation) - } - } - - /// Creates an association that joins another one. The columns of the - /// associated record are not selected. The returned association does not - /// require that the associated database table contains a matching row. - public func joining(optional association: A) -> Self where A.OriginRowDecoder == RowDecoder { - return mapDestinationRelation { - $0.joining(optional: association.sqlAssociation) - } - } - - /// Creates an association that joins another one. The columns of the - /// associated record are not selected. The returned association requires - /// that the associated database table contains a matching row. - public func joining(required association: A) -> Self where A.OriginRowDecoder == RowDecoder { - return mapDestinationRelation { - $0.joining(required: association.sqlAssociation) - } - } -} - -// Allow association.filter(key: ...) -extension Association where Self: TableRequest, RowDecoder: TableRecord { - /// :nodoc: - public var databaseTableName: String { return RowDecoder.databaseTableName } -} - -// MARK: - AssociationToOne - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The base protocol for all associations that define a one-to-one connection. -public protocol AssociationToOne: Association { } - -extension AssociationToOne { - public func forKey(_ key: String) -> Self { - let associationKey = SQLAssociationKey.fixedSingular(key) - return Self.init(sqlAssociation: sqlAssociation.forDestinationKey(associationKey)) - } -} - -// MARK: - AssociationToMany - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The base protocol for all associations that define a one-to-many connection. -public protocol AssociationToMany: Association { } - -extension AssociationToMany { - public func forKey(_ key: String) -> Self { - let associationKey = SQLAssociationKey.fixedPlural(key) - return Self.init(sqlAssociation: sqlAssociation.forDestinationKey(associationKey)) - } -} - -extension AssociationToMany where OriginRowDecoder: TableRecord { - private func makeAggregate(_ expression: SQLExpression) -> AssociationAggregate { - return AssociationAggregate { request in - let tableAlias = TableAlias() - let request = request - .joining(optional: self.aliased(tableAlias)) - .groupByPrimaryKey() - let expression = tableAlias[expression] - return (request: request, expression: expression) - } - } - - /// The number of associated records. - /// - /// It has a default name, which is "[key]Count", where key is the key of - /// the association. For example: - /// - /// For example: - /// - /// struct TeamInfo: FetchableRecord, Decodable { - /// var team: Team - /// var playerCount: Int - /// } - /// let request = Team.annotated(with: Team.players.count()) - /// let infos: [TeamInfo] = try TeamInfo.fetchAll(db, request) - /// - /// let teams: [Team] = try Team.having(Team.players.count() > 10).fetchAll(db) - public var count: AssociationAggregate { - return makeAggregate(SQLExpressionCountDistinct(Column.rowID)) - .aliased("\(key.singularizedName)Count") - } - - /// Creates an aggregate that is true if there exists no associated records. - /// - /// It has a default name, which is "hasNo[Key]", where key is the key of - /// the association. For example: - /// - /// struct TeamInfo: FetchableRecord, Decodable { - /// var team: Team - /// var hasNoPlayer: Bool - /// } - /// let request = Team.annotated(with: Team.players.isEmpty()) - /// let infos: [TeamInfo] = try TeamInfo.fetchAll(db, request) - /// - /// let teams: [Team] = try Team.having(Team.players.isEmpty()).fetchAll(db) - /// let teams: [Team] = try Team.having(!Team.players.isEmpty()) - /// let teams: [Team] = try Team.having(Team.players.isEmpty() == false) - public var isEmpty: AssociationAggregate { - return makeAggregate(SQLExpressionIsEmpty(SQLExpressionCountDistinct(Column.rowID))) - .aliased("hasNo\(key.singularizedName.uppercasingFirstCharacter)") - } - - /// Creates an aggregate which evaluate to the average value of the given - /// expression in associated records. - /// - /// When the averaged expression is a column, the aggregate has a default - /// name which is "average[Key][Column]", where key is the key of the - /// association. For example: - /// - /// For example: - /// - /// struct TeamInfo: FetchableRecord, Decodable { - /// var team: Team - /// var averagePlayerScore: Double - /// } - /// let request = Team.annotated(with: Team.players.average(Column("score"))) - /// let infos: [TeamInfo] = try TeamInfo.fetchAll(db, request) - /// - /// let teams: [Team] = try Team.having(Team.players.average(Column("score")) > 100).fetchAll(db) - public func average(_ expression: SQLExpressible) -> AssociationAggregate { - let aggregate = makeAggregate(SQLExpressionFunction(.avg, arguments: expression)) - if let column = expression as? ColumnExpression { - let name = key.singularizedName - return aggregate.aliased("average\(name.uppercasingFirstCharacter)\(column.name.uppercasingFirstCharacter)") - } else { - return aggregate - } - } - - /// Creates an aggregate which evaluate to the maximum value of the given - /// expression in associated records. - /// - /// When the maximized expression is a column, the aggregate has a default - /// name which is "maximum[Key][Column]", where key is the key of the - /// association. For example: - /// - /// For example: - /// - /// struct TeamInfo: FetchableRecord, Decodable { - /// var team: Team - /// var maxPlayerScore: Double - /// } - /// let request = Team.annotated(with: Team.players.max(Column("score"))) - /// let infos: [TeamInfo] = try TeamInfo.fetchAll(db, request) - /// - /// let teams: [Team] = try Team.having(Team.players.max(Column("score")) < 100).fetchAll(db) - public func max(_ expression: SQLExpressible) -> AssociationAggregate { - let aggregate = makeAggregate(SQLExpressionFunction(.max, arguments: expression)) - if let column = expression as? ColumnExpression { - let name = key.singularizedName - return aggregate.aliased("max\(name.uppercasingFirstCharacter)\(column.name.uppercasingFirstCharacter)") - } else { - return aggregate - } - } - - /// Creates an aggregate which evaluate to the minimum value of the given - /// expression in associated records. - /// - /// When the minimized expression is a column, the aggregate has a default - /// name which is "minimum[Key][Column]", where key is the key of the - /// association. For example: - /// - /// For example: - /// - /// struct TeamInfo: FetchableRecord, Decodable { - /// var team: Team - /// var minPlayerScore: Double - /// } - /// let request = Team.annotated(with: Team.players.min(Column("score"))) - /// let infos: [TeamInfo] = try TeamInfo.fetchAll(db, request) - /// - /// let teams: [Team] = try Team.having(Team.players.min(Column("score")) > 100).fetchAll(db) - public func min(_ expression: SQLExpressible) -> AssociationAggregate { - let aggregate = makeAggregate(SQLExpressionFunction(.min, arguments: expression)) - if let column = expression as? ColumnExpression { - let name = key.singularizedName - return aggregate.aliased("min\(name.uppercasingFirstCharacter)\(column.name.uppercasingFirstCharacter)") - } else { - return aggregate - } - } - - /// Creates an aggregate which evaluate to the sum of the given expression - /// in associated records. - /// - /// When the summed expression is a column, the aggregate has a default - /// name which is "[key][Column]Sum", where key is the key of the - /// association. For example: - /// - /// For example: - /// - /// struct TeamInfo: FetchableRecord, Decodable { - /// var team: Team - /// var playerScoreSum: Double - /// } - /// let request = Team.annotated(with: Team.players.sum(Column("score"))) - /// let infos: [TeamInfo] = try TeamInfo.fetchAll(db, request) - /// - /// let teams: [Team] = try Team.having(Team.players.sum(Column("score")) > 100).fetchAll(db) - public func sum(_ expression: SQLExpressible) -> AssociationAggregate { - let aggregate = makeAggregate(SQLExpressionFunction(.sum, arguments: expression)) - if let column = expression as? ColumnExpression { - let name = key.singularizedName - return aggregate.aliased("\(name)\(column.name.uppercasingFirstCharacter)Sum") - } else { - return aggregate - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/AssociationAggregate.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/AssociationAggregate.swift deleted file mode 100755 index cf67dd5..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/AssociationAggregate.swift +++ /dev/null @@ -1,709 +0,0 @@ -import Foundation - -/// An AssociationAggregate is able to compute aggregated values from a -/// population of associated records. -/// -/// For example: -/// -/// struct Author: TableRecord { -/// static let books = hasMany(Book.self) -/// } -/// -/// let bookCount = Author.books.count // AssociationAggregate -/// -/// Association aggregates can be used in the `annotated(with:)` and -/// `having(_:)` request methods: -/// -/// let request = Author.annotated(with: bookCount) -/// let request = Author.having(bookCount >= 10) -/// -/// The RowDecoder generic type helps the compiler prevent incorrect use -/// of aggregates: -/// -/// // Won't compile because Fruit is not Author. -/// let request = Fruit.annotated(with: bookCount) -public struct AssociationAggregate { - /// Given a request, returns a tuple made of a request extended with the - /// associated records used to compute the aggregate, and an expression - /// whose value is the aggregated value. - /// - /// For example: - /// - /// struct Author: TableRecord { - /// static let books = hasMany(Book.self) - /// } - /// - /// // SELECT * FROM author - /// let request = Author.all() - /// - /// let aggregate = Author.books.count - /// let tuple = aggregate.prepare(request) - /// - /// // The request extended with associated records: - /// // - /// // SELECT author.* FROM author - /// // LEFT JOIN book ON book.authorId = author.id - /// // GROUP BY author.id - /// tuple.request - /// - /// // The aggregated value: - /// // - /// // COUNT(DISTINCT book.rowid) - /// tuple.expression - /// - /// The aggregated value is not right away embedded in the extended request: - /// - /// - We don't know yet if the aggregated value will be used in the - /// SQL selection, or in the HAVING clause. - /// - It helps implementing aggregate operators such as `&&`, `+`, etc. - let prepare: (QueryInterfaceRequest) -> (request: QueryInterfaceRequest, expression: SQLExpression) - - /// The SQL alias for the value of this aggregate. See aliased(_:). - var alias: String? - - init(_ prepare: @escaping (QueryInterfaceRequest) -> (request: QueryInterfaceRequest, expression: SQLExpression)) { - self.prepare = prepare - } -} - -extension AssociationAggregate { - /// Returns an aggregate that is selected in a column with the given name. - /// - /// For example: - /// - /// let aggregate = Author.books.count.aliased("foo") - /// let request = Author.annotated(with: aggregate) - /// if let row = try Row.fetchOne(db, request) { - /// let bookCount: Int = row["foo"] - /// } - public func aliased(_ name: String) -> AssociationAggregate { - var aggregate = self - aggregate.alias = name - return aggregate - } - - /// Returns an aggregate that is selected in a column named like the given - /// coding key. - /// - /// For example: - /// - /// struct AuthorInfo: Decodable, FetchableRecord { - /// var author: Author - /// var bookCount: Int - /// - /// static func fetchAll(_ db: Database) throws -> [AuthorInfo] { - /// let aggregate = Author.books.count.aliased(CodingKeys.bookCount) - /// let request = Author.annotated(with: aggregate) - /// return try AuthorInfo.fetchAll(db, request) - /// } - /// } - public func aliased(_ key: CodingKey) -> AssociationAggregate { - return aliased(key.stringValue) - } -} - -// MARK: - Logical Operators (AND, OR, NOT) - -/// Returns a logically negated aggregate. -/// -/// For example: -/// -/// Author.having(!Author.books.isEmpty) -public prefix func ! (aggregate: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = aggregate.prepare(request) - return (request: request, expression: !expression) - } -} - -/// Groups two aggregates with the `AND` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.isEmpty && Author.paintings.isEmpty) -public func && (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression && rExpression) - } -} - -// TODO: test -/// :nodoc: -public func && (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression && rhs) - } -} - -// TODO: test -/// :nodoc: -public func && (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs && expression) - } -} - - -/// Groups two aggregates with the `OR` SQL operator. -/// -/// For example: -/// -/// Author.having(!Author.books.isEmpty || !Author.paintings.isEmpty) -public func || (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression || rExpression) - } -} - -// TODO: test -/// :nodoc: -public func || (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression || rhs) - } -} - -// TODO: test -/// :nodoc: -public func || (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs || expression) - } -} - -// MARK: - Egality and Identity Operators (=, <>, IS, IS NOT) - -/// Returns an aggregate that compares two aggregates with the `=` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count == Author.paintings.count) -public func == (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression == rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `=` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count == 3) -public func == (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression == rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `=` SQL operator. -/// -/// For example: -/// -/// Author.having(3 == Author.books.count) -public func == (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs == expression) - } -} - -/// Returns an aggregate that checks the boolean value of an aggregate. -/// -/// For example: -/// -/// Author.having(Author.books.isEmpty == false) -public func == (lhs: AssociationAggregate, rhs: Bool) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression == rhs) - } -} - -/// Returns an aggregate that checks the boolean value of an aggregate. -/// -/// For example: -/// -/// Author.having(false == Author.books.isEmpty) -public func == (lhs: Bool, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs == expression) - } -} - -/// Returns an aggregate that compares two aggregates with the `<>` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count != Author.paintings.count) -public func != (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression != rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `<>` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count != 3) -public func != (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression != rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `<>` SQL operator. -/// -/// For example: -/// -/// Author.having(3 != Author.books.count) -public func != (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs != expression) - } -} - -/// Returns an aggregate that checks the boolean value of an aggregate. -/// -/// For example: -/// -/// Author.having(Author.books.isEmpty != true) -public func != (lhs: AssociationAggregate, rhs: Bool) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression != rhs) - } -} - -/// Returns an aggregate that checks the boolean value of an aggregate. -/// -/// For example: -/// -/// Author.having(true != Author.books.isEmpty) -public func != (lhs: Bool, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs != expression) - } -} - -/// Returns an aggregate that compares two aggregates with the `IS` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count === Author.paintings.count) -public func === (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression === rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `IS` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count === 3) -public func === (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression === rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `IS` SQL operator. -/// -/// For example: -/// -/// Author.having(3 === Author.books.count) -public func === (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs === expression) - } -} - -/// Returns an aggregate that compares two aggregates with the `IS NOT` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count !== Author.paintings.count) -public func !== (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression !== rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `IS NOT` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count !== 3) -public func !== (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression !== rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `IS NOT` SQL operator. -/// -/// For example: -/// -/// Author.having(3 !== Author.books.count) -public func !== (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs !== expression) - } -} - -// MARK: - Comparison Operators (<, >, <=, >=) - -/// Returns an aggregate that compares two aggregates with the `<=` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count <= Author.paintings.count) -public func <= (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression <= rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `<=` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count <= 3) -public func <= (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression <= rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `<=` SQL operator. -/// -/// For example: -/// -/// Author.having(3 <= Author.books.count) -public func <= (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs <= expression) - } -} - -/// Returns an aggregate that compares two aggregates with the `<` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count < Author.paintings.count) -public func < (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression < rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `<` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count < 3) -public func < (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression < rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `<` SQL operator. -/// -/// For example: -/// -/// Author.having(3 < Author.books.count) -public func < (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs < expression) - } -} - -/// Returns an aggregate that compares two aggregates with the `>` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count > Author.paintings.count) -public func > (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression > rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `>` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count > 3) -public func > (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression > rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `>` SQL operator. -/// -/// For example: -/// -/// Author.having(3 > Author.books.count) -public func > (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs > expression) - } -} - -/// Returns an aggregate that compares two aggregates with the `>=` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count >= Author.paintings.count) -public func >= (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression >= rExpression) - } -} - -/// Returns an aggregate that compares an aggregate with the `>=` SQL operator. -/// -/// For example: -/// -/// Author.having(Author.books.count >= 3) -public func >= (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression >= rhs) - } -} - -/// Returns an aggregate that compares an aggregate with the `>=` SQL operator. -/// -/// For example: -/// -/// Author.having(3 >= Author.books.count) -public func >= (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs >= expression) - } -} - -// MARK: - Arithmetic Operators (+, -, *, /) - -/// Returns an arithmetically negated aggregate. -/// -/// For example: -/// -/// Author.annotated(with: -Author.books.count) -public prefix func - (aggregate: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = aggregate.prepare(request) - return (request: request, expression:-expression) - } -} - -/// Returns an aggregate that sums two aggregates with the `+` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count + Author.paintings.count) -public func + (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression + rExpression) - } -} - -/// Returns an aggregate that sums an aggregate with the `+` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count + 1) -public func + (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression + rhs) - } -} - -/// Returns an aggregate that sums an aggregate with the `+` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: 1 + Author.books.count) -public func + (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs + expression) - } -} - -/// Returns an aggregate that substracts two aggregates with the `-` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count - Author.paintings.count) -public func - (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression - rExpression) - } -} - -/// Returns an aggregate that substracts an aggregate with the `-` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count - 1) -public func - (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression - rhs) - } -} - -/// Returns an aggregate that substracts an aggregate with the `-` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: 1 - Author.books.count) -public func - (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs - expression) - } -} - -/// Returns an aggregate that multiplies two aggregates with the `*` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count * Author.paintings.count) -public func * (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression * rExpression) - } -} - -/// Returns an aggregate that substracts an aggregate with the `*` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count * 2) -public func * (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression * rhs) - } -} - -/// Returns an aggregate that substracts an aggregate with the `*` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: 2 * Author.books.count) -public func * (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs * expression) - } -} - -/// Returns an aggregate that multiplies two aggregates with the `/` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count / Author.paintings.count) -public func / (lhs: AssociationAggregate, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (lRequest, lExpression) = lhs.prepare(request) - let (request, rExpression) = rhs.prepare(lRequest) - return (request: request, expression: lExpression / rExpression) - } -} - -/// Returns an aggregate that substracts an aggregate with the `/` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: Author.books.count / 2) -public func / (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression / rhs) - } -} - -/// Returns an aggregate that substracts an aggregate with the `/` SQL operator. -/// -/// For example: -/// -/// Author.annotated(with: 2 / Author.books.count) -public func / (lhs: SQLExpressible, rhs: AssociationAggregate) -> AssociationAggregate { - return AssociationAggregate { request in - let (request, expression) = rhs.prepare(request) - return (request: request, expression: lhs / expression) - } -} - -// MARK: - IFNULL(...) - -/// Returns an aggregate that evaluates the `IFNULL` SQL function. -/// -/// Team.annotated(with: Team.players.min(Column("score")) ?? 0) -public func ?? (lhs: AssociationAggregate, rhs: SQLExpressible) -> AssociationAggregate { - var aggregate = AssociationAggregate { request in - let (request, expression) = lhs.prepare(request) - return (request: request, expression: expression ?? rhs) - } - - // Preserve alias - aggregate.alias = lhs.alias - return aggregate -} - -// TODO: add support for ABS(aggregate) -// TODO: add support for LENGTH(aggregate) diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/BelongsToAssociation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/BelongsToAssociation.swift deleted file mode 100755 index 1f45962..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/BelongsToAssociation.swift +++ /dev/null @@ -1,78 +0,0 @@ -/// The BelongsTo association sets up a one-to-one connection from a record -/// type to another record type, such as each instance of the declaring record -/// "belongs to" an instance of the other record. -/// -/// For example, if your application includes authors and books, and each book -/// is assigned its author, you'd declare the association this way: -/// -/// struct Author: TableRecord { ... } -/// struct Book: TableRecord { -/// static let author = belongsTo(Author.self) -/// ... -/// } -/// -/// A BelongsTo associations should be supported by an SQLite foreign key. -/// -/// Foreign keys are the recommended way to declare relationships between -/// database tables because not only will SQLite guarantee the integrity of your -/// data, but GRDB will be able to use those foreign keys to automatically -/// configure your association. -/// -/// You define the foreign key when you create database tables. For example: -/// -/// try db.create(table: "author") { t in -/// t.autoIncrementedPrimaryKey("id") // (1) -/// t.column("name", .text) -/// } -/// try db.create(table: "book") { t in -/// t.autoIncrementedPrimaryKey("id") -/// t.column("authorId", .integer) // (2) -/// .notNull() // (3) -/// .indexed() // (4) -/// .references("author", onDelete: .cascade) // (5) -/// t.column("title", .text) -/// } -/// -/// 1. The author table has a primary key. -/// 2. The book.authorId column is used to link a book to the author it -/// belongs to. -/// 3. Make the book.authorId column not null if you want SQLite to guarantee -/// that all books have an author. -/// 4. Create an index on the book.authorId column in order to ease the -/// selection of an author's books. -/// 5. Create a foreign key from book.authorId column to authors.id, so that -/// SQLite guarantees that no book refers to a missing author. The -/// `onDelete: .cascade` option has SQLite automatically delete all of an -/// author's books when that author is deleted. -/// See https://sqlite.org/foreignkeys.html#fk_actions for more information. -/// -/// The example above uses auto-incremented primary keys. But generally -/// speaking, all primary keys are supported. -/// -/// If the database schema does not define foreign keys between tables, you can -/// still use BelongsTo associations. But your help is needed to define the -/// missing foreign key: -/// -/// struct Book: FetchableRecord, TableRecord { -/// static let author = belongsTo(Author.self, using: ForeignKey(...)) -/// } -/// -/// See ForeignKey for more information. -public struct BelongsToAssociation: AssociationToOne { - /// :nodoc: - public typealias OriginRowDecoder = Origin - - /// :nodoc: - public typealias RowDecoder = Destination - - /// :nodoc: - public var sqlAssociation: SQLAssociation - - /// :nodoc: - public init(sqlAssociation: SQLAssociation) { - self.sqlAssociation = sqlAssociation - } -} - -// Allow BelongsToAssociation(...).filter(key: ...) -extension BelongsToAssociation: TableRequest where Destination: TableRecord { } diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasManyAssociation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasManyAssociation.swift deleted file mode 100755 index d02586f..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasManyAssociation.swift +++ /dev/null @@ -1,78 +0,0 @@ -/// The HasMany association indicates a one-to-many connection between two -/// record types, such as each instance of the declaring record "has many" -/// instances of the other record. -/// -/// For example, if your application includes authors and books, and each author -/// is assigned zero or more books, you'd declare the association this way: -/// -/// struct Book: TableRecord { ... } -/// struct Author: TableRecord { -/// static let books = hasMany(Book.self) -/// ... -/// } -/// -/// HasMany associations should be supported by an SQLite foreign key. -/// -/// Foreign keys are the recommended way to declare relationships between -/// database tables because not only will SQLite guarantee the integrity of your -/// data, but GRDB will be able to use those foreign keys to automatically -/// configure your association. -/// -/// You define the foreign key when you create database tables. For example: -/// -/// try db.create(table: "author") { t in -/// t.autoIncrementedPrimaryKey("id") // (1) -/// t.column("name", .text) -/// } -/// try db.create(table: "book") { t in -/// t.autoIncrementedPrimaryKey("id") -/// t.column("authorId", .integer) // (2) -/// .notNull() // (3) -/// .indexed() // (4) -/// .references("author", onDelete: .cascade) // (5) -/// t.column("title", .text) -/// } -/// -/// 1. The author table has a primary key. -/// 2. The book.authorId column is used to link a book to the author it -/// belongs to. -/// 3. Make the book.authorId column not null if you want SQLite to guarantee -/// that all books have an author. -/// 4. Create an index on the book.authorId column in order to ease the -/// selection of an author's books. -/// 5. Create a foreign key from book.authorId column to authors.id, so that -/// SQLite guarantees that no book refers to a missing author. The -/// `onDelete: .cascade` option has SQLite automatically delete all of an -/// author's books when that author is deleted. -/// See https://sqlite.org/foreignkeys.html#fk_actions for more information. -/// -/// The example above uses auto-incremented primary keys. But generally -/// speaking, all primary keys are supported. -/// -/// If the database schema does not define foreign keys between tables, you can -/// still use HasMany associations. But your help is needed to define the -/// missing foreign key: -/// -/// struct Author: TableRecord { -/// static let books = hasMany(Book.self, using: ForeignKey(...)) -/// } -/// -/// See ForeignKey for more information. -public struct HasManyAssociation: AssociationToMany { - /// :nodoc: - public typealias OriginRowDecoder = Origin - - /// :nodoc: - public typealias RowDecoder = Destination - - /// :nodoc: - public var sqlAssociation: SQLAssociation - - /// :nodoc: - public init(sqlAssociation: SQLAssociation) { - self.sqlAssociation = sqlAssociation - } -} - -// Allow HasManyAssociation(...).filter(key: ...) -extension HasManyAssociation: TableRequest where Destination: TableRecord { } diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasManyThroughAssociation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasManyThroughAssociation.swift deleted file mode 100755 index e4e5cb3..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasManyThroughAssociation.swift +++ /dev/null @@ -1,61 +0,0 @@ -/// The **HasManyThrough** association is often used to set up a many-to-many -/// connection with another record. This association indicates that the -/// declaring record can be matched with zero or more instances of another -/// record by proceeding through a third record. -/// -/// For example, consider the practice of passport delivery. One coutry -/// "has many" citizens "through" its passports: -/// -/// struct Country: TableRecord { -/// static let passports = hasMany(Passport.self) -/// static let citizens = hasMany(Citizen.self, through: passports, using: Passport.citizen) -/// ... -/// } -/// -/// struct Passport: TableRecord { -/// static let citizen = belongsTo(Citizen.self) -/// ... -/// } -/// -/// struct Citizen: TableRecord { ... } -/// -/// The **HasManyThrough** association is also useful for setting up -/// "shortcuts" through nested HasMany associations. For example, if a document -/// has many sections, and a section has many paragraphs, you may sometimes want -/// to get a simple collection of all paragraphs in the document. You could set -/// that up this way: -/// -/// struct Document: TableRecord { -/// static let sections = hasMany(Section.self) -/// static let paragraphs = hasMany(Paragraph.self, through: sections, using: Section.paragraphs) -/// } -/// -/// struct Section: TableRecord { -/// static let paragraphs = hasMany(Paragraph.self) -/// } -/// -/// struct Paragraph: TableRecord { -/// } -/// -/// As in the examples above, **HasManyThrough** association is always built from -/// two other associations: the `through:` and `using:` arguments. Those -/// associations can be any other association (BelongsTo, HasMany, -/// HasManyThrough, etc). -public struct HasManyThroughAssociation: AssociationToMany { - /// :nodoc: - public typealias OriginRowDecoder = Origin - - /// :nodoc: - public typealias RowDecoder = Destination - - /// :nodoc: - public var sqlAssociation: SQLAssociation - - /// :nodoc: - public init(sqlAssociation: SQLAssociation) { - self.sqlAssociation = sqlAssociation - } -} - -// Allow HasManyThroughAssociation(...).filter(key: ...) -extension HasManyThroughAssociation: TableRequest where Destination: TableRecord { } diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasOneAssociation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasOneAssociation.swift deleted file mode 100755 index 6b1f49f..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasOneAssociation.swift +++ /dev/null @@ -1,80 +0,0 @@ -/// The HasOne association indicates a one-to-one connection between two -/// record types, such as each instance of the declaring record "has one" -/// instances of the other record. -/// -/// For example, if your application has one database table for countries, and -/// another for their demographic profiles, you'd declare the association -/// this way: -/// -/// struct Demographics: TableRecord { ... } -/// struct Country: TableRecord { -/// static let demographics = hasOne(Demographics.self) -/// ... -/// } -/// -/// HasOne associations should be supported by an SQLite foreign key. -/// -/// Foreign keys are the recommended way to declare relationships between -/// database tables because not only will SQLite guarantee the integrity of your -/// data, but GRDB will be able to use those foreign keys to automatically -/// configure your association. -/// -/// You define the foreign key when you create database tables. For example: -/// -/// try db.create(table: "country") { t in -/// t.column("code", .text).primaryKey() // (1) -/// t.column("name", .text) -/// } -/// try db.create(table: "demographics") { t in -/// t.autoIncrementedPrimaryKey("id") -/// t.column("countryCode", .text) // (2) -/// .notNull() // (3) -/// .unique() // (4) -/// .references("country", onDelete: .cascade) // (5) -/// t.column("population", .integer) -/// t.column("density", .double) -/// } -/// -/// 1. The country table has a primary key. -/// 2. The demographics.countryCode column is used to link a demographic -/// profile to the country it belongs to. -/// 3. Make the demographics.countryCode column not null if you want SQLite to -/// guarantee that all profiles are linked to a country. -/// 4. Create a unique index on the demographics.countryCode column in order to -/// guarantee the unicity of any country's profile. -/// 5. Create a foreign key from demographics.countryCode column to -/// country.code, so that SQLite guarantees that no profile refers to a -/// missing country. The `onDelete: .cascade` option has SQLite automatically -/// delete a profile when its country is deleted. -/// See https://sqlite.org/foreignkeys.html#fk_actions for more information. -/// -/// The example above uses a string primary for the country table. But generally -/// speaking, all primary keys are supported. -/// -/// If the database schema does not follow this convention, and does not define -/// foreign keys between tables, you can still use HasOne associations. But -/// your help is needed to define the missing foreign key: -/// -/// struct Country: FetchableRecord, TableRecord { -/// static let demographics = hasOne(Demographics.self, using: ForeignKey(...)) -/// } -/// -/// See ForeignKey for more information. -public struct HasOneAssociation: AssociationToOne { - /// :nodoc: - public typealias OriginRowDecoder = Origin - - /// :nodoc: - public typealias RowDecoder = Destination - - /// :nodoc: - public var sqlAssociation: SQLAssociation - - /// :nodoc: - public init(sqlAssociation: SQLAssociation) { - self.sqlAssociation = sqlAssociation - } -} - -// Allow HasOneAssociation(...).filter(key: ...) -extension HasOneAssociation: TableRequest where Destination: TableRecord { } diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasOneThroughAssociation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasOneThroughAssociation.swift deleted file mode 100755 index be25e31..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/Association/HasOneThroughAssociation.swift +++ /dev/null @@ -1,41 +0,0 @@ -/// A **HasOneThrough** association sets up a one-to-one connection with -/// another record. This association indicates that the declaring record can be -/// matched with one instance of another record by proceeding through a third -/// record. For example, if each book belongs to a library, and each library has -/// one address, then one knows where the book should be returned to: -/// -/// struct Book: TableRecord { -/// static let library = belongsTo(Library.self) -/// static let returnAddress = hasOne(Address.self, through: library, using: library.address) -/// ... -/// } -/// -/// struct Library: TableRecord { -/// static let address = hasOne(Address.self) -/// ... -/// } -/// -/// struct Address: TableRecord { ... } -/// -/// As in the example above, **HasOneThrough** association is always built from -/// two other associations: the `through:` and `using:` arguments. Those -/// associations can be any other association to one (BelongsTo, HasOne, -/// HasOneThrough). -public struct HasOneThroughAssociation: AssociationToOne { - /// :nodoc: - public typealias OriginRowDecoder = Origin - - /// :nodoc: - public typealias RowDecoder = Destination - - /// :nodoc: - public var sqlAssociation: SQLAssociation - - /// :nodoc: - public init(sqlAssociation: SQLAssociation) { - self.sqlAssociation = sqlAssociation - } -} - -// Allow HasOneThroughAssociation(...).filter(key: ...) -extension HasOneThroughAssociation: TableRequest where Destination: TableRecord { } diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/QueryInterfaceRequest+Association.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/QueryInterfaceRequest+Association.swift deleted file mode 100755 index 8ef57e1..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/QueryInterfaceRequest+Association.swift +++ /dev/null @@ -1,102 +0,0 @@ -extension QueryInterfaceRequest where RowDecoder: TableRecord { - // MARK: - Associations - - /// Creates a request that prefetches an association. - public func including(all association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { - return mapQuery { - $0.mapRelation { - $0.including(all: association.sqlAssociation) - } - } - } - - /// Creates a request that includes an association. The columns of the - /// associated record are selected. The returned request does not - /// require that the associated database table contains a matching row. - public func including(optional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { - return mapQuery { - $0.mapRelation { - $0.including(optional: association.sqlAssociation) - } - } - } - - /// Creates a request that includes an association. The columns of the - /// associated record are selected. The returned request requires - /// that the associated database table contains a matching row. - public func including(required association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { - return mapQuery { - $0.mapRelation { - $0.including(required: association.sqlAssociation) - } - } - } - - /// Creates a request that joins an association. The columns of the - /// associated record are not selected. The returned request does not - /// require that the associated database table contains a matching row. - public func joining(optional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { - return mapQuery { - $0.mapRelation { - $0.joining(optional: association.sqlAssociation) - } - } - } - - /// Creates a request that joins an association. The columns of the - /// associated record are not selected. The returned request requires - /// that the associated database table contains a matching row. - public func joining(required association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { - return mapQuery { - $0.mapRelation { - $0.joining(required: association.sqlAssociation) - } - } - } - - // MARK: - Association Aggregates - - private func annotated(with aggregate: AssociationAggregate) -> QueryInterfaceRequest { - let (request, expression) = aggregate.prepare(self) - if let alias = aggregate.alias { - return request.annotated(with: [expression.aliased(alias)]) - } else { - return request.annotated(with: [expression]) - } - } - - /// Creates a request which appends *aggregates* to the current selection. - /// - /// // SELECT player.*, COUNT(DISTINCT book.rowid) AS bookCount - /// // FROM player LEFT JOIN book ... - /// var request = Player.all() - /// request = request.annotated(with: Player.books.count) - public func annotated(with aggregates: AssociationAggregate...) -> QueryInterfaceRequest { - return annotated(with: aggregates) - } - - /// Creates a request which appends *aggregates* to the current selection. - /// - /// // SELECT player.*, COUNT(DISTINCT book.rowid) AS bookCount - /// // FROM player LEFT JOIN book ... - /// var request = Player.all() - /// request = request.annotated(with: [Player.books.count]) - public func annotated(with aggregates: [AssociationAggregate]) -> QueryInterfaceRequest { - return aggregates.reduce(self) { request, aggregate in - request.annotated(with: aggregate) - } - } - - /// Creates a request which appends the provided aggregate *predicate* to - /// the eventual set of already applied predicates. - /// - /// // SELECT player.* - /// // FROM player LEFT JOIN book ... - /// // HAVING COUNT(DISTINCT book.rowid) = 0 - /// var request = Player.all() - /// request = request.having(Player.books.isEmpty) - public func having(_ predicate: AssociationAggregate) -> QueryInterfaceRequest { - let (request, expression) = predicate.prepare(self) - return request.having(expression) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/QueryInterfaceRequest.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/QueryInterfaceRequest.swift deleted file mode 100755 index 39f4a26..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/QueryInterfaceRequest.swift +++ /dev/null @@ -1,424 +0,0 @@ -// QueryInterfaceRequest is the type of requests generated by TableRecord: -// -// struct Player: TableRecord { ... } -// let playerRequest = Player.all() // QueryInterfaceRequest -// -// It wraps an SQLQuery, and has an attached type. -// -// The attached type helps decoding raw database values: -// -// try dbQueue.read { db in -// try playerRequest.fetchAll(db) // [Player] -// } -// -// The attached type also helps the compiler validate associated requests: -// -// playerRequest.including(required: Player.team) // OK -// fruitRequest.including(required: Player.team) // Does not compile - -/// QueryInterfaceRequest is a request that generates SQL for you. -/// -/// For example: -/// -/// try dbQueue.read { db in -/// let request = Player -/// .filter(Column("score") > 1000) -/// .order(Column("name")) -/// let players = try request.fetchAll(db) // [Player] -/// } -/// -/// See https://github.com/groue/GRDB.swift#the-query-interface -public struct QueryInterfaceRequest { - var query: SQLQuery - - init(query: SQLQuery) { - self.query = query - } - - init(relation: SQLRelation) { - self.init(query: SQLQuery(relation: relation)) - } - - var prefetchedAssociations: [SQLAssociation] { - return query.relation.prefetchedAssociations - } -} - -extension QueryInterfaceRequest : FetchRequest { - public typealias RowDecoder = T - - /// Returns a tuple that contains a prepared statement that is ready to be - /// executed, and an eventual row adapter. - /// - /// - parameter db: A database connection. - /// - parameter singleResult: A hint as to whether the query should be optimized for a single result. - /// - returns: A prepared statement and an eventual row adapter. - /// :nodoc: - public func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?) { - var query = self.query - - // Optimize query by setting a limit of 1 when appropriate - if singleResult && !query.expectsSingleResult { - query.limit = SQLLimit(limit: 1, offset: query.limit?.offset) - } - - return try SQLQueryGenerator(query).prepare(db) - } - - /// Returns the number of rows fetched by the request. - /// - /// - parameter db: A database connection. - /// :nodoc: - public func fetchCount(_ db: Database) throws -> Int { - return try query.fetchCount(db) - } - - /// Returns the database region that the request looks into. - /// - /// - parameter db: A database connection. - /// :nodoc: - public func databaseRegion(_ db: Database) throws -> DatabaseRegion { - var region = try SQLQueryGenerator(query).databaseRegion(db) - - // Iterate all prefetched associations - var fifo = query.relation.prefetchedAssociations - while !fifo.isEmpty { - let association = fifo.removeFirst() - - // Build the query for prefetched rows. - // CAUTION: Keep this code in sync with Row.prefetch(_:associations:in:) - let pivotMappings = try association.pivot.condition.columnMappings(db) - let pivotColumns = pivotMappings.map { $0.right } - let pivotAlias = TableAlias() - let prefetchedRelation = association - .mapPivotRelation { $0.qualified(with: pivotAlias) } - .destinationRelation(fromOriginRows: { _ in [] /* no origin row */ }) - .annotated(with: pivotColumns.map { pivotAlias[Column($0)].aliased("grdb_\($0)") }) - let prefetchedQuery = SQLQuery(relation: prefetchedRelation) - - // Union region - try region.formUnion(SQLQueryGenerator(prefetchedQuery).databaseRegion(db)) - - // Append nested prefetched associations (support for - // A.including(all: A.bs.including(all: B.cs)) - fifo.append(contentsOf: prefetchedRelation.prefetchedAssociations) - } - - return region - } -} - -extension QueryInterfaceRequest : DerivableRequest, AggregatingRequest { - - // MARK: Request Derivation - - /// Creates a request which selects *selection*. - /// - /// // SELECT id, email FROM player - /// var request = Player.all() - /// request = request.select([Column("id"), Column("email")]) - /// - /// Any previous selection is replaced: - /// - /// // SELECT email FROM player - /// request - /// .select([Column("id")]) - /// .select([Column("email")]) - public func select(_ selection: [SQLSelectable]) -> QueryInterfaceRequest { - return mapQuery { $0.select(selection) } - } - - /// Creates a request which selects *selection*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.all().select([max(Column("score"))], as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public func select(_ selection: [SQLSelectable], as type: RowDecoder.Type) -> QueryInterfaceRequest { - return mapQuery { $0.select(selection) }.asRequest(of: RowDecoder.self) - } - - /// Creates a request which selects *selection*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.all().select(max(Column("score")), as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public func select(_ selection: SQLSelectable..., as type: RowDecoder.Type) -> QueryInterfaceRequest { - return select(selection, as: type) - } - - /// Creates a request which selects *sql*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.all().select(sql: "max(score)", as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public func select(sql: String, arguments: StatementArguments = StatementArguments(), as type: RowDecoder.Type) -> QueryInterfaceRequest { - return select(literal: SQLLiteral(sql: sql, arguments: arguments), as: type) - } - - /// Creates a request which selects an SQL *literal*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT IFNULL(name, 'Anonymous') FROM player WHERE id = 42 - /// let request = Player. - /// .filter(primaryKey: 42) - /// .select( - /// SQLLiteral( - /// sql: "IFNULL(name, ?)", - /// arguments: ["Anonymous"]), - /// as: String.self) - /// let name: String? = try request.fetchOne(db) - /// } - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// try dbQueue.read { db in - /// // SELECT IFNULL(name, 'Anonymous') FROM player WHERE id = 42 - /// let request = Player. - /// .filter(primaryKey: 42) - /// .select( - /// literal: "IFNULL(name, \("Anonymous"))", - /// as: String.self) - /// let name: String? = try request.fetchOne(db) - /// } - public func select(literal sqlLiteral: SQLLiteral, as type: RowDecoder.Type) -> QueryInterfaceRequest { - return select(SQLSelectionLiteral(literal: sqlLiteral), as: type) - } - - /// Creates a request which appends *selection*. - /// - /// // SELECT id, email, name FROM player - /// var request = Player.all() - /// request = request - /// .select([Column("id"), Column("email")]) - /// .annotated(with: [Column("name")]) - public func annotated(with selection: [SQLSelectable]) -> QueryInterfaceRequest { - // TODO: test consumption of - // - // let author = TableAlias() - // let request = Book - // .annotated(with: [author[Column("name")]]) - // .joining(requireed: Book.author) - // - // The problem is the "author.name" column. Can we consume it as "name"? - // From raw rows? From copied rows? - return mapQuery { $0.annotated(with: selection) } - } - - /// Creates a request which returns distinct rows. - /// - /// // SELECT DISTINCT * FROM player - /// var request = Player.all() - /// request = request.distinct() - /// - /// // SELECT DISTINCT name FROM player - /// var request = Player.select(Column("name")) - /// request = request.distinct() - public func distinct() -> QueryInterfaceRequest { - return mapQuery { $0.distinct() } - } - - /// Creates a request with the provided *predicate promise* added to the - /// eventual set of already applied predicates. - /// - /// // SELECT * FROM player WHERE 1 - /// var request = Player.all() - /// request = request.filter { db in true } - public func filter(_ predicate: @escaping (Database) throws -> SQLExpressible) -> QueryInterfaceRequest { - return mapQuery { $0.filter(predicate) } - } - - /// Creates a request which expects a single result. - /// - /// It is unlikely you need to call this method. Its net effect is that - /// QueryInterfaceRequest does not use any `LIMIT 1` sql clause when you - /// call a `fetchOne` method. - /// - /// :nodoc: - public func expectingSingleResult() -> QueryInterfaceRequest { - return mapQuery { $0.expectingSingleResult() } - } - - /// Creates a request grouped according to *expressions promise*. - public func group(_ expressions: @escaping (Database) throws -> [SQLExpressible]) -> QueryInterfaceRequest { - return mapQuery { $0.group(expressions) } - } - - /// Creates a request with the provided *predicate* added to the - /// eventual set of already applied predicates. - public func having(_ predicate: SQLExpressible) -> QueryInterfaceRequest { - return mapQuery { $0.having(predicate) } - } - - /// Creates a request with the provided *orderings promise*. - /// - /// // SELECT * FROM player ORDER BY name - /// var request = Player.all() - /// request = request.order { _ in [Column("name")] } - /// - /// Any previous ordering is replaced: - /// - /// // SELECT * FROM player ORDER BY name - /// request - /// .order{ _ in [Column("email")] } - /// .reversed() - /// .order{ _ in [Column("name")] } - public func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> QueryInterfaceRequest { - return mapQuery { $0.order(orderings) } - } - - /// Creates a request that reverses applied orderings. - /// - /// // SELECT * FROM player ORDER BY name DESC - /// var request = Player.all().order(Column("name")) - /// request = request.reversed() - /// - /// If no ordering was applied, the returned request is identical. - /// - /// // SELECT * FROM player - /// var request = Player.all() - /// request = request.reversed() - public func reversed() -> QueryInterfaceRequest { - return mapQuery { $0.reversed() } - } - - /// Creates a request without any ordering. - /// - /// // SELECT * FROM player - /// var request = Player.all().order(Column("name")) - /// request = request.unordered() - public func unordered() -> QueryInterfaceRequest { - return mapQuery { $0.unordered() } - } - - /// Creates a request which fetches *limit* rows, starting at *offset*. - /// - /// // SELECT * FROM player LIMIT 1 - /// var request = Player.all() - /// request = request.limit(1) - /// - /// Any previous limit is replaced. - public func limit(_ limit: Int, offset: Int? = nil) -> QueryInterfaceRequest { - return mapQuery { $0.limit(limit, offset: offset) } - } - - /// Creates a request that allows you to define expressions that target - /// a specific database table. - /// - /// In the example below, the "team.avgScore < player.score" condition in - /// the ON clause could be not achieved without table aliases. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // JOIN team ON ... AND team.avgScore < player.score - /// let playerAlias = TableAlias() - /// let request = Player - /// .all() - /// .aliased(playerAlias) - /// .including(required: Player.team.filter(Column("avgScore") < playerAlias[Column("score")]) - public func aliased(_ alias: TableAlias) -> QueryInterfaceRequest { - return mapQuery { $0.qualified(with: alias) } - } - - /// Creates a request bound to type Target. - /// - /// The returned request can fetch if the type Target is fetchable (Row, - /// value, record). - /// - /// // Int? - /// let maxScore = try Player - /// .select(max(scoreColumn)) - /// .asRequest(of: Int.self) // <-- - /// .fetchOne(db) - /// - /// - parameter type: The fetched type Target - /// - returns: A typed request bound to type Target. - public func asRequest(of type: RowDecoder.Type) -> QueryInterfaceRequest { - return QueryInterfaceRequest(query: query) - } - - /// Returns a request whose query is transformed by the given closure. - func mapQuery(_ transform: (SQLQuery) -> SQLQuery) -> QueryInterfaceRequest { - var request = self - request.query = transform(query) - return request - } -} - -extension QueryInterfaceRequest { - /// Turns a request into a SQLRelation. - /// - /// This method helps initializing associations: - /// - /// struct Book: TableRecord { - /// // invokes Author.all().relation - /// static let author = belongsTo(Author.self) - /// } - var relation: SQLRelation { - let query = self.query - - // Prevent information loss - GRDBPrecondition(!query.isDistinct, "Not implemented: join distinct queries") - GRDBPrecondition(query.groupPromise == nil, "Can't join aggregated queries") - GRDBPrecondition(query.havingExpression == nil, "Can't join aggregated queries") - GRDBPrecondition(query.limit == nil, "Can't join limited queries") - - return query.relation - } -} - -extension QueryInterfaceRequest: TableRequest { - /// :nodoc: - public var databaseTableName: String { - switch query.relation.source { - case .table(tableName: let tableName, alias: _): - // Use case: - // - // let request = Player.all() - // request.filter(key: ...) - // request.filter(keys: ...) - // request.orderByPrimaryKey() - return tableName - case .query: - // The only current use case for SQLSource.query is the - // "trivial count query" (see SQLQuery.countQuery): - // - // // SELECT COUNT(*) FROM (SELECT * FROM player LIMIT 10) - // let request = Player.limit(10) - // let count = try request.fetchCount(db) - // - // This query is currently never wrapped in a QueryInterfaceRequest - // So this fatal error can not currently happen. - fatalError("Request is not based on a database table") - } - } -} - -extension QueryInterfaceRequest where T: MutablePersistableRecord { - - // MARK: Deleting - - /// Deletes matching rows; returns the number of deleted rows. - /// - /// - parameter db: A database connection. - /// - returns: The number of deleted rows - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult - public func deleteAll(_ db: Database) throws -> Int { - try SQLQueryGenerator(query).makeDeleteStatement(db).execute() - return db.changesCount - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/RequestProtocols.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/RequestProtocols.swift deleted file mode 100755 index 44f6901..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Request/RequestProtocols.swift +++ /dev/null @@ -1,522 +0,0 @@ -// MARK: - SelectionRequest - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all requests that can refine their selection. -/// -/// :nodoc: -public protocol SelectionRequest { - /// Creates a request which selects *selection*. - /// - /// // SELECT id, email FROM player - /// var request = Player.all() - /// request = request.select([Column("id"), Column("email")]) - /// - /// Any previous selection is replaced: - /// - /// // SELECT email FROM player - /// request - /// .select([Column("id")]) - /// .select([Column("email")]) - func select(_ selection: [SQLSelectable]) -> Self - - /// Creates a request which appends *selection*. - /// - /// // SELECT id, email, name FROM player - /// var request = Player.all() - /// request = request - /// .select([Column("id"), Column("email")]) - /// .annotated(with: [Column("name")]) - func annotated(with selection: [SQLSelectable]) -> Self -} - -/// :nodoc: -extension SelectionRequest { - /// Creates a request which selects *selection*. - /// - /// // SELECT id, email FROM player - /// var request = Player.all() - /// request = request.select(Column("id"), Column("email")) - /// - /// Any previous selection is replaced: - /// - /// // SELECT email FROM player - /// request - /// .select(Column("id")) - /// .select(Column("email")) - public func select(_ selection: SQLSelectable...) -> Self { - return select(selection) - } - - /// Creates a request which selects *sql*. - /// - /// // SELECT id, email FROM player - /// var request = Player.all() - /// request = request.select(sql: "id, email") - /// - /// Any previous selection is replaced: - /// - /// // SELECT email FROM player - /// request - /// .select(sql: "id") - /// .select(sql: "email") - public func select(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { - return select(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request which selects an SQL *literal*. - /// - /// // SELECT id, email, score + 1000 FROM player - /// let bonus = 1000 - /// var request = Player.all() - /// request = request.select(literal: SQLLiteral(sql: """ - /// id, email, score + ? - /// """, arguments: [bonus])) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// // SELECT id, email, score + 1000 FROM player - /// let bonus = 1000 - /// var request = Player.all() - /// request = request.select(literal: """ - /// id, email, score + \(bonus) - /// """) - /// - /// Any previous selection is replaced: - /// - /// // SELECT email FROM player - /// request - /// .select(...) - /// .select(literal: SQLLiteral(sql: "email")) - public func select(literal sqlLiteral: SQLLiteral) -> Self { - // NOT TESTED - return select(SQLSelectionLiteral(literal: sqlLiteral)) - } - - /// Creates a request which appends *selection*. - /// - /// // SELECT id, email, name FROM player - /// var request = Player.all() - /// request = request - /// .select([Column("id"), Column("email")]) - /// .annotated(with: Column("name")) - public func annotated(with selection: SQLSelectable...) -> Self { - return annotated(with: selection) - } -} - -// MARK: - FilteredRequest - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all requests that can be filtered. -/// -/// :nodoc: -public protocol FilteredRequest { - /// Creates a request with the provided *predicate promise* added to the - /// eventual set of already applied predicates. - /// - /// // SELECT * FROM player WHERE 1 - /// var request = Player.all() - /// request = request.filter { db in true } - func filter(_ predicate: @escaping (Database) throws -> SQLExpressible) -> Self - - /// Creates a request which expects a single result. - /// - /// Requests expecting a single result may ignore the second parameter of - /// the `FetchRequest.prepare(_:forSingleResult:)` method, in order to - /// produce sharply tailored SQL. - /// - /// This method has a default implementation which returns self. - func expectingSingleResult() -> Self -} - -/// :nodoc: -extension FilteredRequest { - public func expectingSingleResult() -> Self { - return self - } - - /// Creates a request with the provided *predicate* added to the - /// eventual set of already applied predicates. - /// - /// // SELECT * FROM player WHERE email = 'arthur@example.com' - /// var request = Player.all() - /// request = request.filter(Column("email") == "arthur@example.com") - public func filter(_ predicate: SQLExpressible) -> Self { - return filter { _ in predicate } - } - - /// Creates a request with the provided *predicate* added to the - /// eventual set of already applied predicates. - /// - /// // SELECT * FROM player WHERE email = 'arthur@example.com' - /// var request = Player.all() - /// request = request.filter(sql: "email = ?", arguments: ["arthur@example.com"]) - public func filter(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { - return filter(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request with the provided *predicate* added to the - /// eventual set of already applied predicates. - /// - /// // SELECT * FROM player WHERE email = 'arthur@example.com' - /// var request = Player.all() - /// request = request.filter(literal: SQLLiteral(sql: """ - /// email = ? - /// """, arguments: ["arthur@example.com"]) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// var request = Player.all() - /// request = request.filter(literal: "name = \("O'Brien")") - public func filter(literal sqlLiteral: SQLLiteral) -> Self { - // NOT TESTED - return filter(SQLExpressionLiteral(literal: sqlLiteral)) - } - - /// Creates a request that matches nothing. - /// - /// // SELECT * FROM player WHERE 0 - /// var request = Player.all() - /// request = request.none() - public func none() -> Self { - return filter(false) - } -} - -// MARK: - TableRequest { - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all requests that feed from a database table -/// -/// :nodoc: -public protocol TableRequest { - /// The name of the database table - var databaseTableName: String { get } - - /// Creates a request that allows you to define expressions that target - /// a specific database table. - /// - /// In the example below, the "team.avgScore < player.score" condition in - /// the ON clause could be not achieved without table aliases. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // JOIN team ON ... AND team.avgScore < player.score - /// let playerAlias = TableAlias() - /// let request = Player - /// .all() - /// .aliased(playerAlias) - /// .including(required: Player.team.filter(Column("avgScore") < playerAlias[Column("score")]) - func aliased(_ alias: TableAlias) -> Self -} - -/// :nodoc: -extension TableRequest where Self: FilteredRequest { - - /// Creates a request with the provided primary key *predicate*. - public func filter(key: PrimaryKeyType?) -> Self { - guard let key = key else { - return none() - } - return filter(keys: [key]) - } - - /// Creates a request with the provided primary key *predicate*. - public func filter(keys: Sequence) -> Self where Sequence.Element: DatabaseValueConvertible { - var request = self - let keys = Array(keys) - switch keys.count { - case 0: - return none() - case 1: - request = request.expectingSingleResult() - default: - break - } - - let databaseTableName = self.databaseTableName - return request.filter { db in - let primaryKey = try db.primaryKey(databaseTableName) - GRDBPrecondition( - primaryKey.columns.count == 1, - "Requesting by key requires a single-column primary key in the table \(databaseTableName)") - return keys.contains(Column(primaryKey.columns[0])) - } - } - - /// Creates a request with the provided primary key *predicate*. - /// - /// When executed, this request raises a fatal error if there is no unique - /// index on the key columns. - public func filter(key: [String: DatabaseValueConvertible?]?) -> Self { - guard let key = key else { - return none() - } - return filter(keys: [key]) - } - - /// Creates a request with the provided primary key *predicate*. - /// - /// When executed, this request raises a fatal error if there is no unique - /// index on the key columns. - public func filter(keys: [[String: DatabaseValueConvertible?]]) -> Self { - var request = self - switch keys.count { - case 0: - return none() - case 1: - request = request.expectingSingleResult() - default: - break - } - - let databaseTableName = self.databaseTableName - return request.filter { db in - try keys - .map { key in - // Prevent filter(keys: [["foo": 1, "bar": 2]]) where - // ("foo", "bar") is not a unique key (primary key or columns of a - // unique index) - guard let columns = try db.columnsForUniqueKey(key.keys, in: databaseTableName) else { - fatalError("table \(databaseTableName) has no unique index on column(s) \(key.keys.sorted().joined(separator: ", "))") - } - - let lowercaseColumns = columns.map { $0.lowercased() } - return key - // Preserve ordering of columns in the unique index - .sorted { (kv1, kv2) in - let index1 = lowercaseColumns.firstIndex(of: kv1.key.lowercased())! - let index2 = lowercaseColumns.firstIndex(of: kv2.key.lowercased())! - return index1 < index2 - } - .map { (column, value) in Column(column) == value } - .joined(operator: .and) - } - .joined(operator: .or) - } - } -} - -/// :nodoc: -extension TableRequest where Self: OrderedRequest { - /// Creates a request ordered by primary key. - public func orderByPrimaryKey() -> Self { - let tableName = self.databaseTableName - return order { db in - try db.primaryKey(tableName).columns.map { Column($0) } - } - } -} - -/// :nodoc: -extension TableRequest where Self: AggregatingRequest { - /// Creates a request grouped by primary key. - public func groupByPrimaryKey() -> Self { - let tableName = self.databaseTableName - return group { db in - try db.primaryKey(tableName).columns.map { Column($0) } - } - } -} - -// MARK: - AggregatingRequest - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all requests that can aggregate. -/// -/// :nodoc: -public protocol AggregatingRequest { - /// Creates a request grouped according to *expressions promise*. - func group(_ expressions: @escaping (Database) throws -> [SQLExpressible]) -> Self - - /// Creates a request with the provided *predicate* added to the - /// eventual set of already applied predicates. - func having(_ predicate: SQLExpressible) -> Self -} - -/// :nodoc: -extension AggregatingRequest { - /// Creates a request grouped according to *expressions*. - public func group(_ expressions: [SQLExpressible]) -> Self { - return group { _ in expressions } - } - - /// Creates a request grouped according to *expressions*. - public func group(_ expressions: SQLExpressible...) -> Self { - return group(expressions) - } - - /// Creates a request with a new grouping. - public func group(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { - return group(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request with a new grouping. - public func group(literal sqlLiteral: SQLLiteral) -> Self { - // NOT TESTED - // This "expression" is not a real expression. We support raw sql which - // actually contains several expressions: - // - // request = Player.group(sql: "teamId, level") - // - // This is why we use the "unsafeLiteral" initializer, so that the - // SQLExpressionLiteral does not wrap input in parentheses, and - // generates invalid SQL `GROUP BY (teamId, level)`. - return group(SQLExpressionLiteral(unsafeLiteral: sqlLiteral)) - } - - /// Creates a request with the provided *sql* added to the - /// eventual set of already applied predicates. - public func having(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { - return having(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request with the provided *sql* added to the - /// eventual set of already applied predicates. - public func having(literal sqlLiteral: SQLLiteral) -> Self { - // NOT TESTED - return having(SQLExpressionLiteral(literal: sqlLiteral)) - } -} - -// MARK: - OrderedRequest - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all requests that be ordered. -/// -/// :nodoc: -public protocol OrderedRequest { - /// Creates a request with the provided *orderings promise*. - /// - /// // SELECT * FROM player ORDER BY name - /// var request = Player.all() - /// request = request.order { _ in [Column("name")] } - /// - /// Any previous ordering is replaced: - /// - /// // SELECT * FROM player ORDER BY name - /// request - /// .order{ _ in [Column("email")] } - /// .reversed() - /// .order{ _ in [Column("name")] } - func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> Self - - /// Creates a request that reverses applied orderings. - /// - /// // SELECT * FROM player ORDER BY name DESC - /// var request = Player.all().order(Column("name")) - /// request = request.reversed() - /// - /// If no ordering was applied, the returned request is identical. - /// - /// // SELECT * FROM player - /// var request = Player.all() - /// request = request.reversed() - func reversed() -> Self - - /// Creates a request without any ordering. - /// - /// // SELECT * FROM player - /// var request = Player.all().order(Column("name")) - /// request = request.unordered() - func unordered() -> Self -} - -/// :nodoc: -extension OrderedRequest { - /// Creates a request with the provided *orderings*. - /// - /// // SELECT * FROM player ORDER BY name - /// var request = Player.all() - /// request = request.order(Column("name")) - /// - /// Any previous ordering is replaced: - /// - /// // SELECT * FROM player ORDER BY name - /// request - /// .order(Column("email")) - /// .reversed() - /// .order(Column("name")) - public func order(_ orderings: SQLOrderingTerm...) -> Self { - return order { _ in orderings } - } - - /// Creates a request with the provided *orderings*. - /// - /// // SELECT * FROM player ORDER BY name - /// var request = Player.all() - /// request = request.order(Column("name")) - /// - /// Any previous ordering is replaced: - /// - /// // SELECT * FROM player ORDER BY name - /// request - /// .order(Column("email")) - /// .reversed() - /// .order(Column("name")) - public func order(_ orderings: [SQLOrderingTerm]) -> Self { - return order { _ in orderings } - } - - /// Creates a request sorted according to *sql*. - /// - /// // SELECT * FROM player ORDER BY name - /// var request = Player.all() - /// request = request.order(sql: "name") - /// - /// Any previous ordering is replaced: - /// - /// // SELECT * FROM player ORDER BY name - /// request - /// .order(sql: "email") - /// .order(sql: "name") - public func order(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { - return order(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request sorted according to an SQL *literal*. - /// - /// // SELECT * FROM player ORDER BY name - /// var request = Player.all() - /// request = request.order(sql: "name") - /// - /// Any previous ordering is replaced: - /// - /// // SELECT * FROM player ORDER BY name - /// request - /// .order(sql: "email") - /// .order(sql: "name") - public func order(literal sqlLiteral: SQLLiteral) -> Self { - // NOT TESTED - // This "expression" is not a real expression. We support raw sql which - // actually contains several expressions: - // - // request = Player.order(sql: "teamId, level") - // - // This is why we use the "unsafeLiteral" initializer, so that the - // SQLExpressionLiteral does not wrap input in parentheses, and - // generates invalid SQL `ORDER BY (teamId, level)`. - return order(SQLExpressionLiteral(unsafeLiteral: sqlLiteral)) - } -} - -// MARK: - DerivableRequest - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The base protocol for all requests that can be refined. -/// -/// :nodoc: -public protocol DerivableRequest: SelectionRequest, FilteredRequest, OrderedRequest { - associatedtype RowDecoder -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Row+QueryInterfaceRequest.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Row+QueryInterfaceRequest.swift deleted file mode 100755 index 4efd97c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Row+QueryInterfaceRequest.swift +++ /dev/null @@ -1,219 +0,0 @@ -extension QueryInterfaceRequest where RowDecoder == Row { - - // MARK: Fetching Rows - - /// A cursor over fetched rows. - /// - /// let request: QueryInterfaceRequest = ... - /// let rows = try request.fetchCursor(db) // RowCursor - /// while let row = try rows.next() { // Row - /// let id: Int64 = row[0] - /// let name: String = row[1] - /// } - /// - /// Fetched rows are reused during the cursor iteration: don't turn a row - /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since - /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` - /// instead. - /// - /// For the same reason, make sure you make a copy whenever you extract a - /// row for later use: `row.copy()`. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> RowCursor { - return try Row.fetchCursor(db, self) - } - - /// An array of fetched rows. - /// - /// let request: QueryInterfaceRequest = ... - /// let rows = try request.fetchAll(db) - /// - /// - parameter db: A database connection. - /// - returns: An array of fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [Row] { - return try Row.fetchAll(db, self) - } - - /// The first fetched row. - /// - /// let request: QueryInterfaceRequest = ... - /// let row = try request.fetchOne(db) - /// - /// - parameter db: A database connection. - /// - returns: An optional row. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchOne(_ db: Database) throws -> Row? { - return try Row.fetchOne(db, self) - } -} - -extension Row { - - // MARK: - Fetching From QueryInterfaceRequest - - /// Returns a cursor over rows fetched from a fetch request. - /// - /// let request = Player.all() - /// let rows = try Row.fetchCursor(db, request) // RowCursor - /// while let row = try rows.next() { // Row - /// let id: Int64 = row["id"] - /// let name: String = row["name"] - /// } - /// - /// Fetched rows are reused during the cursor iteration: don't turn a row - /// cursor into an array with `Array(rows)` or `rows.filter { ... }` since - /// you would not get the distinct rows you expect. Use `Row.fetchAll(...)` - /// instead. - /// - /// For the same reason, make sure you make a copy whenever you extract a - /// row for later use: `row.copy()`. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: A cursor over fetched rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: QueryInterfaceRequest) throws -> RowCursor { - precondition(request.prefetchedAssociations.isEmpty, "Not implemented: fetchCursor with prefetched associations") - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of rows fetched from a fetch request. - /// - /// let request = Player.all() - /// let rows = try Row.fetchAll(db, request) - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An array of rows. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: QueryInterfaceRequest) throws -> [Row] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - let rows = try fetchAll(statement, adapter: adapter) - - let associations = request.prefetchedAssociations - if associations.isEmpty == false { - try prefetch(db, associations: associations, in: rows) - } - return rows - } - - /// Returns a single row fetched from a fetch request. - /// - /// let request = Player.filter(key: 1) - /// let row = try Row.fetchOne(db, request) - /// - /// - parameters: - /// - db: A database connection. - /// - request: A FetchRequest. - /// - returns: An optional row. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, _ request: QueryInterfaceRequest) throws -> Row? { - let (statement, adapter) = try request.prepare(db, forSingleResult: true) - guard let row = try fetchOne(statement, adapter: adapter) else { - return nil - } - - let associations = request.prefetchedAssociations - if associations.isEmpty == false { - try prefetch(db, associations: associations, in: [row]) - } - return row - } - - /// Append rows from prefetched associations into the argument rows. - static func prefetch(_ db: Database, associations: [SQLAssociation], in rows: [Row]) throws { - guard let firstRow = rows.first else { - return - } - - // CAUTION: Keep this code in sync with QueryInterfaceRequest.databaseRegion(_:) - for association in associations { - let pivotMappings = try association.pivot.condition.columnMappings(db) - - let prefetchedRows: [[DatabaseValue] : [Row]] - do { - // Annotate prefetched rows with pivot columns, so that we can - // group them. - // - // Those pivot columns are necessary when we prefetch - // indirect associations: - // - // // SELECT country.*, passport.citizenId AS grdb_citizenId - // // -- ^ the necessary pivot column - // // FROM country - // // JOIN passport ON passport.countryCode = country.code - // // AND passport.citizenId IN (1, 2, 3) - // Citizen.including(all: Citizen.countries) - // - // Those pivot columns are redundant when we prefetch direct - // associations (maybe we'll remove this redundancy later): - // - // // SELECT *, authorId AS grdb_authorId - // // -- ^ the redundant pivot column - // // FROM book - // // WHERE authorId IN (1, 2, 3) - // Author.including(all: Author.books) - let pivotColumns = pivotMappings.map { $0.right } - let pivotAlias = TableAlias() - let prefetchedRelation = association - .mapPivotRelation { $0.qualified(with: pivotAlias) } - .destinationRelation(fromOriginRows: { _ in rows }) - .annotated(with: pivotColumns.map { pivotAlias[Column($0)].aliased("grdb_\($0)") }) - prefetchedRows = try QueryInterfaceRequest(relation: prefetchedRelation) - .fetchAll(db) - .grouped(byDatabaseValuesOnColumns: pivotColumns.map { "grdb_\($0)" }) - // TODO: can we remove those grdb_ columns now that grouping has been done? - } - - let groupingIndexes = firstRow.indexes(ofColumns: pivotMappings.map { $0.left }) - for row in rows { - let groupingKey = groupingIndexes.map { row.impl.databaseValue(atUncheckedIndex: $0) } - let prefetchedRows = prefetchedRows[groupingKey, default: []] - row.prefetchedRows.setRows(prefetchedRows, forKeyPath: association.keyPath) - } - } - } -} - -extension Array where Element == Row { - /// - precondition: Columns all exist in all rows. All rows have the same - /// columnns, in the same order. - fileprivate func grouped(byDatabaseValuesOnColumns columns: [String]) -> [[DatabaseValue]: [Row]] { - guard let firstRow = first else { - return [:] - } - let indexes = firstRow.indexes(ofColumns: columns) - return Dictionary(grouping: self, by: { row in - indexes.map { row.impl.databaseValue(atUncheckedIndex: $0) } - }) - } -} - -extension Row { - /// - precondition: Columns all exist in the row. - fileprivate func indexes(ofColumns columns: [String]) -> [Int] { - return columns.map { column -> Int in - guard let index = index(ofColumn: column) else { - fatalError("Column \(column) is not selected") - } - return index - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/Column.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/Column.swift deleted file mode 100755 index ef13a06..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/Column.swift +++ /dev/null @@ -1,100 +0,0 @@ -/// Adopt the ColumnExpression protocol when you define a column type. -/// -/// You can, for example, define a String-based column enum: -/// -/// enum Columns: String, ColumnExpression { -/// case id, name, score -/// } -/// let arthur = try Player.filter(Columns.name == "Arthur").fetchOne(db) -/// -/// You can also define a genuine column type: -/// -/// struct MyColumn: ColumnExpression { -/// var name: String -/// var sqlType: String -/// } -/// let nameColumn = MyColumn(name: "name", sqlType: "VARCHAR") -/// let arthur = try Player.filter(nameColumn == "Arthur").fetchOne(db) -/// -/// See https://github.com/groue/GRDB.swift#the-query-interface -public protocol ColumnExpression: SQLExpression { - /// The unqualified name of a database column. - /// - /// "score" is a valid unqualified name. "player.score" is not. - var name: String { get } -} - -extension ColumnExpression { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return name.quotedDatabaseIdentifier - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return QualifiedColumn(name, alias: alias) - } -} - -/// A column in a database table. -/// -/// When you need to introduce your own column type, don't wrap a Column. -/// Instead, adopt the ColumnExpression protocol. -/// -/// See https://github.com/groue/GRDB.swift#the-query-interface -public struct Column: ColumnExpression { - /// The hidden rowID column - public static let rowID = Column("rowid") - - /// The name of the column - public var name: String - - /// Creates a column given its name. - public init(_ name: String) { - self.name = name - } - - /// Creates a column given a CodingKey. - public init(_ codingKey: CodingKey) { - self.name = codingKey.stringValue - } -} - -/// A qualified column in the database, as in `SELECT t.a FROM t` -struct QualifiedColumn: ColumnExpression { - var name: String - private let alias: TableAlias - - /// Creates a column given its name. - init(_ name: String, alias: TableAlias) { - self.name = name - self.alias = alias - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - if let qualifier = context.qualifier(for: alias) { - return qualifier.quotedDatabaseIdentifier + "." + name.quotedDatabaseIdentifier - } - return name.quotedDatabaseIdentifier - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - // Never requalify - return self - } -} - -/// Support for column enums: -/// -/// struct Player { -/// enum Columns: String, ColumnExpression { -/// case id, name, score -/// } -/// } -extension ColumnExpression where Self: RawRepresentable, Self.RawValue == String { - public var name: String { - return rawValue - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/DatabasePromise.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/DatabasePromise.swift deleted file mode 100755 index d3194fc..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/DatabasePromise.swift +++ /dev/null @@ -1,50 +0,0 @@ -/// DatabasePromise represents a value that can only be resolved when a -/// database connection is available. -/// -/// This type is important for the query interface, which lets the user define -/// requests without any database context. -/// -/// For example, consider those two requests: -/// -/// let playerRequest = Player.filter(key: 1) -/// let countryRequest = Country.filter(key: "FR") -/// -/// Both need a database connection in order to introspect the database schema, -/// find the primary key of both table, and generate the correct SQL: -/// -/// try dbQueue.read { db in -/// // SELECT * FROM player WHERE id = 1 -/// let player = try playerRequest.fetchOne(db) -/// // SELECT * FROM country WHERE code = 'FR' -/// let country = try countryRequest.fetchOne(db) -/// } -/// -/// Such late computations are backed by DatabasePromise. In our example, -/// see SQLRelation.filterPromise. -struct DatabasePromise { - /// Returns the resolved value. - let resolve: (Database) throws -> T - - /// Creates a promise that resolves to a value. - init(value: T) { - self.resolve = { _ in value } - } - - /// Creates a promise from a closure. - init(_ resolve: @escaping (Database) throws -> T) { - self.resolve = resolve - } - - /// Returns a promise whose value is transformed by the given closure. - func map(_ transform: @escaping (T) throws -> U) -> DatabasePromise { - return DatabasePromise { db in - try transform(self.resolve(db)) - } - } - - func flatMap(_ transform: @escaping (T) -> DatabasePromise) -> DatabasePromise { - return DatabasePromise { db in - try transform(self.resolve(db)).resolve(db) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLAssociation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLAssociation.swift deleted file mode 100755 index a918fce..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLAssociation.swift +++ /dev/null @@ -1,353 +0,0 @@ -// MARK: - SQLAssociation - -/// An SQL association is a non-empty chain of steps which starts at the -/// "pivot" and ends on the "destination": -/// -/// // SELECT origin.*, destination.* -/// // FROM origin -/// // JOIN pivot ON ... -/// // JOIN ... -/// // JOIN ... -/// // JOIN destination ON ... -/// Origin.including(required: association) -/// -/// For direct associations such as BelongTo or HasMany, the chain contains a -/// single element, the "destination", without intermediate step: -/// -/// // "Origin" belongsTo "destination": -/// // SELECT origin.*, destination.* -/// // FROM origin -/// // JOIN destination ON destination.originId = origin.id -/// let association = Origin.belongsTo(Destination.self) -/// Origin.including(required: association) -/// -/// Indirect associations such as HasManyThrough have one or several -/// intermediate steps: -/// -/// // "Origin" has many "destination" through "pivot": -/// // SELECT origin.*, destination.* -/// // FROM origin -/// // JOIN pivot ON pivot.originId = origin.id -/// // JOIN destination ON destination.id = pivot.destinationId -/// let association = Origin.hasMany( -/// Destination.self, -/// through: Origin.hasMany(Pivot.self), -/// via: Pivot.belongsTo(Destination.self)) -/// Origin.including(required: association) -/// -/// // "Origin" has many "destination" through "pivot1" and "pivot2": -/// // SELECT origin.*, destination.* -/// // FROM origin -/// // JOIN pivot1 ON pivot1.originId = origin.id -/// // JOIN pivot2 ON pivot2.pivot1Id = pivot1.id -/// // JOIN destination ON destination.id = pivot.destinationId -/// let association = Origin.hasMany( -/// Destination.self, -/// through: Origin.hasMany(Pivot1.self), -/// via: Pivot1.hasMany( -/// Destination.self, -/// through: Pivot1.hasMany(Pivot2.self), -/// via: Pivot2.belongsTo(Destination.self))) -/// Origin.including(required: association) -/// -/// :nodoc: -public /* TODO: internal */ struct SQLAssociation { - // All steps, from pivot to destination. Never empty. - private(set) var steps: [SQLAssociationStep] - var keyPath: [String] { - return steps.map { $0.key.name(for: $0.cardinality) } - } - - var destination: SQLAssociationStep { - get { return steps[steps.count - 1] } - set { steps[steps.count - 1] = newValue } - } - - var pivot: SQLAssociationStep { - get { return steps[0] } - set { steps[0] = newValue } - } - - init(steps: [SQLAssociationStep]) { - assert(!steps.isEmpty) - self.steps = steps - } - - init( - key: SQLAssociationKey, - condition: SQLAssociationCondition, - relation: SQLRelation, - cardinality: SQLAssociationCardinality) - { - let step = SQLAssociationStep( - key: key, - condition: condition, - relation: relation, - cardinality: cardinality) - self.init(steps: [step]) - } - - /// Changes the destination key - func forDestinationKey(_ key: SQLAssociationKey) -> SQLAssociation { - var result = self - result.destination.key = key - return result - } - - /// Transforms the destination relation - func mapDestinationRelation(_ transform: (SQLRelation) -> SQLRelation) -> SQLAssociation { - var result = self - result.destination = result.destination.mapRelation(transform) - return result - } - - /// Transforms the pivot relation - func mapPivotRelation(_ transform: (SQLRelation) -> SQLRelation) -> SQLAssociation { - var result = self - result.pivot = result.pivot.mapRelation(transform) - return result - } - - /// Returns a new association - func through(_ other: SQLAssociation) -> SQLAssociation { - return SQLAssociation(steps: other.steps + steps) - } - - /// Given an origin alias and rows, returns the destination of the - /// association as a relation. - /// - /// This method provides support for association methods such - /// as `request(for:)`: - /// - /// struct Destination: TableRecord { } - /// struct Origin: TableRecord, EncodableRecord { - /// static let destinations = hasMany(Destination.self) - /// var destinations: QueryInterface { - /// return request(for: Origin.destinations) - /// } - /// } - /// - /// // SELECT destination.* - /// // FROM destination - /// // WHERE destination.originId = 1 - /// let origin = Origin(id: 1) - /// let destinations = origin.destinations.fetchAll(db) - /// - /// At low-level, this gives: - /// - /// let origin = Origin(id: 1) - /// let originAlias = TableAlias(tableName: Origin.databaseTableName) - /// let sqlAssociation = Origin.destination.sqlAssociation - /// let destinationRelation = sqlAssociation.destinationRelation( - /// from: originAlias, - /// rows: { db in try [Row(PersistenceContainer(db, origin))] }) - /// let query = SQLQuery(relation: destinationRelation) - /// let generator = SQLQueryGenerator(query) - /// let statement, _ = try generator.prepare(db) - /// print(statement.sql) - /// // SELECT destination.* - /// // FROM destination - /// // WHERE destination.originId = 1 - /// - /// This method works for simple direct associations such as BelongsTo or - /// HasMany in the above examples, but also for indirect associations such - /// as HasManyThrough, which have any number of pivot relations between the - /// origin and the destination. - func destinationRelation(fromOriginRows originRows: @escaping (Database) throws -> [Row]) -> SQLRelation { - // Filter the pivot - let pivot = self.pivot - let pivotAlias = TableAlias() - let filteredPivotRelation = pivot.relation - .qualified(with: pivotAlias) - .filter({ db in - // `pivot.originId = 123` or `pivot.originId IN (1, 2, 3)` - try pivot.condition.filteringExpression(db, leftRows: originRows(db), rightAlias: pivotAlias) - }) - - if steps.count == 1 { - // This is a direct join from origin to destination, without - // intermediate step. - // - // SELECT destination.* - // FROM destination - // WHERE destination.originId = 1 - // - // let association = Origin.hasMany(Destination.self) - // Origin(id: 1).request(for: association) - return filteredPivotRelation - } - - // This is an indirect join from origin to destination, through - // some intermediate steps: - // - // SELECT destination.* - // FROM destination - // JOIN pivot ON (pivot.destinationId = destination.id) AND (pivot.originId = 1) - // - // let association = Origin.hasMany( - // Destination.self, - // through: Origin.hasMany(Pivot.self), - // via: Pivot.belongsTo(Destination.self)) - // Origin(id: 1).request(for: association) - let reversedSteps = zip(steps, steps.dropFirst()) - .map { (step, nextStep) -> SQLAssociationStep in - // Intermediate steps are not included in the selection, and - // don't have any child. - let relation = step.relation.select([]).deletingChildren() - return SQLAssociationStep( - key: step.key, - condition: nextStep.condition.reversed, - relation: relation, - cardinality: step.cardinality) - } - .reversed() - - var reversedAssociation = SQLAssociation(steps: Array(reversedSteps)) - // Replace pivot with the filtered one (not included in the selection, - // without children). - reversedAssociation = reversedAssociation.mapDestinationRelation { _ in - filteredPivotRelation.select([]).deletingChildren() - } - return destination.relation.appending(reversedAssociation, kind: .oneRequired) - } -} - -struct SQLAssociationStep { - var key: SQLAssociationKey - var condition: SQLAssociationCondition - var relation: SQLRelation - var cardinality: SQLAssociationCardinality - - func mapRelation(_ transform: (SQLRelation) -> SQLRelation) -> SQLAssociationStep { - return SQLAssociationStep( - key: key, - condition: condition, - relation: transform(relation), - cardinality: cardinality) - } -} - -enum SQLAssociationCardinality { - case toOne - case toMany -} - -// MARK: - SQLAssociationKey - -/// Associations are meant to be consumed, most often into Decodable records. -/// -/// Those records have singular or plural property names, and we want -/// associations to be able to fill those singular or plural names -/// automatically, so that the user does not have to perform explicit -/// decoding configuration. -/// -/// Those plural or singular names are not decided when the association is -/// defined. For example, the Author.books association, which looks plural, may -/// actually generate "book" or "books" depending on the context: -/// -/// struct Author: TableRecord { -/// static let books = hasMany(Book.self) -/// } -/// struct Book: TableRecord { -/// } -/// -/// // "books" -/// struct AuthorInfo: FetchableRecord, Decodable { -/// var author: Author -/// var books: [Book] -/// } -/// let request = Author.including(all: Author.books) -/// let authorInfos = try AuthorInfo.fetchAll(db, request) -/// -/// "book" -/// struct AuthorInfo: FetchableRecord, Decodable { -/// var author: Author -/// var book: Book -/// } -/// let request = Author.including(required: Author.books) -/// let authorInfos = try AuthorInfo.fetchAll(db, request) -/// -/// "bookCount" -/// struct AuthorInfo: FetchableRecord, Decodable { -/// var author: Author -/// var bookCount: Int -/// } -/// let request = Author.annotated(with: Author.books.count) -/// let authorInfos = try AuthorInfo.fetchAll(db, request) -/// -/// The SQLAssociationKey type aims at providing the necessary support for -/// those various inflections. -enum SQLAssociationKey { - /// A key that is inflected in singular and plural contexts. - /// - /// For example: - /// - /// struct Author: TableRecord { - /// static let databaseTableName = "authors" - /// } - /// struct Book: TableRecord { - /// let author = belongsTo(Author.self) - /// } - /// - /// let request = Book.including(required: Book.author) - /// let row = try Row.fetchOne(db, request)! - /// row.scopes["author"] // singularized "authors" table name - case inflected(String) - - /// A key that is inflected in plural contexts, but stricly honors - /// user-provided name in singular contexts. - /// - /// For example: - /// - /// struct Country: TableRecord { - /// let demographics = hasOne(Demographics.self, key: "demographics") - /// } - /// - /// let request = Country.including(required: Country.demographics) - /// let row = try Row.fetchOne(db, request)! - /// row.scopes["demographics"] // not singularized - case fixedSingular(String) - - /// A key that is inflected in singular contexts, but stricly honors - /// user-provided name in plural contexts. - /// See .inflected and .fixedSingular for some context. - case fixedPlural(String) - - /// A key that is never inflected. - case fixed(String) - - func name(for cardinality: SQLAssociationCardinality) -> String { - switch cardinality { - case .toOne: - return singularizedName - case .toMany: - return pluralizedName - } - } - - var pluralizedName: String { - switch self { - case .inflected(let name): - return name.pluralized - case .fixedSingular(let name): - return name.pluralized - case .fixedPlural(let name): - return name - case .fixed(let name): - return name - } - } - - var singularizedName: String { - switch self { - case .inflected(let name): - return name.singularized - case .fixedSingular(let name): - return name - case .fixedPlural(let name): - return name.singularized - case .fixed(let name): - return name - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLCollatedExpression.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLCollatedExpression.swift deleted file mode 100755 index 29582b5..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLCollatedExpression.swift +++ /dev/null @@ -1,73 +0,0 @@ -/// SQLCollatedExpression taints an expression so that every derived expression -/// is eventually evaluated using an SQLite collation. -/// -/// You create one by calling the SQLSpecificExpressible.collating() method. -/// -/// let email: SQLCollatedExpression = Column("email").collating(.nocase) -/// -/// // SELECT * FROM player WHERE email = 'arthur@example.com' COLLATE NOCASE -/// Player.filter(email == "arthur@example.com") -/// -/// :nodoc: -public struct SQLCollatedExpression { - /// The tainted expression - public let expression: SQLExpression - - /// The name of the collation - public let collationName: Database.CollationName - - /// Returns an ordering suitable for QueryInterfaceRequest.order() - /// - /// let email: SQLCollatedExpression = Column("email").collating(.nocase) - /// - /// // SELECT * FROM player ORDER BY email COLLATE NOCASE ASC - /// Player.order(email.asc) - /// - /// See https://github.com/groue/GRDB.swift/#the-query-interface - public var asc: SQLOrderingTerm { - return SQLOrdering.asc(sqlExpression) - } - - /// Returns an ordering suitable for QueryInterfaceRequest.order() - /// - /// let email: SQLCollatedExpression = Column("email").collating(.nocase) - /// - /// // SELECT * FROM player ORDER BY email COLLATE NOCASE DESC - /// Player.order(email.desc) - /// - /// See https://github.com/groue/GRDB.swift/#the-query-interface - public var desc: SQLOrderingTerm { - return SQLOrdering.desc(sqlExpression) - } - - init(_ expression: SQLExpression, collationName: Database.CollationName) { - self.expression = expression - self.collationName = collationName - } - - var sqlExpression: SQLExpression { - return SQLExpressionCollate(expression, collationName: collationName) - } -} - -/// :nodoc: -extension SQLCollatedExpression : SQLOrderingTerm { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var reversed: SQLOrderingTerm { - return desc - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func orderingTermSQL(_ context: inout SQLGenerationContext) -> String { - return sqlExpression.orderingTermSQL(&context) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedOrdering(with alias: TableAlias) -> SQLOrderingTerm { - return SQLCollatedExpression(expression.qualifiedExpression(with: alias), collationName: collationName) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLCollection.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLCollection.swift deleted file mode 100755 index 8bc5f68..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLCollection.swift +++ /dev/null @@ -1,63 +0,0 @@ -// MARK: - SQLCollection - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLCollection is the protocol for types that can be checked for inclusion. -/// -/// :nodoc: -public protocol SQLCollection { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns an SQL string that represents the collection. - func collectionSQL(_ context: inout SQLGenerationContext) -> String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns an expression that check whether the collection contains - /// the expression. - func contains(_ value: SQLExpressible) -> SQLExpression -} - - -// MARK: Default Implementations - -/// :nodoc: -extension SQLCollection { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns a SQLExpressionContains which applies the `IN` SQL operator. - public func contains(_ value: SQLExpressible) -> SQLExpression { - return SQLExpressionContains(value, self) - } -} - - -// MARK: - SQLExpressionsArray - -/// SQLExpressionsArray wraps an array of expressions -/// -/// SQLExpressionsArray([1, 2, 3]) -struct SQLExpressionsArray : SQLCollection { - let expressions: [SQLExpression] - - init(_ expressions: S) where S.Iterator.Element : SQLExpressible { - self.expressions = expressions.map { $0.sqlExpression } - } - - func collectionSQL(_ context: inout SQLGenerationContext) -> String { - return (expressions.map { $0.expressionSQL(&context) } as [String]).joined(separator: ", ") - } - - func contains(_ value: SQLExpressible) -> SQLExpression { - guard let expression = expressions.first else { - // [].contains(Column("name")) => 0 - return false.databaseValue - } - if expressions.count == 1 { - // ["foo"].contains(Column("name")) => name = 'foo' - return value == expression - } - // ["foo", "bar"].contains(Column("name")) => name IN ('foo', 'bar') - return SQLExpressionContains(value, self) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpressible.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpressible.swift deleted file mode 100755 index f907786..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpressible.swift +++ /dev/null @@ -1,99 +0,0 @@ -// MARK: - SQLExpressible - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all types that can be turned into an SQL expression. -/// -/// It is adopted by protocols like DatabaseValueConvertible, and types -/// like Column. -/// -/// See https://github.com/groue/GRDB.swift/#the-query-interface -/// -/// :nodoc: -public protocol SQLExpressible { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns an SQLExpression - var sqlExpression: SQLExpression { get } -} - -// MARK: - SQLSpecificExpressible - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLSpecificExpressible is a protocol for all database-specific types that can -/// be turned into an SQL expression. Types whose existence is not purely -/// dedicated to the database should adopt the SQLExpressible protocol instead. -/// -/// For example, Column is a type that only exists to help you build requests, -/// and it adopts SQLSpecificExpressible. -/// -/// On the other side, Int adopts SQLExpressible (via DatabaseValueConvertible). -/// -/// :nodoc: -public protocol SQLSpecificExpressible : SQLExpressible { - // SQLExpressible can be adopted by Swift standard types, and user - // types, through the DatabaseValueConvertible protocol which inherits - // from SQLExpressible. - // - // For example, Int adopts SQLExpressible through - // DatabaseValueConvertible. - // - // SQLSpecificExpressible, on the other side, is not adopted by any - // Swift standard type or any user type. It is only adopted by GRDB types, - // such as Column and SQLExpression. - // - // This separation lets us define functions and operators that do not - // spill out. The three declarations below have no chance overloading a - // Swift-defined operator, or a user-defined operator: - // - // - ==(SQLExpressible, SQLSpecificExpressible) - // - ==(SQLSpecificExpressible, SQLExpressible) - // - ==(SQLSpecificExpressible, SQLSpecificExpressible) -} - -// MARK: - SQLExpressible & SQLOrderingTerm - -extension SQLExpressible where Self: SQLOrderingTerm { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var reversed: SQLOrderingTerm { - return SQLOrdering.desc(sqlExpression) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func orderingTermSQL(_ context: inout SQLGenerationContext) -> String { - return sqlExpression.expressionSQL(&context) - } -} - -// MARK: - SQLExpressible & SQLSelectable - -extension SQLExpressible where Self: SQLSelectable { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func resultColumnSQL(_ context: inout SQLGenerationContext) -> String { - return sqlExpression.expressionSQL(&context) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func countedSQL(_ context: inout SQLGenerationContext) -> String { - return sqlExpression.expressionSQL(&context) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func count(distinct: Bool) -> SQLCount? { - return sqlExpression.count(distinct: distinct) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func columnCount(_ db: Database) throws -> Int { - return 1 - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpression+QueryInterface.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpression+QueryInterface.swift deleted file mode 100755 index 3d1c0de..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpression+QueryInterface.swift +++ /dev/null @@ -1,730 +0,0 @@ -// MARK: - SQLExpression - -extension SQLExpression { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Converts an expression to an SQLLiteral - /// - /// :nodoc: - public var sqlLiteral: SQLLiteral { - var context = SQLGenerationContext.literalGenerationContext(withArguments: true) - let sql = expressionSQL(&context) - return SQLLiteral(sql: sql, arguments: context.arguments!) - } - - /// The expression as a quoted SQL literal (not public in order to avoid abuses) - /// - /// "foo'bar".databaseValue.quotedSQL() // "'foo''bar'"" - func quotedSQL() -> String { - var context = SQLGenerationContext.literalGenerationContext(withArguments: false) - return expressionSQL(&context) - } -} - -// MARK: - SQLExpressionLiteral - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLExpressionLiteral is an expression built from a raw SQL snippet. -/// -/// SQLExpressionLiteral(sql: "1 + 2") -/// -/// The SQL literal may contain `?` and colon-prefixed arguments: -/// -/// SQLExpressionLiteral(sql: "? + ?", arguments: [1, 2]) -/// SQLExpressionLiteral(sql: ":one + :two", arguments: ["one": 1, "two": 2]) -public struct SQLExpressionLiteral : SQLExpression { - private let sqlLiteral: SQLLiteral - - public var sql: String { return sqlLiteral.sql } - public var arguments: StatementArguments { return sqlLiteral.arguments } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an SQL literal expression. - /// - /// SQLExpressionLiteral(sql: "1 + 2") - /// SQLExpressionLiteral(sql: "? + ?", arguments: [1, 2]) - /// SQLExpressionLiteral(sql: ":one + :two", arguments: ["one": 1, "two": 2]) - public init(sql: String, arguments: StatementArguments = StatementArguments()) { - self.init(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an SQL literal expression. - /// - /// SQLExpressionLiteral(literal: SQLLiteral(sql: "1 + 2") - /// SQLExpressionLiteral(literal: SQLLiteral(sql: "? + ?", arguments: [1, 2])) - /// SQLExpressionLiteral(literal: SQLLiteral(sql: ":one + :two", arguments: ["one": 1, "two": 2])) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// SQLExpressionLiteral(literal: "\(1) + \(2)") - public init(literal sqlLiteral: SQLLiteral) { - self.init(unsafeLiteral: sqlLiteral.mapSQL { "(\($0))" }) - } - - /// Creates an SQL literal expression without wrapping the SQL literal - /// inside parentheses. It is unsafe because the result expression can not - /// be safely composed with other expressions. - init(unsafeLiteral sqlLiteral: SQLLiteral) { - self.sqlLiteral = sqlLiteral - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func expressionSQL(_ context: inout SQLGenerationContext) -> String { - if context.append(arguments: sqlLiteral.arguments) == false { - // GRDB limitation: we don't know how to look for `?` in sql and - // replace them with with literals. - fatalError("Not implemented") - } - return sqlLiteral.sql - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return self - } -} - -// MARK: - SQLExpressionUnary - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLUnaryOperator is a SQLite unary operator. -/// -/// :nodoc: -public struct SQLUnaryOperator : Hashable { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The SQL operator - public let sql: String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// If true GRDB puts a white space between the operator and the operand. - public let needsRightSpace: Bool - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an unary operator - /// - /// SQLUnaryOperator("~", needsRightSpace: false) - public init(_ sql: String, needsRightSpace: Bool) { - self.sql = sql - self.needsRightSpace = needsRightSpace - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLExpressionUnary is an expression made of an unary operator and -/// an operand expression. -/// -/// :nodoc: -public struct SQLExpressionUnary : SQLExpression { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The unary operator - public let op: SQLUnaryOperator - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The operand - public let expression: SQLExpression - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an expression made of an unary operator and - /// an operand expression. - /// - /// // NOT favorite - /// SQLExpressionUnary(.not, Column("favorite")) - public init(_ op: SQLUnaryOperator, _ value: SQLExpressible) { - self.op = op - self.expression = value.sqlExpression - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return op.sql + (op.needsRightSpace ? " " : "") + expression.expressionSQL(&context) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionUnary(op, expression.qualifiedExpression(with: alias)) - } -} - -// MARK: - SQLExpressionBinary - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLBinaryOperator is a SQLite binary operator. -/// -/// :nodoc: -public struct SQLBinaryOperator : Hashable { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The SQL operator - public let sql: String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The SQL for the negated operator, if any - public let negatedSQL: String? - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates a binary operator - /// - /// SQLBinaryOperator("+") - /// SQLBinaryOperator("IS", negated: "IS NOT") - public init(_ sql: String, negated: String? = nil) { - self.sql = sql - self.negatedSQL = negated - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns the negated binary operator, if any - /// - /// let operator = SQLBinaryOperator("IS", negated: "IS NOT") - /// operator.negated!.sql // IS NOT - public var negated: SQLBinaryOperator? { - guard let negatedSQL = negatedSQL else { - return nil - } - return SQLBinaryOperator(negatedSQL, negated: sql) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLExpressionBinary is an expression made of two expressions joined with a -/// binary operator. -/// -/// SQLExpressionBinary(.multiply, Column("length"), Column("width")) -/// -/// :nodoc: -public struct SQLExpressionBinary : SQLExpression { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The left operand - public let lhs: SQLExpression - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The operator - public let op: SQLBinaryOperator - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The right operand - public let rhs: SQLExpression - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an expression made of two expressions joined with a - /// binary operator. - /// - /// // length * width - /// SQLExpressionBinary(.multiply, Column("length"), Column("width")) - public init(_ op: SQLBinaryOperator, _ lhs: SQLExpressible, _ rhs: SQLExpressible) { - self.lhs = lhs.sqlExpression - self.op = op - self.rhs = rhs.sqlExpression - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "(" + lhs.expressionSQL(&context) + " " + op.sql + " " + rhs.expressionSQL(&context) + ")" - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var negated: SQLExpression { - if let negatedOp = op.negated { - return SQLExpressionBinary(negatedOp, lhs, rhs) - } else { - return SQLExpressionNot(self) - } - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionBinary(op, lhs.qualifiedExpression(with: alias), rhs.qualifiedExpression(with: alias)) - } -} - -// MARK: - SQLExpressionAnd - -struct SQLExpressionAnd : SQLExpression { - let expressions: [SQLExpression] - - init(_ expressions: [SQLExpression]) { - self.expressions = expressions - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - guard let first = expressions.first else { - // Ruby [].all? # => true - return true.sqlExpression.expressionSQL(&context) - } - if expressions.count == 1 { - return first.expressionSQL(&context) - } - let expressionSQLs = expressions.map { $0.expressionSQL(&context) } - return "(" + expressionSQLs.joined(separator: " AND ") + ")" - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionAnd(expressions.map { $0.qualifiedExpression(with: alias) }) - } - - func matchedRowIds(rowIdName: String?) -> Set? { - let matchedRowIds = expressions.compactMap { - $0.matchedRowIds(rowIdName: rowIdName) - } - guard let first = matchedRowIds.first else { - return nil - } - return matchedRowIds.suffix(from: 1).reduce(into: first) { $0.formIntersection($1) } - } -} - -// MARK: - SQLExpressionOr - -struct SQLExpressionOr : SQLExpression { - let expressions: [SQLExpression] - - init(_ expressions: [SQLExpression]) { - self.expressions = expressions - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - guard let first = expressions.first else { - // Ruby [].any? # => false - return false.sqlExpression.expressionSQL(&context) - } - if expressions.count == 1 { - return first.expressionSQL(&context) - } - let expressionSQLs = expressions.map { $0.expressionSQL(&context) } - return "(" + expressionSQLs.joined(separator: " OR ") + ")" - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionOr(expressions.map { $0.qualifiedExpression(with: alias) }) - } - - func matchedRowIds(rowIdName: String?) -> Set? { - if expressions.isEmpty { - return [] - } - var result: Set = [] - for expr in expressions { - guard let matchedRowIds = expr.matchedRowIds(rowIdName: rowIdName) else { - return nil - } - result.formUnion(matchedRowIds) - } - return result - } -} - -// MARK: - SQLExpressionEqual - -struct SQLExpressionEqual: SQLExpression { - var lhs: SQLExpression - var rhs: SQLExpression - var op: Operator - - init(_ op: Operator, _ lhs: SQLExpression, _ rhs: SQLExpression) { - self.lhs = lhs - self.rhs = rhs - self.op = op - } - - enum Operator: String { - case equal = "=" - case notEqual = "<>" - case `is` = "IS" - case isNot = "IS NOT" - - var negated: Operator { - switch self { - case .equal: return .notEqual - case .notEqual: return .equal - case .is: return .isNot - case .isNot: return .is - } - } - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "(" + - lhs.expressionSQL(&context) + - " " + - op.rawValue + - " " + - rhs.expressionSQL(&context) + - ")" - } - - var negated: SQLExpression { - return SQLExpressionEqual(op.negated, lhs, rhs) - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionEqual(op, lhs.qualifiedExpression(with: alias), rhs.qualifiedExpression(with: alias)) - } - - func matchedRowIds(rowIdName: String?) -> Set? { - // FIXME: this implementation ignores column aliases - switch op { - case .equal, .is: - // Look for `id ==/IS 1`, `rowid ==/IS 1`, `1 ==/IS id`, `1 ==/IS rowid` - func matchedRowIds(column: ColumnExpression, dbValue: DatabaseValue) -> Set? { - var rowIdNames = [Column.rowID.name.lowercased()] - if let rowIdName = rowIdName { - rowIdNames.append(rowIdName.lowercased()) - } - guard rowIdNames.contains(column.name.lowercased()) else { - return nil - } - if let rowId = Int64.fromDatabaseValue(dbValue) { - return [rowId] - } else { - return [] - } - } - switch (lhs, rhs) { - case (let column as ColumnExpression, let dbValue as DatabaseValue): - return matchedRowIds(column: column, dbValue: dbValue) - case (let dbValue as DatabaseValue, let column as ColumnExpression): - return matchedRowIds(column: column, dbValue: dbValue) - default: - return nil - } - - case .notEqual, .isNot: - return nil - } - } -} - -// MARK: - SQLExpressionContains - -/// SQLExpressionContains is an expression that checks the inclusion of a -/// value in a collection with the `IN` operator. -/// -/// // id IN (1,2,3) -/// SQLExpressionContains(Column("id"), SQLExpressionsArray([1,2,3])) -struct SQLExpressionContains : SQLExpression { - let expression: SQLExpression - let collection: SQLCollection - let isNegated: Bool - - init(_ value: SQLExpressible, _ collection: SQLCollection, negated: Bool = false) { - self.expression = value.sqlExpression - self.collection = collection - self.isNegated = negated - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "(" + - expression.expressionSQL(&context) + - (isNegated ? " NOT IN (" : " IN (") + - collection.collectionSQL(&context) + - "))" - } - - var negated: SQLExpression { - return SQLExpressionContains(expression, collection, negated: !isNegated) - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionContains(expression.qualifiedExpression(with: alias), collection, negated: isNegated) - } - - func matchedRowIds(rowIdName: String?) -> Set? { - // FIXME: this implementation ignores column aliases - // Look for `id IN (1, 2, 3)` - guard let column = expression as? ColumnExpression, - let array = collection as? SQLExpressionsArray else - { - return nil - } - - var rowIdNames = [Column.rowID.name.lowercased()] - if let rowIdName = rowIdName { - rowIdNames.append(rowIdName.lowercased()) - } - - guard rowIdNames.contains(column.name.lowercased()) else { - return nil - } - - var rowIDs: Set = [] - for expression in array.expressions { - guard let dbValue = expression as? DatabaseValue else { return nil } - if let rowId = Int64.fromDatabaseValue(dbValue) { - rowIDs.insert(rowId) - } - } - return rowIDs - } -} - -// MARK: - SQLExpressionBetween - -/// SQLExpressionBetween is an expression that checks if a values is included -/// in a range with the `BETWEEN` operator. -/// -/// // id BETWEEN 1 AND 3 -/// SQLExpressionBetween(Column("id"), 1.databaseValue, 3.databaseValue) -struct SQLExpressionBetween : SQLExpression { - let expression: SQLExpression - let lowerBound: SQLExpression - let upperBound: SQLExpression - let isNegated: Bool - - init(_ expression: SQLExpression, _ lowerBound: SQLExpression, _ upperBound: SQLExpression, negated: Bool = false) { - self.expression = expression - self.lowerBound = lowerBound - self.upperBound = upperBound - self.isNegated = negated - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "(" + - expression.expressionSQL(&context) + - (isNegated ? " NOT BETWEEN " : " BETWEEN ") + - lowerBound.expressionSQL(&context) + - " AND " + - upperBound.expressionSQL(&context) + - ")" - } - - var negated: SQLExpression { - return SQLExpressionBetween(expression, lowerBound, upperBound, negated: !isNegated) - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionBetween( - expression.qualifiedExpression(with: alias), - lowerBound.qualifiedExpression(with: alias), - upperBound.qualifiedExpression(with: alias), - negated: isNegated) - } -} - -// MARK: - SQLExpressionFunction - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLFunctionName is an SQL function name. -public struct SQLFunctionName : Hashable { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The SQL function name - /// - /// :nodoc: - public var sql: String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates a function name - /// - /// SQLFunctionName("ABS") - /// - /// :nodoc: - public init(_ sql: String) { - self.sql = sql - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLExpressionFunction is an SQL function call. -/// -/// // ABS(-1) -/// SQLExpressionFunction(.abs, [-1.databaseValue]) -/// -/// :nodoc: -public struct SQLExpressionFunction : SQLExpression { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The function name - public let functionName: SQLFunctionName - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The function arguments - public let arguments: [SQLExpression] - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an SQL function call - /// - /// // ABS(-1) - /// SQLExpressionFunction(.abs, arguments: [-1.databaseValue]) - public init(_ functionName: SQLFunctionName, arguments: [SQLExpression]) { - self.functionName = functionName - self.arguments = arguments - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Creates an SQL function call - /// - /// // ABS(-1) - /// SQLExpressionFunction(.abs, arguments: -1) - public init(_ functionName: SQLFunctionName, arguments: SQLExpressible...) { - self.init(functionName, arguments: arguments.map { $0.sqlExpression }) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return functionName.sql + "(" + (self.arguments.map { $0.expressionSQL(&context) } as [String]).joined(separator: ", ") + ")" - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionFunction(functionName, arguments: arguments.map { $0.qualifiedExpression(with: alias) }) - } -} - -// MARK: - SQLExpressionCount - -/// SQLExpressionCount is a call to the SQL `COUNT` function. -/// -/// // COUNT(name) -/// SQLExpressionCount(Column("name")) -struct SQLExpressionCount : SQLExpression { - /// The counted value - let counted: SQLSelectable - - init(_ counted: SQLSelectable) { - self.counted = counted - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "COUNT(" + counted.countedSQL(&context) + ")" - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionCount(counted.qualifiedSelectable(with: alias)) - } -} - -// MARK: - SQLExpressionCountDistinct - -/// SQLExpressionCountDistinct is a call to the SQL `COUNT(DISTINCT ...)` function. -/// -/// // COUNT(DISTINCT name) -/// SQLExpressionCountDistinct(Column("name")) -struct SQLExpressionCountDistinct : SQLExpression { - let counted: SQLExpression - - init(_ counted: SQLExpression) { - self.counted = counted - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "COUNT(DISTINCT " + counted.expressionSQL(&context) + ")" - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionCountDistinct(counted.qualifiedExpression(with: alias)) - } -} - -// MARK: - SQLExpressionIsEmpty - -/// This one helps generating `COUNT(...) = 0` or `COUNT(...) > 0` while letting -/// the user using the not `!` logical operator, or comparisons with booleans -/// such as `== true` or `== false`. -struct SQLExpressionIsEmpty : SQLExpression { - var countExpression: SQLExpression - var isEmpty: Bool - - // countExpression should be a counting expression - init(_ countExpression: SQLExpression, isEmpty: Bool = true) { - self.countExpression = countExpression - self.isEmpty = isEmpty - } - - var negated: SQLExpression { - return SQLExpressionIsEmpty(countExpression, isEmpty: !isEmpty) - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - if isEmpty { - return "(" + countExpression.expressionSQL(&context) + " = 0)" - } else { - return "(" + countExpression.expressionSQL(&context) + " > 0)" - } - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionIsEmpty(countExpression.qualifiedExpression(with: alias), isEmpty: isEmpty) - } -} - -// MARK: - TableMatchExpression - -struct TableMatchExpression: SQLExpression { - var alias: TableAlias - var pattern: SQLExpression - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "(" + context.resolvedName(for: alias).quotedDatabaseIdentifier + " MATCH " + pattern.expressionSQL(&context) + ")" - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return TableMatchExpression( - alias: self.alias, - pattern: pattern.qualifiedExpression(with: alias)) - } -} - -// MARK: - SQLExpressionCollate - -/// SQLExpressionCollate is an expression tainted by an SQLite collation. -/// -/// // email = 'arthur@example.com' COLLATE NOCASE -/// SQLExpressionCollate(Column("email") == "arthur@example.com", "NOCASE") -struct SQLExpressionCollate : SQLExpression { - let expression: SQLExpression - let collationName: Database.CollationName - - init(_ expression: SQLExpression, collationName: Database.CollationName) { - self.expression = expression - self.collationName = collationName - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - let sql = expression.expressionSQL(&context) - if sql.last! == ")" { - return String(sql.prefix(upTo: sql.index(sql.endIndex, offsetBy: -1))) + " COLLATE " + collationName.rawValue + ")" - } else { - return sql + " COLLATE " + collationName.rawValue - } - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionCollate(expression.qualifiedExpression(with: alias), collationName: collationName) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpression.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpression.swift deleted file mode 100755 index ff235ed..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLExpression.swift +++ /dev/null @@ -1,147 +0,0 @@ -// MARK: - SQLExpression - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLExpression is the protocol for types that represent an SQL expression, as -/// described at https://www.sqlite.org/lang_expr.html -/// -/// GRDB ships with a variety of types that already adopt this protocol, and -/// allow to represent many SQLite expressions: -/// -/// - Column -/// - DatabaseValue -/// - SQLExpressionLiteral -/// - SQLExpressionUnary -/// - SQLExpressionBinary -/// - SQLExpressionExists -/// - SQLExpressionFunction -/// - SQLExpressionCollate -/// -/// :nodoc: -public protocol SQLExpression : SQLSpecificExpressible, SQLSelectable, SQLOrderingTerm { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns an SQL string that represents the expression. - func expressionSQL(_ context: inout SQLGenerationContext) -> String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns the expression, negated. This property fuels the `!` operator. - /// - /// The default implementation returns the expression prefixed by `NOT`. - /// - /// let column = Column("favorite") - /// column.negated // NOT favorite - /// - /// Some expressions may provide a custom implementation that returns a - /// more natural SQL expression. - /// - /// let expression = [1,2,3].contains(Column("id")) // id IN (1,2,3) - /// expression.negated // id NOT IN (1,2,3) - var negated: SQLExpression { get } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns the rowIds matched by the expression. - func matchedRowIds(rowIdName: String?) -> Set? // FIXME: this method should take TableAlias in account - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func qualifiedExpression(with alias: TableAlias) -> SQLExpression -} - -extension SQLExpression { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The default implementation returns the expression prefixed by `NOT`. - /// - /// let column = Column("favorite") - /// column.negated // NOT favorite - /// - /// :nodoc: - public var negated: SQLExpression { - return SQLExpressionNot(self) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The default implementation returns nil - /// - /// :nodoc: - public func matchedRowIds(rowIdName: String?) -> Set? { - return nil - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The default implementation returns qualifiedExpression(with:) - /// - /// :nodoc: - public func qualifiedSelectable(with alias: TableAlias) -> SQLSelectable { - return qualifiedExpression(with: alias) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The default implementation returns qualifiedExpression(with:) - /// - /// :nodoc: - public func qualifiedOrdering(with alias: TableAlias) -> SQLOrderingTerm { - return qualifiedExpression(with: alias) - } -} - -// SQLExpression: SQLExpressible - -extension SQLExpression { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public var sqlExpression: SQLExpression { - return self - } -} - -// SQLExpression: SQLSelectable - -extension SQLExpression { - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func count(distinct: Bool) -> SQLCount? { - if distinct { - // SELECT DISTINCT expr FROM tableName ... - // -> - // SELECT COUNT(DISTINCT expr) FROM tableName ... - return .distinct(self) - } else { - // SELECT expr FROM tableName ... - // -> - // SELECT COUNT(*) FROM tableName ... - return .all - } - } -} - -// MARK: - SQLExpressionNot - -struct SQLExpressionNot : SQLExpression { - let expression: SQLExpression - - init(_ expression: SQLExpression) { - self.expression = expression - } - - func expressionSQL(_ context: inout SQLGenerationContext) -> String { - return "NOT \(expression.expressionSQL(&context))" - } - - var negated: SQLExpression { - return expression - } - - func qualifiedExpression(with alias: TableAlias) -> SQLExpression { - return SQLExpressionNot(expression.qualifiedExpression(with: alias)) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLForeignKeyRequest.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLForeignKeyRequest.swift deleted file mode 100755 index 8514256..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLForeignKeyRequest.swift +++ /dev/null @@ -1,80 +0,0 @@ -/// SQLForeignKeyRequest looks for the foreign keys associations need to -/// join tables. -/// -/// Mappings come from foreign keys, when they exist in the database schema. -/// -/// When the schema does not define any foreign key, we can still infer complete -/// mappings from partial information and primary keys. -struct SQLForeignKeyRequest: Equatable { - let originTable: String - let destinationTable: String - let originColumns: [String]? - let destinationColumns: [String]? - - init(originTable: String, destinationTable: String, foreignKey: ForeignKey?) { - self.originTable = originTable - self.destinationTable = destinationTable - - self.originColumns = foreignKey?.originColumns - self.destinationColumns = foreignKey?.destinationColumns - } - - /// The (origin, destination) column pairs that join a left table to a right table. - func fetchMapping(_ db: Database) throws -> [(origin: String, destination: String)] { - if let originColumns = originColumns, let destinationColumns = destinationColumns { - // Total information: no need to query the database schema. - GRDBPrecondition(originColumns.count == destinationColumns.count, "Number of columns don't match") - let mapping = zip(originColumns, destinationColumns).map { - (origin: $0, destination: $1) - } - return mapping - } - - // Incomplete information: let's look for schema foreign keys - let foreignKeys = try db.foreignKeys(on: originTable).filter { foreignKey in - if destinationTable.lowercased() != foreignKey.destinationTable.lowercased() { - return false - } - if let originColumns = originColumns { - let originColumns = Set(originColumns.lazy.map { $0.lowercased() }) - let foreignKeyColumns = Set(foreignKey.mapping.lazy.map { $0.origin.lowercased() }) - if originColumns != foreignKeyColumns { - return false - } - } - if let destinationColumns = destinationColumns { - // TODO: test - let destinationColumns = Set(destinationColumns.lazy.map { $0.lowercased() }) - let foreignKeyColumns = Set(foreignKey.mapping.lazy.map { $0.destination.lowercased() }) - if destinationColumns != foreignKeyColumns { - return false - } - } - return true - } - - // Matching foreign key(s) found - if let foreignKey = foreignKeys.first { - if foreignKeys.count == 1 { - // Non-ambiguous - return foreignKey.mapping - } else { - // Ambiguous: can't choose - fatalError("Ambiguous foreign key from \(originTable) to \(destinationTable)") - } - } - - // No matching foreign key found: use the destination primary key - if let originColumns = originColumns { - let destinationColumns = try db.primaryKey(destinationTable).columns - if (originColumns.count == destinationColumns.count) { - let mapping = zip(originColumns, destinationColumns).map { - (origin: $0, destination: $1) - } - return mapping - } - } - - fatalError("Could not infer foreign key from \(originTable) to \(destinationTable)") - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLFunctions.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLFunctions.swift deleted file mode 100755 index e61af72..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLFunctions.swift +++ /dev/null @@ -1,217 +0,0 @@ -// MARK: - Custom Functions - -extension DatabaseFunction { - // TODO: rename call (https://forums.swift.org/t/se-0253-callable-values-of-user-defined-nominal-types/24177) - /// Returns an SQL expression that applies the function. - /// - /// See https://github.com/groue/GRDB.swift/#sql-functions - public func apply(_ arguments: SQLExpressible...) -> SQLExpression { - return SQLExpressionFunction(SQLFunctionName(name), arguments: arguments.map { $0.sqlExpression }) - } -} - - -// MARK: - ABS(...) - -extension SQLFunctionName { - /// The `ABS` function name - public static let abs = SQLFunctionName("ABS") -} - -/// Returns an expression that evaluates the `ABS` SQL function. -/// -/// // ABS(amount) -/// abs(Column("amount")) -public func abs(_ value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionFunction(.abs, arguments: value) -} - - -// MARK: - AVG(...) - -extension SQLFunctionName { - /// The `AVG` function name - public static let avg = SQLFunctionName("AVG") -} - -/// Returns an expression that evaluates the `AVG` SQL function. -/// -/// // AVG(length) -/// average(Column("length")) -public func average(_ value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionFunction(.avg, arguments: value) -} - - -// MARK: - COUNT(...) - -/// Returns an expression that evaluates the `COUNT` SQL function. -/// -/// // COUNT(email) -/// count(Column("email")) -public func count(_ counted: SQLSelectable) -> SQLExpression { - return SQLExpressionCount(counted) -} - - -// MARK: - COUNT(DISTINCT ...) - -/// Returns an expression that evaluates the `COUNT(DISTINCT)` SQL function. -/// -/// // COUNT(DISTINCT email) -/// count(distinct: Column("email")) -public func count(distinct value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionCountDistinct(value.sqlExpression) -} - - -// MARK: - IFNULL(...) - -extension SQLFunctionName { - /// The `IFNULL` function name - public static let ifNull = SQLFunctionName("IFNULL") -} - -/// Returns an expression that evaluates the `IFNULL` SQL function. -/// -/// // IFNULL(name, 'Anonymous') -/// Column("name") ?? "Anonymous" -public func ?? (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionFunction(.ifNull, arguments: lhs, rhs) -} - - -// MARK: - LENGTH(...) - -extension SQLFunctionName { - /// The `LENGTH` function name - public static let length = SQLFunctionName("LENGTH") -} - -/// Returns an expression that evaluates the `LENGTH` SQL function. -/// -/// // LENGTH(name) -/// length(Column("name")) -public func length(_ value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionFunction(.length, arguments: value) -} - - -// MARK: - MAX(...) - -extension SQLFunctionName { - /// The `MAX` function name - public static let max = SQLFunctionName("MAX") -} - -/// Returns an expression that evaluates the `MAX` SQL function. -/// -/// // MAX(score) -/// max(Column("score")) -public func max(_ value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionFunction(.max, arguments: value) -} - - -// MARK: - MIN(...) - -extension SQLFunctionName { - /// The `MIN` function name - public static let min = SQLFunctionName("MIN") -} - -/// Returns an expression that evaluates the `MIN` SQL function. -/// -/// // MIN(score) -/// min(Column("score")) -public func min(_ value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionFunction(.min, arguments: value) -} - - -// MARK: - SUM(...) - -extension SQLFunctionName { - /// The `SUM` function name - public static let sum = SQLFunctionName("SUM") -} - -/// Returns an expression that evaluates the `SUM` SQL function. -/// -/// // SUM(amount) -/// sum(Column("amount")) -public func sum(_ value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionFunction(.sum, arguments: value) -} - - -// MARK: - Swift String functions - -/// :nodoc: -extension SQLSpecificExpressible { - /// Returns an SQL expression that applies the Swift's built-in - /// capitalized String property. It is NULL for non-String arguments. - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.capitalized) - /// let names = try String.fetchAll(dbQueue, request) // [String] - public var capitalized: SQLExpression { - return DatabaseFunction.capitalize.apply(sqlExpression) - } - - /// Returns an SQL expression that applies the Swift's built-in - /// lowercased String property. It is NULL for non-String arguments. - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.lowercased()) - /// let names = try String.fetchAll(dbQueue, request) // [String] - public var lowercased: SQLExpression { - return DatabaseFunction.lowercase.apply(sqlExpression) - } - - /// Returns an SQL expression that applies the Swift's built-in - /// uppercased String property. It is NULL for non-String arguments. - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.uppercased()) - /// let names = try String.fetchAll(dbQueue, request) // [String] - public var uppercased: SQLExpression { - return DatabaseFunction.uppercase.apply(sqlExpression) - } -} - -/// :nodoc: -extension SQLSpecificExpressible { - /// Returns an SQL expression that applies the Swift's built-in - /// localizedCapitalized String property. It is NULL for non-String arguments. - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.localizedCapitalized) - /// let names = try String.fetchAll(dbQueue, request) // [String] - @available(OSX 10.11, watchOS 3.0, *) - public var localizedCapitalized: SQLExpression { - return DatabaseFunction.localizedCapitalize.apply(sqlExpression) - } - - /// Returns an SQL expression that applies the Swift's built-in - /// localizedLowercased String property. It is NULL for non-String arguments. - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.localizedLowercased) - /// let names = try String.fetchAll(dbQueue, request) // [String] - @available(OSX 10.11, watchOS 3.0, *) - public var localizedLowercased: SQLExpression { - return DatabaseFunction.localizedLowercase.apply(sqlExpression) - } - - /// Returns an SQL expression that applies the Swift's built-in - /// localizedUppercased String property. It is NULL for non-String arguments. - /// - /// let nameColumn = Column("name") - /// let request = Player.select(nameColumn.localizedUppercased) - /// let names = try String.fetchAll(dbQueue, request) // [String] - @available(OSX 10.11, watchOS 3.0, *) - public var localizedUppercased: SQLExpression { - return DatabaseFunction.localizedUppercase.apply(sqlExpression) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLOperators.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLOperators.swift deleted file mode 100755 index 8ff7c99..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLOperators.swift +++ /dev/null @@ -1,810 +0,0 @@ -// MARK: - Egality and Identity Operators (=, <>, IS, IS NOT) - -// Outputs "x = y" or "x IS NULL" -private func isEqual(_ lhs: SQLExpression, _ rhs: SQLExpression) -> SQLExpression { - switch (lhs, rhs) { - case (let lhs, let rhs as DatabaseValue): - switch rhs.storage { - case .null: - return SQLExpressionEqual(.is, lhs, rhs) - default: - return SQLExpressionEqual(.equal, lhs, rhs) - } - case (let lhs as DatabaseValue, let rhs): - switch lhs.storage { - case .null: - return SQLExpressionEqual(.is, rhs, lhs) - default: - return SQLExpressionEqual(.equal, lhs, rhs) - } - default: - return SQLExpressionEqual(.equal, lhs, rhs) - } -} - -/// An SQL expression that compares two expressions with the `=` SQL operator. -/// -/// // name = 'Arthur' -/// Column("name") == "Arthur" -/// -/// When the right operand is nil, `IS NULL` is used instead. -/// -/// // name IS NULL -/// Column("name") == nil -public func == (lhs: SQLSpecificExpressible, rhs: SQLExpressible?) -> SQLExpression { - return isEqual(lhs.sqlExpression, rhs?.sqlExpression ?? DatabaseValue.null) -} - -/// An SQL expression that compares two expressions with the `=` SQL operator. -/// -/// // name = 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) == "Arthur" -/// -/// When the right operand is nil, `IS NULL` is used instead. -/// -/// // name IS NULL -/// Column("name").collating(.nocase) == nil -public func == (lhs: SQLCollatedExpression, rhs: SQLExpressible?) -> SQLExpression { - return SQLExpressionCollate(lhs.expression == rhs, collationName: lhs.collationName) -} - -/// An SQL expression that checks the boolean value of an expression. -/// -/// The comparison is done with the built-in boolean evaluation of SQLite: -/// -/// // validated -/// Column("validated") == true -/// -/// // NOT validated -/// Column("validated") == false -public func == (lhs: SQLSpecificExpressible, rhs: Bool) -> SQLExpression { - if rhs { - return lhs.sqlExpression - } else { - return lhs.sqlExpression.negated - } -} - -/// An SQL expression that compares two expressions with the `=` SQL operator. -/// -/// // 'Arthur' = name -/// "Arthur" == Column("name") -/// -/// When the left operand is nil, `IS NULL` is used instead. -/// -/// // name IS NULL -/// nil == Column("name") -public func == (lhs: SQLExpressible?, rhs: SQLSpecificExpressible) -> SQLExpression { - return isEqual(lhs?.sqlExpression ?? DatabaseValue.null, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `=` SQL operator. -/// -/// // 'Arthur' = name COLLATE NOCASE -/// "Arthur" == Column("name").collating(.nocase) -/// -/// When the left operand is nil, `IS NULL` is used instead. -/// -/// // name IS NULL -/// nil == Column("name").collating(.nocase) -public func == (lhs: SQLExpressible?, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs == rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that checks the boolean value of an expression. -/// -/// The comparison is done with the built-in boolean evaluation of SQLite: -/// -/// // validated -/// true == Column("validated") -/// -/// // NOT validated -/// false == Column("validated") -public func == (lhs: Bool, rhs: SQLSpecificExpressible) -> SQLExpression { - if lhs { - return rhs.sqlExpression - } else { - return rhs.sqlExpression.negated - } -} - -/// An SQL expression that compares two expressions with the `=` SQL operator. -/// -/// // email = login -/// Column("email") == Column("login") -public func == (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return isEqual(lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `<>` SQL operator. -/// -/// // name <> 'Arthur' -/// Column("name") != "Arthur" -/// -/// When the right operand is nil, `IS NOT NULL` is used instead. -/// -/// // name IS NOT NULL -/// Column("name") != nil -public func != (lhs: SQLSpecificExpressible, rhs: SQLExpressible?) -> SQLExpression { - return isEqual(lhs.sqlExpression, rhs?.sqlExpression ?? DatabaseValue.null).negated -} - -/// An SQL expression that compares two expressions with the `<>` SQL operator. -/// -/// // name <> 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) != "Arthur" -/// -/// When the right operand is nil, `IS NOT NULL` is used instead. -/// -/// // name IS NOT NULL -/// Column("name").collating(.nocase) != nil -public func != (lhs: SQLCollatedExpression, rhs: SQLExpressible?) -> SQLExpression { - return SQLExpressionCollate(lhs.expression != rhs, collationName: lhs.collationName) -} - -/// An SQL expression that checks the boolean value of an expression. -/// -/// The comparison is done with the built-in boolean evaluation of SQLite: -/// -/// // NOT validated -/// Column("validated") != true -/// -/// // validated -/// Column("validated") != false -public func != (lhs: SQLSpecificExpressible, rhs: Bool) -> SQLExpression { - if rhs { - return lhs.sqlExpression.negated - } else { - return lhs.sqlExpression - } -} - -/// An SQL expression that compares two expressions with the `<>` SQL operator. -/// -/// // 'Arthur' <> name -/// "Arthur" != Column("name") -/// -/// When the left operand is nil, `IS NOT NULL` is used instead. -/// -/// // name IS NOT NULL -/// nil != Column("name") -public func != (lhs: SQLExpressible?, rhs: SQLSpecificExpressible) -> SQLExpression { - return isEqual(lhs?.sqlExpression ?? DatabaseValue.null, rhs.sqlExpression).negated -} - -/// An SQL expression that compares two expressions with the `<>` SQL operator. -/// -/// // 'Arthur' <> name COLLATE NOCASE -/// "Arthur" != Column("name").collating(.nocase) -/// -/// When the left operand is nil, `IS NOT NULL` is used instead. -/// -/// // name IS NOT NULL -/// nil != Column("name").collating(.nocase) -public func != (lhs: SQLExpressible?, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs != rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that checks the boolean value of an expression. -/// -/// The comparison is done with the built-in boolean evaluation of SQLite: -/// -/// // NOT validated -/// true != Column("validated") -/// -/// // validated -/// false != Column("validated") -public func != (lhs: Bool, rhs: SQLSpecificExpressible) -> SQLExpression { - if lhs { - return rhs.sqlExpression.negated - } else { - return rhs.sqlExpression - } -} - -/// An SQL expression that compares two expressions with the `<>` SQL operator. -/// -/// // email <> login -/// Column("email") != Column("login") -public func != (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return isEqual(lhs.sqlExpression, rhs.sqlExpression).negated -} - -/// An SQL expression that compares two expressions with the `IS` SQL operator. -/// -/// // name IS 'Arthur' -/// Column("name") === "Arthur" -public func === (lhs: SQLSpecificExpressible, rhs: SQLExpressible?) -> SQLExpression { - return SQLExpressionEqual(.is, lhs.sqlExpression, rhs?.sqlExpression ?? DatabaseValue.null) -} - -/// An SQL expression that compares two expressions with the `IS` SQL operator. -/// -/// // name IS 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) === "Arthur" -public func === (lhs: SQLCollatedExpression, rhs: SQLExpressible?) -> SQLExpression { - return SQLExpressionCollate(lhs.expression === rhs, collationName: lhs.collationName) -} - -/// An SQL expression that compares two expressions with the `IS` SQL operator. -/// -/// // name IS 'Arthur' -/// "Arthur" === Column("name") -public func === (lhs: SQLExpressible?, rhs: SQLSpecificExpressible) -> SQLExpression { - if let lhs = lhs { - return SQLExpressionEqual(.is, lhs.sqlExpression, rhs.sqlExpression) - } else { - return SQLExpressionEqual(.is, rhs.sqlExpression, DatabaseValue.null) - } -} - -/// An SQL expression that compares two expressions with the `IS` SQL operator. -/// -/// // name IS 'Arthur' COLLATE NOCASE -/// "Arthur" === Column("name").collating(.nocase) -public func === (lhs: SQLExpressible?, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs === rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that compares two expressions with the `IS` SQL operator. -/// -/// // email IS login -/// Column("email") === Column("login") -public func === (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionEqual(.is, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `IS NOT` SQL operator. -/// -/// // name IS NOT 'Arthur' -/// Column("name") !== "Arthur" -public func !== (lhs: SQLSpecificExpressible, rhs: SQLExpressible?) -> SQLExpression { - return SQLExpressionEqual(.isNot, lhs.sqlExpression, rhs?.sqlExpression ?? DatabaseValue.null) -} - -/// An SQL expression that compares two expressions with the `IS NOT` SQL operator. -/// -/// // name IS NOT 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) !== "Arthur" -public func !== (lhs: SQLCollatedExpression, rhs: SQLExpressible?) -> SQLExpression { - return SQLExpressionCollate(lhs.expression !== rhs, collationName: lhs.collationName) -} - -/// An SQL expression that compares two expressions with the `IS NOT` SQL operator. -/// -/// // name IS NOT 'Arthur' -/// "Arthur" !== Column("name") -public func !== (lhs: SQLExpressible?, rhs: SQLSpecificExpressible) -> SQLExpression { - if let lhs = lhs { - return SQLExpressionEqual(.isNot, lhs.sqlExpression, rhs.sqlExpression) - } else { - return SQLExpressionEqual(.isNot, rhs.sqlExpression, DatabaseValue.null) - } -} - -/// An SQL expression that compares two expressions with the `IS NOT` SQL operator. -/// -/// // name IS NOT 'Arthur' COLLATE NOCASE -/// "Arthur" !== Column("name").collating(.nocase) -public func !== (lhs: SQLExpressible?, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs !== rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that compares two expressions with the `IS NOT` SQL operator. -/// -/// // email IS NOT login -/// Column("email") !== Column("login") -public func !== (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionEqual(.isNot, lhs.sqlExpression, rhs.sqlExpression) -} - - -// MARK: - Comparison Operators (<, >, <=, >=) - -extension SQLBinaryOperator { - /// The `<` binary operator - static let lessThan = SQLBinaryOperator("<") - - /// The `<=` binary operator - static let lessThanOrEqual = SQLBinaryOperator("<=") - - /// The `>` binary operator - static let greaterThan = SQLBinaryOperator(">") - - /// The `>=` binary operator - static let greaterThanOrEqual = SQLBinaryOperator(">=") -} - -/// An SQL expression that compares two expressions with the `<` SQL operator. -/// -/// // score < 18 -/// Column("score") < 18 -public func < (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.lessThan, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `<` SQL operator. -/// -/// // name < 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) < "Arthur" -public func < (lhs: SQLCollatedExpression, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionCollate(lhs.expression < rhs, collationName: lhs.collationName) -} - -/// An SQL expression that compares two expressions with the `<` SQL operator. -/// -/// // 18 < score -/// 18 < Column("score") -public func < (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.lessThan, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `<` SQL operator. -/// -/// // 'Arthur' < name COLLATE NOCASE -/// "Arthur" < Column("name").collating(.nocase) -public func < (lhs: SQLExpressible, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs < rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that compares two expressions with the `<` SQL operator. -/// -/// // width < height -/// Column("width") < Column("height") -public func < (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.lessThan, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `<=` SQL operator. -/// -/// // score <= 18 -/// Column("score") <= 18 -public func <= (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.lessThanOrEqual, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `<=` SQL operator. -/// -/// // name <= 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) <= "Arthur" -public func <= (lhs: SQLCollatedExpression, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionCollate(lhs.expression <= rhs, collationName: lhs.collationName) -} - -/// An SQL expression that compares two expressions with the `<=` SQL operator. -/// -/// // 18 <= score -/// 18 <= Column("score") -public func <= (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.lessThanOrEqual, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `<=` SQL operator. -/// -/// // 'Arthur' <= name COLLATE NOCASE -/// "Arthur" <= Column("name").collating(.nocase) -public func <= (lhs: SQLExpressible, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs <= rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that compares two expressions with the `<=` SQL operator. -/// -/// // width <= height -/// Column("width") <= Column("height") -public func <= (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.lessThanOrEqual, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `>` SQL operator. -/// -/// // score > 18 -/// Column("score") > 18 -public func > (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.greaterThan, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `>` SQL operator. -/// -/// // name > 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) > "Arthur" -public func > (lhs: SQLCollatedExpression, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionCollate(lhs.expression > rhs, collationName: lhs.collationName) -} - -/// An SQL expression that compares two expressions with the `>` SQL operator. -/// -/// // 18 > score -/// 18 > Column("score") -public func > (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.greaterThan, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `>` SQL operator. -/// -/// // 'Arthur' > name COLLATE NOCASE -/// "Arthur" > Column("name").collating(.nocase) -public func > (lhs: SQLExpressible, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs > rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that compares two expressions with the `>` SQL operator. -/// -/// // width > height -/// Column("width") > Column("height") -public func > (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.greaterThan, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `>=` SQL operator. -/// -/// // score >= 18 -/// Column("score") >= 18 -public func >= (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.greaterThanOrEqual, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `>=` SQL operator. -/// -/// // name >= 'Arthur' COLLATE NOCASE -/// Column("name").collating(.nocase) >= "Arthur" -public func >= (lhs: SQLCollatedExpression, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionCollate(lhs.expression >= rhs, collationName: lhs.collationName) -} - -/// An SQL expression that compares two expressions with the `>=` SQL operator. -/// -/// // 18 >= score -/// 18 >= Column("score") -public func >= (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.greaterThanOrEqual, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL expression that compares two expressions with the `>=` SQL operator. -/// -/// // 'Arthur' >= name COLLATE NOCASE -/// "Arthur" >= Column("name").collating(.nocase) -public func >= (lhs: SQLExpressible, rhs: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(lhs >= rhs.expression, collationName: rhs.collationName) -} - -/// An SQL expression that compares two expressions with the `>=` SQL operator. -/// -/// // width >= height -/// Column("width") >= Column("height") -public func >= (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.greaterThanOrEqual, lhs.sqlExpression, rhs.sqlExpression) -} - - -// MARK: - Inclusion Operators (BETWEEN, IN) - -extension Range where Bound: SQLExpressible { - /// An SQL expression that checks the inclusion of an expression in a range. - /// - /// // email >= 'A' AND email < 'B' - /// ("A"..<"B").contains(Column("email")) - public func contains(_ element: SQLSpecificExpressible) -> SQLExpression { - return (element >= lowerBound) && (element < upperBound) - } - - /// An SQL expression that checks the inclusion of an expression in a range. - /// - /// // email >= 'A' COLLATE NOCASE AND email < 'B' COLLATE NOCASE - /// ("A"..<"B").contains(Column("email").collating(.nocase)) - public func contains(_ element: SQLCollatedExpression) -> SQLExpression { - return (element >= lowerBound) && (element < upperBound) - } -} - -extension ClosedRange where Bound: SQLExpressible { - /// An SQL expression that checks the inclusion of an expression in a range. - /// - /// // email BETWEEN 'A' AND 'B' - /// ("A"..."B").contains(Column("email")) - public func contains(_ element: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBetween(element.sqlExpression, lowerBound.sqlExpression, upperBound.sqlExpression) - } - - /// An SQL expression that checks the inclusion of an expression in a range. - /// - /// // email BETWEEN 'A' AND 'B' COLLATE NOCASE - /// ("A"..."B").contains(Column("email").collating(.nocase)) - public func contains(_ element: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(contains(element.expression), collationName: element.collationName) - } -} - -extension CountableRange where Bound: SQLExpressible { - /// An SQL expression that checks the inclusion of an expression in a range. - /// - /// // id BETWEEN 1 AND 9 - /// (1..<10).contains(Column("id")) - public func contains(_ element: SQLSpecificExpressible) -> SQLExpression { - return (element >= lowerBound) && (element < upperBound) - } -} - -extension CountableClosedRange where Bound: SQLExpressible { - /// An SQL expression that checks the inclusion of an expression in a range. - /// - /// // id BETWEEN 1 AND 10 - /// (1...10).contains(Column("id")) - public func contains(_ element: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBetween(element.sqlExpression, lowerBound.sqlExpression, upperBound.sqlExpression) - } -} - -extension Sequence where Self.Iterator.Element: SQLExpressible { - /// An SQL expression that checks the inclusion of an expression in - /// a sequence. - /// - /// // id IN (1,2,3) - /// [1, 2, 3].contains(Column("id")) - public func contains(_ element: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionsArray(self).contains(element.sqlExpression) - } - - /// An SQL expression that checks the inclusion of an expression in - /// a sequence. - /// - /// // name IN ('A', 'B') COLLATE NOCASE - /// ["A", "B"].contains(Column("name").collating(.nocase)) - public func contains(_ element: SQLCollatedExpression) -> SQLExpression { - return SQLExpressionCollate(contains(element.expression), collationName: element.collationName) - } -} - - -// MARK: - Arithmetic Operators (+, -, *, /) - -extension SQLBinaryOperator { - /// The `+` binary operator - static let plus = SQLBinaryOperator("+") - - /// The `-` binary operator - static let minus = SQLBinaryOperator("-") - - /// The `*` binary operator - static let multiply = SQLBinaryOperator("*") - - /// The `/` binary operator - static let divide = SQLBinaryOperator("/") -} - -extension SQLUnaryOperator { - /// The `-` unary operator - static let minus = SQLUnaryOperator("-", needsRightSpace: false) -} - -/// An SQL arithmetic multiplication. -/// -/// // width * 2 -/// Column("width") * 2 -public func * (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.multiply, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic multiplication. -/// -/// // 2 * width -/// 2 * Column("width") -public func * (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.multiply, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic multiplication. -/// -/// // width * height -/// Column("width") * Column("height") -public func * (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.multiply, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic division. -/// -/// // width / 2 -/// Column("width") / 2 -public func / (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.divide, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic division. -/// -/// // 2 / width -/// 2 / Column("width") -public func / (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.divide, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic division. -/// -/// // width / height -/// Column("width") / Column("height") -public func / (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.divide, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic addition. -/// -/// // width + 2 -/// Column("width") + 2 -public func + (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.plus, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic addition. -/// -/// // 2 + width -/// 2 + Column("width") -public func + (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.plus, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic addition. -/// -/// // width + height -/// Column("width") + Column("height") -public func + (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.plus, lhs.sqlExpression, rhs.sqlExpression) -} - -/// A negated SQL arithmetic expression. -/// -/// // -width -/// -Column("width") -public prefix func - (value: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionUnary(.minus, value.sqlExpression) -} - -/// An SQL arithmetic substraction. -/// -/// // width - 2 -/// Column("width") - 2 -public func - (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.minus, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic substraction. -/// -/// // 2 - width -/// 2 - Column("width") -public func - (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.minus, lhs.sqlExpression, rhs.sqlExpression) -} - -/// An SQL arithmetic substraction. -/// -/// // width - height -/// Column("width") - Column("height") -public func - (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionBinary(.minus, lhs.sqlExpression, rhs.sqlExpression) -} - - -// MARK: - Logical Operators (AND, OR, NOT) - -/// A logical SQL expression with the `AND` SQL operator. -/// -/// // favorite AND 0 -/// Column("favorite") && false -public func && (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionAnd([lhs.sqlExpression, rhs.sqlExpression]) -} - -/// A logical SQL expression with the `AND` SQL operator. -/// -/// // 0 AND favorite -/// false && Column("favorite") -public func && (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionAnd([lhs.sqlExpression, rhs.sqlExpression]) -} - -/// A logical SQL expression with the `AND` SQL operator. -/// -/// // email IS NOT NULL AND favorite -/// Column("email") != nil && Column("favorite") -public func && (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionAnd([lhs.sqlExpression, rhs.sqlExpression]) -} - -/// A logical SQL expression with the `OR` SQL operator. -/// -/// // favorite OR 1 -/// Column("favorite") || true -public func || (lhs: SQLSpecificExpressible, rhs: SQLExpressible) -> SQLExpression { - return SQLExpressionOr([lhs.sqlExpression, rhs.sqlExpression]) -} - -/// A logical SQL expression with the `OR` SQL operator. -/// -/// // 0 OR favorite -/// true || Column("favorite") -public func || (lhs: SQLExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionOr([lhs.sqlExpression, rhs.sqlExpression]) -} - -/// A logical SQL expression with the `OR` SQL operator. -/// -/// // email IS NULL OR hidden -/// Column("email") == nil || Column("hidden") -public func || (lhs: SQLSpecificExpressible, rhs: SQLSpecificExpressible) -> SQLExpression { - return SQLExpressionOr([lhs.sqlExpression, rhs.sqlExpression]) -} - -/// A negated logical SQL expression with the `NOT` SQL operator. -/// -/// // NOT hidden -/// !Column("hidden") -/// -/// Some expressions may be negated with specific SQL operators: -/// -/// // id NOT BETWEEN 1 AND 10 -/// !((1...10).contains(Column("id"))) -public prefix func ! (value: SQLSpecificExpressible) -> SQLExpression { - return value.sqlExpression.negated -} - -public enum SQLLogicalBinaryOperator { - case and, or -} - -extension Sequence where Element == SQLExpression { - /// Returns an expression by joining all elements with an SQL - /// logical operator. - /// - /// For example: - /// - /// // SELECT * FROM player - /// // WHERE (registered - /// // AND (score >= 1000) - /// // AND (name IS NOT NULL)) - /// let conditions = [ - /// Column("registered"), - /// Column("score") >= 1000, - /// Column("name") != nil] - /// Player.filter(conditions.joined(operator: .and)) - /// - /// When the sequence is empty, `joined(operator: .and)` returns true, - /// and `joined(operator: .or)` returns false: - /// - /// // SELECT * FROM player WHERE 1 - /// Player.filter([].joined(operator: .and)) - /// - /// // SELECT * FROM player WHERE 0 - /// Player.filter([].joined(operator: .or)) - public func joined(operator: SQLLogicalBinaryOperator) -> SQLExpression { - let expressions = Array(self) - switch `operator` { - case .and: return SQLExpressionAnd(expressions) - case .or: return SQLExpressionOr(expressions) - } - } -} - - -// MARK: - Like Operator - -extension SQLBinaryOperator { - /// The `LIKE` binary operator - static let like = SQLBinaryOperator("LIKE") -} - -/// :nodoc: -extension SQLSpecificExpressible { - - /// An SQL expression with the `LIKE` SQL operator. - /// - /// // email LIKE '%@example.com" - /// Column("email").like("%@example.com") - public func like(_ pattern: SQLExpressible) -> SQLExpression { - return SQLExpressionBinary(.like, self, pattern) - } -} - - -// MARK: - Match Operator - -extension SQLBinaryOperator { - /// The `MATCH` binary operator - static let match = SQLBinaryOperator("MATCH") -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLOrdering.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLOrdering.swift deleted file mode 100755 index 51e944b..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLOrdering.swift +++ /dev/null @@ -1,56 +0,0 @@ -// MARK: - SQLOrderingTerm - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The protocol for all types that can be used as an SQL ordering term, as -/// described at https://www.sqlite.org/syntax/ordering-term.html -/// -/// :nodoc: -public protocol SQLOrderingTerm { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// The ordering term, reversed - var reversed: SQLOrderingTerm { get } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns an SQL string that represents the ordering term. - func orderingTermSQL(_ context: inout SQLGenerationContext) -> String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func qualifiedOrdering(with alias: TableAlias) -> SQLOrderingTerm -} - -// MARK: - SQLOrdering - -enum SQLOrdering : SQLOrderingTerm { - case asc(SQLExpression) - case desc(SQLExpression) - - var reversed: SQLOrderingTerm { - switch self { - case .asc(let expression): - return SQLOrdering.desc(expression) - case .desc(let expression): - return SQLOrdering.asc(expression) - } - } - - func orderingTermSQL(_ context: inout SQLGenerationContext) -> String { - switch self { - case .asc(let expression): - return expression.expressionSQL(&context) + " ASC" - case .desc(let expression): - return expression.expressionSQL(&context) + " DESC" - } - } - - func qualifiedOrdering(with alias: TableAlias) -> SQLOrderingTerm { - switch self { - case .asc(let expression): - return SQLOrdering.asc(expression.qualifiedExpression(with: alias)) - case .desc(let expression): - return SQLOrdering.desc(expression.qualifiedExpression(with: alias)) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLQuery.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLQuery.swift deleted file mode 100755 index 3d0ba88..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLQuery.swift +++ /dev/null @@ -1,171 +0,0 @@ -/// SQLQuery is a representation of an SQL query. -/// -/// See SQLQueryGenerator for actual SQL generation. -struct SQLQuery { - var relation: SQLRelation - var isDistinct: Bool - var expectsSingleResult: Bool - var groupPromise: DatabasePromise<[SQLExpression]>? - var havingExpression: SQLExpression? - var limit: SQLLimit? - - init( - relation: SQLRelation, - isDistinct: Bool = false, - expectsSingleResult: Bool = false, - groupPromise: DatabasePromise<[SQLExpression]>? = nil, - havingExpression: SQLExpression? = nil, - limit: SQLLimit? = nil) - { - self.relation = relation - self.isDistinct = isDistinct - self.expectsSingleResult = expectsSingleResult - self.groupPromise = groupPromise - self.havingExpression = havingExpression - self.limit = limit - } -} - -extension SQLQuery: SelectionRequest, FilteredRequest, OrderedRequest { - func select(_ selection: [SQLSelectable]) -> SQLQuery { - return mapRelation { $0.select(selection) } - } - - func annotated(with selection: [SQLSelectable]) -> SQLQuery { - return mapRelation { $0.annotated(with: selection) } - } - - func distinct() -> SQLQuery { - var query = self - query.isDistinct = true - return query - } - - func expectingSingleResult() -> SQLQuery { - var query = self - query.expectsSingleResult = true - return query - } - - func filter(_ predicate: @escaping (Database) throws -> SQLExpressible) -> SQLQuery { - return mapRelation { $0.filter(predicate) } - } - - func group(_ expressions: @escaping (Database) throws -> [SQLExpressible]) -> SQLQuery { - var query = self - query.groupPromise = DatabasePromise { db in try expressions(db).map { $0.sqlExpression } } - return query - } - - func having(_ predicate: SQLExpressible) -> SQLQuery { - var query = self - if let havingExpression = query.havingExpression { - query.havingExpression = (havingExpression && predicate).sqlExpression - } else { - query.havingExpression = predicate.sqlExpression - } - return query - } - - func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> SQLQuery { - return mapRelation { $0.order(orderings) } - } - - func reversed() -> SQLQuery { - return mapRelation { $0.reversed() } - } - - func unordered() -> SQLQuery { - return mapRelation { $0.unordered() } - } - - func limit(_ limit: Int, offset: Int? = nil) -> SQLQuery { - var query = self - query.limit = SQLLimit(limit: limit, offset: offset) - return query - } - - func qualified(with alias: TableAlias) -> SQLQuery { - return mapRelation { $0.qualified(with: alias) } - } - - /// Returns a query whose relation is transformed by the given closure. - func mapRelation(_ transform: (SQLRelation) -> SQLRelation) -> SQLQuery { - var query = self - query.relation = transform(relation) - return query - } -} - -extension SQLQuery { - func fetchCount(_ db: Database) throws -> Int { - let (statement, adapter) = try SQLQueryGenerator(countQuery).prepare(db) - return try Int.fetchOne(statement, adapter: adapter)! - } - - private var countQuery: SQLQuery { - guard groupPromise == nil && limit == nil else { - // SELECT ... GROUP BY ... - // SELECT ... LIMIT ... - return trivialCountQuery - } - - if relation.children.contains(where: { $0.value.impactsParentCount }) { // TODO: not tested - // SELECT ... FROM ... JOIN ... - return trivialCountQuery - } - - guard case .table = relation.source else { - // SELECT ... FROM (something which is not a plain table) - return trivialCountQuery - } - - GRDBPrecondition(!relation.selection.isEmpty, "Can't generate SQL with empty selection") - if relation.selection.count == 1 { - guard let count = relation.selection[0].count(distinct: isDistinct) else { - return trivialCountQuery - } - var countQuery = self.unordered() - countQuery.isDistinct = false - switch count { - case .all: - countQuery = countQuery.select(SQLExpressionCount(AllColumns())) - case .distinct(let expression): - countQuery = countQuery.select(SQLExpressionCountDistinct(expression)) - } - return countQuery - } else { - // SELECT [DISTINCT] expr1, expr2, ... FROM tableName ... - - guard !isDistinct else { - return trivialCountQuery - } - - // SELECT expr1, expr2, ... FROM tableName ... - // -> - // SELECT COUNT(*) FROM tableName ... - return self.unordered().select(SQLExpressionCount(AllColumns())) - } - } - - // SELECT COUNT(*) FROM (self) - private var trivialCountQuery: SQLQuery { - let relation = SQLRelation( - source: .query(unordered()), - selection: [SQLExpressionCount(AllColumns())]) - return SQLQuery(relation: relation) - } -} - -struct SQLLimit { - let limit: Int - let offset: Int? - - var sql: String { - if let offset = offset { - return "\(limit) OFFSET \(offset)" - } else { - return "\(limit)" - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLRelation.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLRelation.swift deleted file mode 100755 index 9320c50..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLRelation.swift +++ /dev/null @@ -1,814 +0,0 @@ -/// A "relation", as defined by the [relational terminology](https://en.wikipedia.org/wiki/Relational_database#Terminology), -/// is "a set of tuples sharing the same attributes; a set of columns and rows." -/// -/// SQLRelation is defined with a selection, a source table of query, and an -/// eventual filter and ordering. -/// -/// SELECT ... FROM ... WHERE ... ORDER BY ... -/// | | | | -/// | | | • ordering -/// | | • filter -/// | • source -/// • selection -/// -/// Other SQL clauses such as GROUP BY, LIMIT are defined one level up, in the -/// SQLQuery type. -/// -/// ## Promises -/// -/// Filter and ordering are actually "promises", which are only resolved -/// when a database connection is available. This is how we can implement -/// requests such as `Record.filter(key: 1)` or `Record.orderByPrimaryKey()`: -/// both need a database connection in order to introspect the primary key. -/// -/// // SELECT * FROM player -/// // WHERE continent = 'EU' -/// // ORDER BY code -- primary key infered from the database schema -/// Country -/// .filter(Column("continent") == "EU") -/// .orderByPrimaryKey() -/// -/// ## Children -/// -/// Relations may also have children. A child is a link to another relation, and -/// provide support for joins and prefetched relations. -/// -/// Relations and their children constitute a tree of relations, which the user -/// builds with associations: -/// -/// // Builds a relation with two children: -/// Author -/// .including(required: Author.country) -/// .including(all: Author.books) -/// -/// There are four kinds of children: -/// -/// - `.oneOptional`: -/// -/// Such children are left joined. They may, or not, be included in -/// the selection. -/// -/// // SELECT book.* -/// // FROM book -/// // LEFT JOIN author ON author.id = book.id -/// Book.joining(optional: Book.author) -/// -/// // SELECT book.*, author.* -/// // FROM book -/// // LEFT JOIN author ON author.id = book.id -/// Book.including(optional: Book.author) -/// -/// - `.oneRequired`: -/// -/// Such children are inner joined. They may, or not, be included in -/// the selection. -/// -/// // SELECT book.* -/// // FROM book -/// // JOIN author ON author.id = book.id AND author.country = 'FR' -/// Book.joining(required: Book.author.filter(Column("country") == "FR")) -/// -/// // SELECT book.*, author.* -/// // FROM book -/// // JOIN author ON author.id = book.id AND author.country = 'FR' -/// Book.including(required: Book.author.filter(Column("country") == "FR")) -/// -/// - `.allPrefetched`: -/// -/// Such children are prefetched using several SQL requests: -/// -/// // SELECT * FROM countries WHERE continent = 'EU' -/// // SELECT * FROM passport WHERE countryCode IN ('BE', 'DE', 'FR', ...) -/// Country -/// .filter(Column("continent") == "EU") -/// .including(all: Country.passports) -/// -/// - `.allNotPrefetched`: -/// -/// Such children are not joined, and not prefetched. They are used as -/// intermediate children towards a prefetched child. In the example -/// below, the country relation has a `.allNotPrefetched` child to -/// passports, and the passport relation has a `.allPrefetched` child -/// to citizens. -/// -/// // SELECT * FROM countries WHERE continent = 'EU' -/// // SELECT citizens.* FROM citizens -/// // JOIN passport ON passport.citizenId = citizens.id -/// // AND passport.countryCode IN ('BE', 'DE', 'FR', ...) -/// Country -/// .filter(Column("continent") == "EU") -/// .including(all: Country.citizens) -struct SQLRelation { - struct Child { - enum Kind { - // Record.including(optional: association) - case oneOptional - // Record.including(required: association) - case oneRequired - // Record.including(all: association) - case allPrefetched - // Record.including(all: associationThroughPivot) - case allNotPrefetched - - var cardinality: SQLAssociationCardinality { - switch self { - case .oneOptional, .oneRequired: - return .toOne - case .allPrefetched, .allNotPrefetched: - return .toMany - } - } - } - - var kind: Kind - var condition: SQLAssociationCondition - var relation: SQLRelation - - /// Returns true iff this child can change the parent count. - /// - /// Record.including(required: association) // true - /// Record.including(all: association) // false - var impactsParentCount: Bool { - switch kind { - case .oneOptional, .oneRequired: - return true - case .allPrefetched, .allNotPrefetched: - return false - } - } - - fileprivate func makeAssociationForKey(_ key: String) -> SQLAssociation { - let key = SQLAssociationKey.fixed(key) - return SQLAssociation( - key: key, - condition: condition, - relation: relation, - cardinality: kind.cardinality) - } - - func mapRelation(_ transform: (SQLRelation) -> SQLRelation) -> Child { - return Child(kind: kind, condition: condition, relation: transform(relation)) - } - } - - var source: SQLSource - var selection: [SQLSelectable] - var filterPromise: DatabasePromise - var ordering: SQLRelation.Ordering - var children: OrderedDictionary - - var prefetchedAssociations: [SQLAssociation] { - return children.flatMap { key, child -> [SQLAssociation] in - switch child.kind { - case .allPrefetched: - return [child.makeAssociationForKey(key)] - case .oneOptional, .oneRequired, .allNotPrefetched: - return child.relation.prefetchedAssociations.map { - $0.through(child.makeAssociationForKey(key)) - } - } - } - } - - init( - source: SQLSource, - selection: [SQLSelectable] = [], - filterPromise: DatabasePromise = DatabasePromise(value: nil), - ordering: SQLRelation.Ordering = SQLRelation.Ordering(), - children: OrderedDictionary = [:]) - { - self.source = source - self.selection = selection - self.filterPromise = filterPromise - self.ordering = ordering - self.children = children - } -} - -extension SQLRelation { - func select(_ selection: [SQLSelectable]) -> SQLRelation { - var relation = self - relation.selection = selection - return relation - } - - func annotated(with selection: [SQLSelectable]) -> SQLRelation { - var relation = self - relation.selection.append(contentsOf: selection) - return relation - } - - func filter(_ predicate: @escaping (Database) throws -> SQLExpressible) -> SQLRelation { - var relation = self - relation.filterPromise = relation.filterPromise.flatMap { filter in - if let filter = filter { - return DatabasePromise { try filter && predicate($0) } - } else { - return DatabasePromise { try predicate($0).sqlExpression } - } - } - return relation - } - - func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> SQLRelation { - return order(SQLRelation.Ordering(orderings: orderings)) - } - - func reversed() -> SQLRelation { - return order(ordering.reversed) - } - - func unordered() -> SQLRelation { - var relation = self - relation.ordering = SQLRelation.Ordering() - relation.children = relation.children.mapValues { $0.mapRelation { $0.unordered() } } - return relation - } - - private func order(_ ordering: SQLRelation.Ordering) -> SQLRelation { - var relation = self - relation.ordering = ordering - return relation - } - - func qualified(with alias: TableAlias) -> SQLRelation { - var relation = self - relation.source = source.qualified(with: alias) - return relation - } -} - -extension SQLRelation { - func deletingChildren() -> SQLRelation { - var relation = self - relation.children = [:] - return relation - } - - /// Creates a relation that prefetches another one. - func including(all association: SQLAssociation) -> SQLRelation { - return appending(association, kind: .allPrefetched) - } - - /// Creates a relation that includes another one. The columns of the - /// associated record are selected. The returned relation does not - /// require that the associated database table contains a matching row. - func including(optional association: SQLAssociation) -> SQLRelation { - return appending(association, kind: .oneOptional) - } - - /// Creates a relation that includes another one. The columns of the - /// associated record are selected. The returned relation requires - /// that the associated database table contains a matching row. - func including(required association: SQLAssociation) -> SQLRelation { - return appending(association, kind: .oneRequired) - } - - /// Creates a relation that joins another one. The columns of the - /// associated record are not selected. The returned relation does not - /// require that the associated database table contains a matching row. - func joining(optional association: SQLAssociation) -> SQLRelation { - return appending(association.mapDestinationRelation { $0.select([]) }, kind: .oneOptional) - } - - /// Creates a relation that joins another one. The columns of the - /// associated record are not selected. The returned relation requires - /// that the associated database table contains a matching row. - func joining(required association: SQLAssociation) -> SQLRelation { - return appending(association.mapDestinationRelation { $0.select([]) }, kind: .oneRequired) - } - - /// Returns a relation extended with an association. - /// - /// This method provides support for public joining methods such - /// as `including(required:)`: - /// - /// struct Destination: TableRecord { } - /// struct Origin: TableRecord { - /// static let destination = belongsTo(Destination.self) - /// } - /// - /// // SELECT origin.*, destination.* - /// // FROM origin - /// // JOIN destination ON destination.id = origin.destinationId - /// let request = Origin.including(required: Origin.destination) - /// - /// At low-level, this gives: - /// - /// let sqlAssociation = Origin.destination.sqlAssociation - /// let origin = Origin.all().query.relation - /// let relation = origin.appending(sqlAssociation, kind: .oneRequired) - /// let query = SQLQuery(relation: relation) - /// let generator = SQLQueryGenerator(query) - /// let statement, _ = try generator.prepare(db) - /// print(statement.sql) - /// // SELECT origin.*, destination.* - /// // FROM origin - /// // JOIN destination ON destination.originId = origin.id - /// - /// This method works for simple direct associations such as BelongsTo or - /// HasMany in the above examples, but also for indirect associations such - /// as HasManyThrough, which have any number of pivot relations between the - /// origin and the destination. - func appending(_ association: SQLAssociation, kind: SQLRelation.Child.Kind) -> SQLRelation { - let childCardinality = (kind == .allNotPrefetched) - // preserve association cardinality in intermediate steps of including(all:) - ? association.destination.cardinality - // force desired cardinality otherwize - : kind.cardinality - let childKey = association.destination.key.name(for: childCardinality) - let child = SQLRelation.Child( - kind: kind, - condition: association.destination.condition, - relation: association.destination.relation) - - let initialSteps = association.steps.dropLast() - if initialSteps.isEmpty { - // This is a direct join from origin to destination, without - // intermediate step. - // - // SELECT origin.*, destination.* - // FROM origin - // JOIN destination ON destination.id = origin.destinationId - // - // let association = Origin.belongsTo(Destination.self) - // Origin.including(required: association) - return appendingChild(child, forKey: childKey) - } - - // This is an indirect join from origin to destination, through - // some pivot(s): - // - // SELECT origin.*, destination.* - // FROM origin - // JOIN pivot ON pivot.originId = origin.id - // JOIN destination ON destination.id = pivot.destinationId - // - // let association = Origin.hasMany( - // Destination.self, - // through: Origin.hasMany(Pivot.self), - // via: Pivot.belongsTo(Destination.self)) - // Origin.including(required: association) - // - // Let's recurse toward a direct join, by making a new association which - // ends on the last pivot, to which we join our destination: - var reducedAssociation = SQLAssociation(steps: Array(initialSteps)) - - reducedAssociation.destination.relation = reducedAssociation.destination.relation - .select([]) // Intermediate steps are not prefetched - .appendingChild(child, forKey: childKey) - - switch kind { - case .oneRequired, .oneOptional, .allNotPrefetched: - return appending(reducedAssociation, kind: kind) - case .allPrefetched: - // Intermediate steps of indirect associations are not prefetched. - // - // For example, the request below prefetches citizens, not - // intermediate passports: - // - // extension Country { - // static let passports = hasMany(Passport.self) - // static let citizens = hasMany(Citizens.self, through: passports, using: Passport.citizen) - // } - // let request = Country.including(all: Country.citizens) - return appending(reducedAssociation, kind: .allNotPrefetched) - } - } - - private func appendingChild(_ child: SQLRelation.Child, forKey key: String) -> SQLRelation { - var relation = self - if let existingChild = relation.children.removeValue(forKey: key) { - guard let mergedChild = existingChild.merged(with: child) else { - // can't merge - fatalError("The association key \"\(key)\" is ambiguous. Use the Association.forKey(_:) method is order to disambiguate.") - } - relation.children.appendValue(mergedChild, forKey: key) - } else { - relation.children.appendValue(child, forKey: key) - } - return relation - } -} - -// MARK: - SQLSource - -enum SQLSource { - case table(tableName: String, alias: TableAlias?) - indirect case query(SQLQuery) - - func qualified(with alias: TableAlias) -> SQLSource { - switch self { - case .table(let tableName, let sourceAlias): - if let sourceAlias = sourceAlias { - alias.becomeProxy(of: sourceAlias) - return self - } else { - alias.setTableName(tableName) - return .table(tableName: tableName, alias: alias) - } - case .query(let query): - return .query(query.qualified(with: alias)) - } - } -} - -// MARK: - SQLRelation.Ordering - -extension SQLRelation { - /// SQLRelation.Ordering provides the order clause to SQLRelation. - struct Ordering { - private enum Element { - case terms(DatabasePromise<[SQLOrderingTerm]>) - case ordering(SQLRelation.Ordering) - - var reversed: Element { - switch self { - case .terms(let terms): - return .terms(terms.map { $0.map { $0.reversed } }) - case .ordering(let ordering): - return .ordering(ordering.reversed) - } - } - - func qualified(with alias: TableAlias) -> Element { - switch self { - case .terms(let terms): - return .terms(terms.map { $0.map { $0.qualifiedOrdering(with: alias) } }) - case .ordering(let ordering): - return .ordering(ordering.qualified(with: alias)) - } - } - - func resolve(_ db: Database) throws -> [SQLOrderingTerm] { - switch self { - case .terms(let terms): - return try terms.resolve(db) - case .ordering(let ordering): - return try ordering.resolve(db) - } - } - } - - private var elements: [Element] = [] - var isReversed: Bool - - var isEmpty: Bool { - return elements.isEmpty - } - - private init(elements: [Element], isReversed: Bool) { - self.elements = elements - self.isReversed = isReversed - } - - init() { - self.init( - elements: [], - isReversed: false) - } - - init(orderings: @escaping (Database) throws -> [SQLOrderingTerm]) { - self.init( - elements: [.terms(DatabasePromise(orderings))], - isReversed: false) - } - - var reversed: Ordering { - return Ordering( - elements: elements, - isReversed: !isReversed) - } - - func qualified(with alias: TableAlias) -> Ordering { - return Ordering( - elements: elements.map { $0.qualified(with: alias) }, - isReversed: isReversed) - } - - func appending(_ ordering: Ordering) -> Ordering { - return Ordering( - elements: elements + [.ordering(ordering)], - isReversed: isReversed) - } - - func resolve(_ db: Database) throws -> [SQLOrderingTerm] { - if isReversed { - return try elements.flatMap { try $0.reversed.resolve(db) } - } else { - return try elements.flatMap { try $0.resolve(db) } - } - } - } -} - -// MARK: - SQLAssociationCondition - -/// The condition that links two tables. -/// -/// Currently, we only support one kind of condition: foreign keys. -/// -/// SELECT ... FROM book JOIN author ON author.id = book.authorId -/// <---- the condition ----> -/// -/// When we eventually add support for new ways to link tables, -/// SQLAssociationCondition is the type we'll need to update. -/// -/// SQLAssociationCondition adopts Equatable so that we can merge associations: -/// -/// // request1 and request2 are equivalent -/// let request1 = Book -/// .including(required: Book.author) -/// let request2 = Book -/// .including(required: Book.author) -/// .including(required: Book.author) -/// -/// // request3 and request4 are equivalent -/// let request3 = Book -/// .including(required: Book.author.filter(condition1 && condition2)) -/// let request4 = Book -/// .joining(required: Book.author.filter(condition1)) -/// .including(optional: Book.author.filter(condition2)) -struct SQLAssociationCondition: Equatable { - /// Definition of a foreign key - var foreignKeyRequest: SQLForeignKeyRequest - - /// True if the table at the origin of the foreign key is on the left of - /// the sql JOIN operator. - /// - /// Let's consider the `book.authorId -> author.id` foreign key. - /// Its origin table is `book`. - /// - /// The origin table `book` is on the left of the JOIN operator for - /// the BelongsTo association: - /// - /// -- Book.including(required: Book.author) - /// SELECT ... FROM book JOIN author ON author.id = book.authorId - /// - /// The origin table `book`is on the right of the JOIN operator for - /// the HasMany and HasOne associations: - /// - /// -- Author.including(required: Author.books) - /// SELECT ... FROM author JOIN book ON author.id = book.authorId - var originIsLeft: Bool - - var reversed: SQLAssociationCondition { - return SQLAssociationCondition( - foreignKeyRequest: foreignKeyRequest, - originIsLeft: !originIsLeft) - } - - /// Orient foreignKey according to the originIsLeft flag - func columnMappings(_ db: Database) throws -> [(left: String, right: String)] { - let foreignKeyMapping = try foreignKeyRequest.fetchMapping(db) - if originIsLeft { - return foreignKeyMapping.map { (left: $0.origin, right: $0.destination) } - } else { - return foreignKeyMapping.map { (left: $0.destination, right: $0.origin) } - } - } - - /// Resolves the condition into an SQL expression which involves both left - /// and right tables. - /// - /// SELECT * FROM left JOIN right ON (right.a = left.b) - /// <----------------> - /// - /// - parameter db: A database connection. - /// - parameter leftAlias: A TableAlias for the table on the left of the - /// JOIN operator. - /// - parameter rightAlias: A TableAlias for the table on the right of the - /// JOIN operator. - /// - Returns: An SQL expression. - func joinExpression(_ db: Database, leftAlias: TableAlias, rightAlias: TableAlias) throws -> SQLExpression { - return try columnMappings(db) - .map { QualifiedColumn($0.right, alias: rightAlias) == QualifiedColumn($0.left, alias: leftAlias) } - .joined(operator: .and) - } - - /// Resolves the condition into an SQL expression which involves only the - /// right table. - /// - /// Given `right.a = left.b`, returns `right.a = 1` or - /// `right.a IN (1, 2, 3)`. - func filteringExpression(_ db: Database, leftRows: [Row], rightAlias: TableAlias) throws -> SQLExpression { - if leftRows.isEmpty { - // Degenerate case: there is no row to attach - return false.sqlExpression - } - - let columnMappings = try self.columnMappings(db) - guard let columnMapping = columnMappings.first else { - // Degenerate case: no joining column - return true.sqlExpression - } - - if columnMappings.count == 1 { - // Join on a single right column. - let rightColumn = QualifiedColumn(columnMapping.right, alias: rightAlias) - - // Unique database values and filter out NULL: - var dbValues = Set(leftRows.map { $0[columnMapping.left] as DatabaseValue }) - dbValues.remove(.null) - - if dbValues.isEmpty { - // Can't join - return false.sqlExpression - } else { - // table.a IN (1, 2, 3, ...) - // Sort database values for nicer output. - return dbValues.sorted(by: <).contains(rightColumn) - } - } else { - // Join on a multiple columns. - // ((table.a = 1) AND (table.b = 2)) OR ((table.a = 3) AND (table.b = 4)) ... - return leftRows - .map { leftRow in - // (table.a = 1) AND (table.b = 2) - columnMappings - .map { columns -> SQLExpression in - let rightColumn = QualifiedColumn(columns.right, alias: rightAlias) - let leftValue = leftRow[columns.left] as DatabaseValue - return rightColumn == leftValue - } - .joined(operator: .and) - } - .joined(operator: .or) - } - } -} - -// MARK: - Merging -// -// "Merging" is an operation that takes two relations and, if they are -// compatible, gathers them into a merged relation. -// -// It is an important feature that allows the user to define associated requests -// in several steps. For example, in the sample code below, both requests are -// equivalent and generate the same SQL query, thanks to merging: -// -// let request1 = Book.including(required: Book.author) -// -// let request2 = Book -// .including(required: Book.author) -// .including(required: Book.author) - -extension SQLRelation { - /// Returns nil if relations can't be merged (conflict in source, joins...) - func merged(with other: SQLRelation) -> SQLRelation? { - guard let mergedSource = source.merged(with: other.source) else { - // can't merge - return nil - } - - let mergedFilterPromise: DatabasePromise = filterPromise.flatMap { expression in - return DatabasePromise { db in - let otherExpression = try other.filterPromise.resolve(db) - let expressions = [expression, otherExpression].compactMap { $0 } - if expressions.isEmpty { - return nil - } else { - return expressions.joined(operator: .and) - } - } - } - - var mergedChildren: OrderedDictionary = [:] - for (key, child) in children { - if let otherChild = other.children[key] { - guard let mergedChild = child.merged(with: otherChild) else { - // can't merge - return nil - } - mergedChildren.appendValue(mergedChild, forKey: key) - } else { - mergedChildren.appendValue(child, forKey: key) - } - } - for (key, child) in other.children where mergedChildren[key] == nil { - mergedChildren.appendValue(child, forKey: key) - } - - // replace selection unless empty - let mergedSelection = other.selection.isEmpty ? selection : other.selection - - // replace ordering unless empty - let mergedOrdering = other.ordering.isEmpty ? ordering : other.ordering - - return SQLRelation( - source: mergedSource, - selection: mergedSelection, - filterPromise: mergedFilterPromise, - ordering: mergedOrdering, - children: mergedChildren) - } -} - -extension SQLSource { - /// Returns nil if sources can't be merged (conflict in tables, aliases...) - func merged(with other: SQLSource) -> SQLSource? { - switch (self, other) { - case let (.table(tableName: tableName, alias: alias), .table(tableName: otherTableName, alias: otherAlias)): - guard tableName == otherTableName else { - // can't merge - return nil - } - switch (alias, otherAlias) { - case (nil, nil): - return .table(tableName: tableName, alias: nil) - case let (alias?, nil), let (nil, alias?): - return .table(tableName: tableName, alias: alias) - case let (alias?, otherAlias?): - guard let mergedAlias = alias.merged(with: otherAlias) else { - // can't merge - return nil - } - return .table(tableName: tableName, alias: mergedAlias) - } - default: - // can't merge - return nil - } - } -} - -extension SQLRelation.Child { - /// Returns nil if joins can't be merged (conflict in condition, relation...) - func merged(with other: SQLRelation.Child) -> SQLRelation.Child? { - guard condition == other.condition else { - // can't merge - return nil - } - - guard let mergedRelation = relation.merged(with: other.relation) else { - // can't merge - return nil - } - - guard let mergedKind = kind.merged(with: other.kind) else { - // can't merge - return nil - } - - return SQLRelation.Child( - kind: mergedKind, - condition: condition, - relation: mergedRelation) - } -} - -extension SQLRelation.Child.Kind { - /// Returns nil if kinds can't be merged - func merged(with other: SQLRelation.Child.Kind) -> SQLRelation.Child.Kind? { - switch (self, other) { - case (.oneRequired, .oneRequired), - (.oneRequired, .oneOptional), - (.oneOptional, .oneRequired): - // Equivalent to Record.including(required: association): - // - // Record - // .including(required: association) - // .including(optional: association) - return .oneRequired - - case (.oneOptional, .oneOptional): - // Equivalent to Record.including(optional: association): - // - // Record - // .including(optional: association) - // .including(optional: association) - return .oneOptional - - case (.allPrefetched, .allPrefetched): - // Equivalent to Record.including(all: association): - // - // Record - // .including(all: association) - // .including(all: association) - return .allPrefetched - - case (.allPrefetched, .allNotPrefetched), - (.allNotPrefetched, .allPrefetched): - // Record - // .including(all: associationToDestinationThroughPivot) - // .including(all: associationToPivot) - fatalError("Not implemented: merging a direct association and an indirect one with including(all:)") - - case (.allNotPrefetched, .allNotPrefetched): - // Equivalent to Record.including(all: association) - // - // Record - // .including(all: association) - // .including(all: association) - return .allNotPrefetched - - default: - // Likely a programmer error: - // - // Record - // .including(all: Author.books.forKey("foo")) - // .including(optional: Author.books.forKey("foo")) - return nil - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSelectable+QueryInterface.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSelectable+QueryInterface.swift deleted file mode 100755 index 08fbe4c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSelectable+QueryInterface.swift +++ /dev/null @@ -1,149 +0,0 @@ -// MARK: - AllColumns - -/// AllColumns is the `*` in `SELECT *`. -/// -/// You use AllColumns in your custom implementation of -/// TableRecord.databaseSelection. -/// -/// For example: -/// -/// struct Player : TableRecord { -/// static var databaseTableName = "player" -/// static let databaseSelection: [SQLSelectable] = [AllColumns(), Column.rowID] -/// } -/// -/// // SELECT *, rowid FROM player -/// let request = Player.all() -public struct AllColumns { - public init() { } -} - -extension AllColumns : SQLSelectable { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func resultColumnSQL(_ context: inout SQLGenerationContext) -> String { - return "*" - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func countedSQL(_ context: inout SQLGenerationContext) -> String { - return "*" - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func count(distinct: Bool) -> SQLCount? { - // SELECT DISTINCT * FROM tableName ... - if distinct { - return nil - } - - // SELECT * FROM tableName ... - // -> - // SELECT COUNT(*) FROM tableName ... - return .all - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func qualifiedSelectable(with alias: TableAlias) -> SQLSelectable { - return QualifiedAllColumns(alias: alias) - } - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public func columnCount(_ db: Database) throws -> Int { - fatalError("Can't compute number of columns without an alias") - } -} - -// MARK: - QualifiedAllColumns - -/// QualifiedAllColumns is the `t.*` in `SELECT t.*`. -struct QualifiedAllColumns { - private let alias: TableAlias - - init(alias: TableAlias) { - self.alias = alias - } -} - -extension QualifiedAllColumns : SQLSelectable { - func resultColumnSQL(_ context: inout SQLGenerationContext) -> String { - if let qualifier = context.qualifier(for: alias) { - return qualifier.quotedDatabaseIdentifier + ".*" - } - return "*" - } - - func countedSQL(_ context: inout SQLGenerationContext) -> String { - // TODO: restore the check below. - // - // It is currently disabled because of AssociationAggregateTests.testHasManyIsEmpty: - // - // let request = Team.having(Team.players.isEmpty) - // try XCTAssertEqual(request.fetchCount(db), 1) - // - // This should build the trivial count query `SELECT COUNT(*) FROM (SELECT ...)` - // - // Unfortunately, we don't support anonymous table aliases that would be - // required here. Because we don't support anonymous tables aliases, - // everything happens as if we wanted to generate - // `SELECT COUNT(team.*) FROM (SELECT ...)`, which is invalid SQL. - // - // So let's always return `*`, and fix this later. - -// if context.qualifier(for: alias) != nil { -// // SELECT COUNT(t.*) is invalid SQL -// fatalError("Not implemented, or invalid query") -// } - - return "*" - } - - func count(distinct: Bool) -> SQLCount? { - return nil - } - - func qualifiedSelectable(with alias: TableAlias) -> SQLSelectable { - // Never requalify - return self - } - - func columnCount(_ db: Database) throws -> Int { - return try db.columns(in: alias.tableName).count - } -} - -// MARK: - SQLAliasedExpression - -struct SQLAliasedExpression : SQLSelectable { - let expression: SQLExpression - let name: String - - init(_ expression: SQLExpression, name: String) { - self.expression = expression - self.name = name - } - - func resultColumnSQL(_ context: inout SQLGenerationContext) -> String { - return expression.resultColumnSQL(&context) + " AS " + name.quotedDatabaseIdentifier - } - - func countedSQL(_ context: inout SQLGenerationContext) -> String { - return expression.countedSQL(&context) - } - - func count(distinct: Bool) -> SQLCount? { - return expression.count(distinct: distinct) - } - - func qualifiedSelectable(with alias: TableAlias) -> SQLSelectable { - return SQLAliasedExpression(expression.qualifiedExpression(with: alias), name: name) - } - - func columnCount(_ db: Database) throws -> Int { - return 1 - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSelectable.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSelectable.swift deleted file mode 100755 index cc492f4..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSelectable.swift +++ /dev/null @@ -1,76 +0,0 @@ -// MARK: - SQLSelectable - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLSelectable is the protocol for types that can be selected, as -/// described at https://www.sqlite.org/syntax/result-column.html -/// -/// :nodoc: -public protocol SQLSelectable { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func resultColumnSQL(_ context: inout SQLGenerationContext) -> String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func countedSQL(_ context: inout SQLGenerationContext) -> String - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func count(distinct: Bool) -> SQLCount? - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func columnCount(_ db: Database) throws -> Int - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - func qualifiedSelectable(with alias: TableAlias) -> SQLSelectable -} - -// MARK: - SQLSelectionLiteral - -struct SQLSelectionLiteral : SQLSelectable { - private let sqlLiteral: SQLLiteral - - init(literal sqlLiteral: SQLLiteral) { - self.sqlLiteral = sqlLiteral - } - - func resultColumnSQL(_ context: inout SQLGenerationContext) -> String { - if context.append(arguments: sqlLiteral.arguments) == false { - // GRDB limitation: we don't know how to look for `?` in sql and - // replace them with with literals. - fatalError("Not implemented") - } - return sqlLiteral.sql - } - - func countedSQL(_ context: inout SQLGenerationContext) -> String { - fatalError("Selection literals can't be counted. To resolve this error, select one or several SQLExpressionLiteral instead.") - } - - func count(distinct: Bool) -> SQLCount? { - fatalError("Selection literals can't be counted. To resolve this error, select one or several SQLExpressionLiteral instead.") - } - - func columnCount(_ db: Database) throws -> Int { - fatalError("Selection literals don't known how many columns they contain. To resolve this error, select one or several SQLExpressionLiteral instead.") - } - - func qualifiedSelectable(with alias: TableAlias) -> SQLSelectable { - return self - } -} - -// MARK: - Counting - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// :nodoc: -public enum SQLCount { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Represents COUNT(*) - case all - - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Represents COUNT(DISTINCT expression) - case distinct(SQLExpression) -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSpecificExpressible+QueryInterface.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSpecificExpressible+QueryInterface.swift deleted file mode 100755 index 329f3b3..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQL/SQLSpecificExpressible+QueryInterface.swift +++ /dev/null @@ -1,66 +0,0 @@ -// MARK: - SQL Ordering Support - -/// :nodoc: -extension SQLSpecificExpressible { - - /// Returns a value that can be used as an argument to QueryInterfaceRequest.order() - /// - /// See https://github.com/groue/GRDB.swift/#the-query-interface - public var asc: SQLOrderingTerm { - return SQLOrdering.asc(sqlExpression) - } - - /// Returns a value that can be used as an argument to QueryInterfaceRequest.order() - /// - /// See https://github.com/groue/GRDB.swift/#the-query-interface - public var desc: SQLOrderingTerm { - return SQLOrdering.desc(sqlExpression) - } -} - - -// MARK: - SQL Selection Support - -/// :nodoc: -extension SQLSpecificExpressible { - - /// Returns a value that can be used as an argument to QueryInterfaceRequest.select() - /// - /// See https://github.com/groue/GRDB.swift/#the-query-interface - public func aliased(_ name: String) -> SQLSelectable { - return SQLAliasedExpression(sqlExpression, name: name) - } - - /// Returns a value that can be used as an argument to QueryInterfaceRequest.select() - /// - /// See https://github.com/groue/GRDB.swift/#the-query-interface - public func aliased(_ key: CodingKey) -> SQLSelectable { - return aliased(key.stringValue) - } -} - - -// MARK: - SQL Collations Support - -/// :nodoc: -extension SQLSpecificExpressible { - - /// Returns a collated expression. - /// - /// For example: - /// - /// Player.filter(Column("email").collating(.nocase) == "contact@example.com") - public func collating(_ collation: Database.CollationName) -> SQLCollatedExpression { - return SQLCollatedExpression(sqlExpression, collationName: collation) - } - - /// Returns a collated expression. - /// - /// For example: - /// - /// Player.filter(Column("name").collating(.localizedStandardCompare) == "Hervé") - public func collating(_ collation: DatabaseCollation) -> SQLCollatedExpression { - return SQLCollatedExpression(sqlExpression, collationName: Database.CollationName(collation.name)) - } -} - diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLGeneration/SQLGenerationContext.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLGeneration/SQLGenerationContext.swift deleted file mode 100755 index 60d5f4a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLGeneration/SQLGenerationContext.swift +++ /dev/null @@ -1,342 +0,0 @@ -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// SQLGenerationContext is responsible for preventing SQL injection and -/// disambiguating table names when GRDB generates SQL queries. -/// -/// :nodoc: -public struct SQLGenerationContext { - var arguments: StatementArguments? - private var resolvedNames: [TableAlias: String] - private var qualifierNeeded: Bool - - /// Used for SQLExpression -> SQLExpressionLiteral conversion - /// and SQLInterpolation - static func literalGenerationContext(withArguments: Bool) -> SQLGenerationContext { - return SQLGenerationContext( - arguments: withArguments ? [] : nil, - resolvedNames: [:], - qualifierNeeded: false) - } - - /// Used for SQLQuery.makeSelectStatement() and SQLQuery.makeDeleteStatement() - static func queryGenerationContext(aliases: [TableAlias]) -> SQLGenerationContext { - // TODO: since 5d9fa76, in the lines below, we started uniquing aliases. - // Something tells me this is a wrong fix. We should try to investigate - // and maybe revert this change. I don't see why we'd really need to - // unique aliases, and I'm afraid we're hiding some latent bug - // somewhere else. - - // Unique aliases, but with preserved ordering, so that we have stable SQL generation - let uniqueAliases = aliases.reduce(into: [TableAlias]()) { - if !$0.contains($1) { - $0.append($1) - } - } - return SQLGenerationContext( - arguments: [], - resolvedNames: uniqueAliases.resolvedNames, - qualifierNeeded: uniqueAliases.count > 1) - } - - /// Used for TableRecord.selectionSQL - static func recordSelectionGenerationContext() -> SQLGenerationContext { - return SQLGenerationContext( - arguments: nil, - resolvedNames: [:], - qualifierNeeded: true) - } - - /// Returns whether arguments could be appended - mutating func append(arguments newArguments: StatementArguments) -> Bool { - if newArguments.isEmpty { - return true - } - guard let arguments = arguments else { - return false - } - self.arguments = arguments + newArguments - return true - } - - /// May be nil, when a qualifier is not needed: - /// - /// WHERE .column == 1 - /// SELECT .* - /// - /// WHERE column == 1 - /// SELECT * - func qualifier(for alias: TableAlias) -> String? { - if alias.hasUserName { - return alias.identityName - } - if qualifierNeeded == false { - return nil - } - return resolvedName(for: alias) - } - - /// WHERE MATCH pattern - func resolvedName(for alias: TableAlias) -> String { - return resolvedNames[alias] ?? alias.identityName - } - - /// FROM tableName - func aliasName(for alias: TableAlias) -> String? { - let resolvedName = self.resolvedName(for: alias) - if resolvedName != alias.tableName { - return resolvedName - } - return nil - } -} - -// MARK: - TableAlias - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A TableAlias identifies a table in a request. -public class TableAlias: Hashable { - private var impl: Impl - private enum Impl { - /// A TableAlias is undefined when it is created by the GRDB user: - /// - /// let alias = TableAlias() - /// let alias = TableAlias(name: "custom") - case undefined(userName: String?) - - /// A TableAlias is a table when explicitly specified: - /// - /// let alias = TableAlias(tableName: "player") - /// - /// Or when it qualifies a request that wasn't qualified yet (in which - /// case it turns from undefined to a table): - /// - /// // SELECT custom.* FROM player custom - /// let alias = TableAlias(name: "custom") - /// let request = Player.all().aliased(alias) - case table(tableName: String, userName: String?) - - /// A TableAlias can be a proxy for another table alias. Two different - /// instances for the same table identifier: - /// - /// // Pointless example: make alias2 a proxy for alias1 - /// let alias1 = TableAlias() - /// let alias2 = TableAlias() - /// Player.all() - /// .aliased(alias1) - /// .aliased(alias2) - /// - /// Proxies are useful because queries get implicit aliases as soon - /// as they are joined with associations. In the example below, - /// customAlias becomes a proxy for the request's implicit alias, which - /// gets a custom name. This allows implicit and user aliases to merge - /// into a single "table identifier" that matches the user's expectations: - /// - /// // SELECT custom.*, team.* - /// // FROM player custom - /// // JOIN team ON taem.id = custom.teamId - /// // WHERE custom.name = 'Arthur' - /// let customAlias = TableAlias(name: "custom") - /// let request = Player - /// .including(required: Player.team) - /// .filter(sql: "custom.name = 'Arthur'") - /// .aliased(customAlias) - case proxy(TableAlias) - } - - /// Resolve all proxies - private var root: TableAlias { - if case .proxy(let base) = impl { - return base.root - } else { - return self - } - } - - // exposed to SQLGenerationContext - fileprivate var identityName: String { - return userName ?? tableName - } - - // exposed to SQLGenerationContext - fileprivate var hasUserName: Bool { - return userName != nil - } - - var tableName: String { - switch impl { - case .undefined: - // Likely a GRDB bug - fatalError("Undefined alias has no table name") - case .table(tableName: let tableName, userName: _): - return tableName - case .proxy(let base): - return base.tableName - } - } - - private var userName: String? { - switch impl { - case .undefined(let userName): - return userName - case .table(tableName: _, userName: let userName): - return userName - case .proxy(let base): - return base.userName - } - } - - public init(name: String? = nil) { - self.impl = .undefined(userName: name) - } - - init(tableName: String, userName: String? = nil) { - self.impl = .table(tableName: tableName, userName: userName) - } - - func becomeProxy(of base: TableAlias) { - switch impl { - case let .undefined(userName): - if let userName = userName { - // rename - assert(base.userName == nil || base.userName == userName) - base.setUserName(userName) - } - self.impl = .proxy(base) - case let .table(tableName: tableName, userName: userName): - assert(tableName == base.tableName) - if let userName = userName { - // rename - assert(base.userName == nil || base.userName == userName) - base.setUserName(userName) - } - self.impl = .proxy(base) - case let .proxy(selfBase): - selfBase.becomeProxy(of: base) - } - } - - /// Returns nil if aliases can't be merged (conflict in tables, aliases...) - func merged(with other: TableAlias) -> TableAlias? { - let root = self.root - let otherRoot = other.root - switch (root.impl, otherRoot.impl) { - case let (.table(tableName: tableName, userName: userName), .table(tableName: otherTableName, userName: otherUserName)): - guard tableName == otherTableName else { - // can't merge - return nil - } - if let userName = userName, let otherUserName = otherUserName, userName != otherUserName { - // can't merge - return nil - } - root.becomeProxy(of: otherRoot) - return otherRoot - default: - // can't merge - return nil - } - } - - private func setUserName(_ userName: String) { - switch impl { - case .undefined: - self.impl = .undefined(userName: userName) - case .table(tableName: let tableName, userName: _): - self.impl = .table(tableName: tableName, userName: userName) - case .proxy(let base): - base.setUserName(userName) - } - } - - func setTableName(_ tableName: String) { - switch impl { - case .undefined(let userName): - self.impl = .table(tableName: tableName, userName: userName) - case .table(tableName: let initialTableName, userName: _): - // It is a programmer error to reuse the same TableAlias for - // multiple tables. - // - // // Don't do that - // let alias = TableAlias() - // let books = Book.aliased(alias)... - // let authors = Author.aliased(alias)... - GRDBPrecondition( - tableName.lowercased() == initialTableName.lowercased(), - "A TableAlias most not be used to refer to multiple tables") - case .proxy(let base): - base.setTableName(tableName) - } - } - - /// Returns a qualified value that is able to resolve ambiguities in - /// joined queries. - public subscript(_ selectable: SQLSelectable) -> SQLSelectable { - return selectable.qualifiedSelectable(with: self) - } - - /// Returns a qualified expression that is able to resolve ambiguities in - /// joined queries. - public subscript(_ expression: SQLExpression) -> SQLExpression { - return expression.qualifiedExpression(with: self) - } - - /// :nodoc: - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(root)) - } - - /// :nodoc: - public static func == (lhs: TableAlias, rhs: TableAlias) -> Bool { - return ObjectIdentifier(lhs.root) == ObjectIdentifier(rhs.root) - } -} - -extension Array where Element == TableAlias { - /// Resolve ambiguities in aliases' names. - fileprivate var resolvedNames: [TableAlias: String] { - // It is a programmer error to reuse the same TableAlias for - // multiple tables. - // - // // Don't do that - // let alias = TableAlias() - // let request = Book - // .including(required: Book.author.aliased(alias)...) - // .including(required: Book.author.aliased(alias)...) - GRDBPrecondition(count == Set(self).count, "A TableAlias most not be used to refer to multiple tables") - - let groups = Dictionary(grouping: self) { - $0.identityName.lowercased() - } - - var uniqueLowercaseNames: Set = [] - var ambiguousGroups: [[TableAlias]] = [] - - for (lowercaseName, group) in groups { - if group.count > 1 { - // It is a programmer error to reuse the same alias for multiple tables - GRDBPrecondition(group.count { $0.hasUserName } < 2, "ambiguous alias: \(group[0].identityName)") - ambiguousGroups.append(group) - } else { - uniqueLowercaseNames.insert(lowercaseName) - } - } - - var resolvedNames: [TableAlias: String] = [:] - for group in ambiguousGroups { - var index = 1 - for alias in group { - if alias.hasUserName { continue } - let radical = alias.identityName.digitlessRadical - var resolvedName: String - repeat { - resolvedName = "\(radical)\(index)" - index += 1 - } while uniqueLowercaseNames.contains(resolvedName.lowercased()) - uniqueLowercaseNames.insert(resolvedName.lowercased()) - resolvedNames[alias] = resolvedName - } - } - return resolvedNames - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLGeneration/SQLQueryGenerator.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLGeneration/SQLQueryGenerator.swift deleted file mode 100755 index e40a985..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLGeneration/SQLQueryGenerator.swift +++ /dev/null @@ -1,481 +0,0 @@ -/// SQLQueryGenerator is able to generate an SQL SELECT query. -struct SQLQueryGenerator { - fileprivate let relation: SQLQualifiedRelation - private let isDistinct: Bool - private let groupPromise: DatabasePromise<[SQLExpression]>? - private let havingExpression: SQLExpression? - private let limit: SQLLimit? - - init(_ query: SQLQuery) { - // To generate SQL, we need a "qualified" relation, where all tables, - // expressions, etc, are identified with table aliases. - // - // All those aliases let us disambiguate tables at the SQL level, and - // prefix columns names. For example, the following request... - // - // Book.filter(Column("kind") == Book.Kind.novel) - // .including(optional: Book.author) - // .including(optional: Book.translator) - // .annotated(with: Book.awards.count) - // - // ... generates the following SQL, where all identifiers are correctly - // disambiguated and qualified: - // - // SELECT book.*, person1.*, person2.*, COUNT(DISTINCT award.id) - // FROM book - // LEFT JOIN person person1 ON person1.id = book.authorId - // LEFT JOIN person person2 ON person2.id = book.translatorId - // LEFT JOIN award ON award.bookId = book.id - // GROUP BY book.id - // WHERE book.kind = 'novel' - relation = SQLQualifiedRelation(query.relation) - - // Qualify group expressions and having clause with the relation alias. - // - // This turns `GROUP BY id` INTO `GROUP BY book.id`, and - // `HAVING MAX(year) < 2000` INTO `HAVING MAX(book.year) < 2000`. - let alias = relation.alias - groupPromise = query.groupPromise?.map { [alias] in $0.map { $0.qualifiedExpression(with: alias) } } - havingExpression = query.havingExpression?.qualifiedExpression(with: alias) - - // Preserve other flags - isDistinct = query.isDistinct - limit = query.limit - } - - func sql(_ db: Database, _ context: inout SQLGenerationContext) throws -> String { - var sql = "SELECT" - - if isDistinct { - sql += " DISTINCT" - } - - let selection = relation.selection - GRDBPrecondition(!selection.isEmpty, "Can't generate SQL with empty selection") - sql += " " + selection.map { $0.resultColumnSQL(&context) }.joined(separator: ", ") - - sql += try " FROM " + relation.source.sql(db, &context) - - for (_, join) in relation.joins { - sql += try " " + join.sql(db, &context, leftAlias: relation.alias) - } - - if let filter = try relation.filterPromise.resolve(db) { - sql += " WHERE " + filter.expressionSQL(&context) - } - - if let groupExpressions = try groupPromise?.resolve(db), !groupExpressions.isEmpty { - sql += " GROUP BY " + groupExpressions.map { $0.expressionSQL(&context) }.joined(separator: ", ") - } - - if let havingExpression = havingExpression { - sql += " HAVING " + havingExpression.expressionSQL(&context) - } - - let orderings = try relation.ordering.resolve(db) - if !orderings.isEmpty { - sql += " ORDER BY " + orderings.map { $0.orderingTermSQL(&context) }.joined(separator: ", ") - } - - if let limit = limit { - sql += " LIMIT " + limit.sql - } - - return sql - } - - func prepare(_ db: Database) throws -> (SelectStatement, RowAdapter?) { - return try (makeSelectStatement(db), rowAdapter(db)) - } - - func databaseRegion(_ db: Database) throws -> DatabaseRegion { - let statement = try makeSelectStatement(db) - let databaseRegion = statement.databaseRegion - - // Can we intersect the region with rowIds? - // - // Give up unless request feeds from a single database table - guard case .table(tableName: let tableName, alias: _) = relation.source else { - // TODO: try harder - return databaseRegion - } - - // Give up unless primary key is rowId - let primaryKeyInfo = try db.primaryKey(tableName) - guard primaryKeyInfo.isRowID else { - return databaseRegion - } - - // Give up unless there is a where clause - guard let filter = try relation.filterPromise.resolve(db) else { - return databaseRegion - } - - // The filter knows better - guard let rowIds = filter.matchedRowIds(rowIdName: primaryKeyInfo.rowIDColumn) else { - return databaseRegion - } - - // Database regions are case-insensitive: use the canonical table name - let canonicalTableName = try db.canonicalTableName(tableName) - return databaseRegion.tableIntersection(canonicalTableName, rowIds: rowIds) - } - - func makeDeleteStatement(_ db: Database) throws -> UpdateStatement { - if let groupExpressions = try groupPromise?.resolve(db), !groupExpressions.isEmpty { - // Programmer error - fatalError("Can't delete query with GROUP BY clause") - } - - guard havingExpression == nil else { - // Programmer error - fatalError("Can't delete query with HAVING clause") - } - - guard relation.joins.isEmpty else { - // Programmer error - fatalError("Can't delete query with JOIN clause") - } - - guard case .table = relation.source else { - // Programmer error - fatalError("Can't delete without any database table") - } - - var context = SQLGenerationContext.queryGenerationContext(aliases: relation.allAliases) - - var sql = try "DELETE FROM " + relation.source.sql(db, &context) - - if let filter = try relation.filterPromise.resolve(db) { - sql += " WHERE " + filter.expressionSQL(&context) - } - - if let limit = limit { - let orderings = try relation.ordering.resolve(db) - if !orderings.isEmpty { - sql += " ORDER BY " + orderings.map { $0.orderingTermSQL(&context) }.joined(separator: ", ") - } - - if Database.sqliteCompileOptions.contains("ENABLE_UPDATE_DELETE_LIMIT") { - sql += " LIMIT " + limit.sql - } else { - fatalError("Can't delete query with limit") - } - } - - let statement = try db.makeUpdateStatement(sql: sql) - statement.arguments = context.arguments! - return statement - } - - /// Returns a select statement - private func makeSelectStatement(_ db: Database) throws -> SelectStatement { - // Build an SQK generation context with all aliases found in the query, - // so that we can disambiguate tables that are used several times with - // SQL aliases. - var context = SQLGenerationContext.queryGenerationContext(aliases: relation.allAliases) - - // Generate SQL - let sql = try self.sql(db, &context) - - // Compile & set arguments - let statement = try db.makeSelectStatement(sql: sql) - statement.arguments = context.arguments! // not nil for this kind of context - return statement - } - - /// Returns the row adapter which presents the fetched rows according to the - /// tree of joined relations. - /// - /// The adapter is nil for queries without any included relation, - /// because the fetched rows don't need any processing: - /// - /// // SELECT * FROM book - /// let request = Book.all() - /// for row in try Row.fetchAll(db, request) { - /// row // [id:1, title:"Moby-Dick"] - /// let book = Book(row: row) - /// } - /// - /// But as soon as the selection includes columns of a included relation, - /// we need an adapter: - /// - /// // SELECT book.*, author.* FROM book JOIN author ON author.id = book.authorId - /// let request = Book.including(required: Book.author) - /// for row in try Row.fetchAll(db, request) { - /// row // [id:1, title:"Moby-Dick"] - /// let book = Book(row: row) - /// - /// row.scopes["author"] // [id:12, name:"Herman Melville"] - /// let author: Author = row["author"] - /// } - private func rowAdapter(_ db: Database) throws -> RowAdapter? { - return try relation.rowAdapter(db, fromIndex: 0)?.adapter - } -} - -/// A "qualified" relation, where all tables are identified with a table alias. -/// -/// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... -/// | | | | | | -/// | | | | | • ordering -/// | | | | • filterPromise -/// | | | • joins -/// | | • alias -/// | • source -/// • fullSelection -private struct SQLQualifiedRelation { - /// The alias for the relation - /// - /// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... - /// | - /// • alias - let alias: TableAlias - - /// All aliases, including aliases of joined relations - var allAliases: [TableAlias] { - var aliases = [alias] - for join in joins.values { - aliases.append(contentsOf: join.relation.allAliases) - } - aliases.append(contentsOf: source.allAliases) - return aliases - } - - /// The source - /// - /// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... - /// | - /// • source - let source: SQLQualifiedSource - - /// The selection, not including selection of joined relations - private let ownSelection: [SQLSelectable] - - /// The full selection, including selection of joined relations - /// - /// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... - /// | - /// • fullSelection - var selection: [SQLSelectable] { - return joins.reduce(into: ownSelection) { - $0.append(contentsOf: $1.value.relation.selection) - } - } - - /// The filtering clause - /// - /// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... - /// | - /// • filterPromise - let filterPromise: DatabasePromise - - /// The ordering, not including ordering of joined relations - private let ownOrdering: SQLRelation.Ordering - - /// The full ordering, including orderings of joined relations - /// - /// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... - /// | - /// • ordering - var ordering: SQLRelation.Ordering { - return joins.reduce(ownOrdering) { - $0.appending($1.value.relation.ordering) - } - } - - /// The joins - /// - /// SELECT ... FROM ... AS ... JOIN ... WHERE ... ORDER BY ... - /// | - /// • joins - let joins: OrderedDictionary - - init(_ relation: SQLRelation) { - // Qualify the source, so that it be disambiguated with an SQL alias - // if needed (when a select query uses the same table several times). - // This disambiguation job will be actually performed by - // SQLGenerationContext, when the SQLSelectQueryGenerator which owns - // this SQLQualifiedRelation generates SQL. - source = SQLQualifiedSource(relation.source) - let alias = source.alias - self.alias = alias - - // Qualify all joins, selection, filter, and ordering, so that all - // identifiers can be correctly disambiguated and qualified. - joins = relation.children.compactMapValues { child -> SQLQualifiedJoin? in - let kind: SQLQualifiedJoin.Kind - switch child.kind { - case .oneRequired: - kind = .innerJoin - case .oneOptional: - kind = .leftJoin - case .allPrefetched, .allNotPrefetched: - // This relation child is not fetched with an SQL join. - return nil - } - - return SQLQualifiedJoin( - kind: kind, - condition: child.condition, - relation: SQLQualifiedRelation(child.relation)) - } - ownSelection = relation.selection.map { $0.qualifiedSelectable(with: alias) } - filterPromise = relation.filterPromise.map { [alias] in $0?.qualifiedExpression(with: alias) } - ownOrdering = relation.ordering.qualified(with: alias) - } - - /// See SQLQueryGenerator.rowAdapter(_:) - /// - /// - parameter db: A database connection. - /// - parameter startIndex: The index of the leftmost selected column of - /// this relation in a full SQL query. `startIndex` is 0 for the relation - /// at the root of a SQLQueryGenerator (as opposed to the - /// joined relations). - /// - returns: An optional tuple made of a RowAdapter and the index past the - /// rightmost selected column of this relation. Nil is returned if this - /// relations does not need any row adapter. - func rowAdapter(_ db: Database, fromIndex startIndex: Int) throws -> (adapter: RowAdapter, endIndex: Int)? { - // Root relation && no join => no need for any adapter - if startIndex == 0 && joins.isEmpty { - return nil - } - - // The number of columns in own selection. Columns selected by joined - // relations are appended after. - let selectionWidth = try ownSelection - .map { try $0.columnCount(db) } - .reduce(0, +) - - // Recursively build adapters for each joined relation with a selection. - // Name them according to the join keys. - var endIndex = startIndex + selectionWidth - var scopes: [String: RowAdapter] = [:] - for (key, join) in joins { - if let (joinAdapter, joinEndIndex) = try join.relation.rowAdapter(db, fromIndex: endIndex) { - scopes[key] = joinAdapter - endIndex = joinEndIndex - } - } - - // (Root relation || empty selection) && no included relation => no need for any adapter - if (startIndex == 0 || selectionWidth == 0) && scopes.isEmpty { - return nil - } - - // Build a RangeRowAdapter extended with the adapters of joined relations. - // - // // SELECT book.*, author.* FROM book JOIN author ON author.id = book.authorId - // let request = Book.including(required: Book.author) - // for row in try Row.fetchAll(db, request) { - // - // The RangeRowAdapter hides the columns appended by joined relations: - // - // row // [id:1, title:"Moby-Dick"] - // let book = Book(row: row) - // - // Scopes give access to those joined relations: - // - // row.scopes["author"] // [id:12, name:"Herman Melville"] - // let author: Author = row["author"] - // } - let rangeAdapter = RangeRowAdapter(startIndex ..< (startIndex + selectionWidth)) - let adapter = rangeAdapter.addingScopes(scopes) - - return (adapter: adapter, endIndex: endIndex) - } -} - -/// A "qualified" source, where all tables are identified with a table alias. -private enum SQLQualifiedSource { - case table(tableName: String, alias: TableAlias) - indirect case query(SQLQueryGenerator) - - var alias: TableAlias { - switch self { - case .table(_, let alias): - return alias - case .query(let query): - return query.relation.alias - } - } - - var allAliases: [TableAlias] { - switch self { - case .table(_, let alias): - return [alias] - case .query(let query): - return query.relation.allAliases - } - } - - init(_ source: SQLSource) { - switch source { - case .table(let tableName, let alias): - let alias = alias ?? TableAlias(tableName: tableName) - self = .table(tableName: tableName, alias: alias) - case .query(let query): - self = .query(SQLQueryGenerator(query)) - } - } - - func sql(_ db: Database, _ context: inout SQLGenerationContext) throws -> String { - switch self { - case .table(let tableName, let alias): - if let aliasName = context.aliasName(for: alias) { - return "\(tableName.quotedDatabaseIdentifier) \(aliasName.quotedDatabaseIdentifier)" - } else { - return "\(tableName.quotedDatabaseIdentifier)" - } - case .query(let query): - return try "(\(query.sql(db, &context)))" - } - } -} - -/// A "qualified" join, where all tables are identified with a table alias. -private struct SQLQualifiedJoin { - enum Kind: String { - case leftJoin = "LEFT JOIN" - case innerJoin = "JOIN" - } - let kind: Kind - let condition: SQLAssociationCondition - let relation: SQLQualifiedRelation - - func sql(_ db: Database,_ context: inout SQLGenerationContext, leftAlias: TableAlias) throws -> String { - return try sql(db, &context, leftAlias: leftAlias, allowingInnerJoin: true) - } - - private func sql(_ db: Database,_ context: inout SQLGenerationContext, leftAlias: TableAlias, allowingInnerJoin allowsInnerJoin: Bool) throws -> String { - var allowsInnerJoin = allowsInnerJoin - var sql = "" - - switch self.kind { - case .innerJoin: - guard allowsInnerJoin else { - // TODO: chainOptionalRequired - fatalError("Not implemented: chaining a required association behind an optional association") - } - case .leftJoin: - allowsInnerJoin = false - } - sql += kind.rawValue - sql += try " " + relation.source.sql(db, &context) - - let rightAlias = relation.alias - let filters = try [ - condition.joinExpression(db, leftAlias: leftAlias, rightAlias: rightAlias), - relation.filterPromise.resolve(db) - ].compactMap { $0 } - if !filters.isEmpty { - sql += " ON " + filters.joined(operator: .and).expressionSQL(&context) - } - - for (_, join) in relation.joins { - sql += try " " + join.sql(db, &context, leftAlias: rightAlias, allowingInnerJoin: allowsInnerJoin) - } - - return sql - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLInterpolation+QueryInterface.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLInterpolation+QueryInterface.swift deleted file mode 100755 index 1b16bed..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/SQLInterpolation+QueryInterface.swift +++ /dev/null @@ -1,149 +0,0 @@ -#if swift(>=5.0) -/// :nodoc: -extension SQLInterpolation { - /// Appends the table name of the record type. - /// - /// // SELECT * FROM player - /// let request: SQLRequest = "SELECT * FROM \(Player.self)" - public mutating func appendInterpolation(_ table: T.Type) { - sql += table.databaseTableName.quotedDatabaseIdentifier - } - - /// Appends the table name of the record. - /// - /// // INSERT INTO player ... - /// let player: Player = ... - /// let request: SQLRequest = "INSERT INTO \(tableOf: player) ..." - public mutating func appendInterpolation(tableOf record: T) { - sql += type(of: record).databaseTableName.quotedDatabaseIdentifier - } - - /// Appends the selectable SQL. - /// - /// // SELECT * FROM player - /// let request: SQLRequest = """ - /// SELECT \(AllColumns()) FROM player - /// """ - public mutating func appendInterpolation(_ selection: SQLSelectable) { - sql += selection.resultColumnSQL(&context) - } - - /// Appends the expression SQL. - /// - /// // SELECT name FROM player - /// let request: SQLRequest = """ - /// SELECT \(Column("name")) FROM player - /// """ - public mutating func appendInterpolation(_ expressible: SQLExpressible & SQLSelectable & SQLOrderingTerm) { - sql += expressible.sqlExpression.expressionSQL(&context) - } - - /// Appends the name of the coding key. - /// - /// // SELECT name FROM player - /// let request: SQLRequest = " - /// SELECT \(CodingKey.name) FROM player - /// """ - public mutating func appendInterpolation(_ codingKey: SQLExpressible & SQLSelectable & SQLOrderingTerm & CodingKey) { - sql += codingKey.sqlExpression.expressionSQL(&context) - } - - /// Appends the expression SQL, or NULL if it is nil. - /// - /// // SELECT score + ? FROM player - /// let bonus = 1000 - /// let request: SQLRequest = """ - /// SELECT score + \(bonus) FROM player - /// """ - public mutating func appendInterpolation(_ expressible: T?) { - if let expressible = expressible { - sql += expressible.sqlExpression.expressionSQL(&context) - } else { - sql += "NULL" - } - } - - /// Appends the name of the coding key. - /// - /// // SELECT name FROM player - /// let request: SQLRequest = """ - /// SELECT \(CodingKey.name) FROM player - /// """ - public mutating func appendInterpolation(_ codingKey: CodingKey) { - appendInterpolation(Column(codingKey.stringValue)) - } - - /// Appends a sequence of expressions, wrapped in parentheses. - /// - /// // SELECT * FROM player WHERE id IN (?,?,?) - /// let ids = [1, 2, 3] - /// let request: SQLRequest = """ - /// SELECT * FROM player WHERE id IN \(ids) - /// """ - /// - /// If the sequence is empty, an empty subquery is appended: - /// - /// // SELECT * FROM player WHERE id IN (SELECT NULL WHERE NULL) - /// let ids: [Int] = [] - /// let request: SQLRequest = """ - /// SELECT * FROM player WHERE id IN \(ids) - /// """ - public mutating func appendInterpolation(_ sequence: S) where S: Sequence, S.Element: SQLExpressible { - appendInterpolation(sequence.lazy.map { $0.sqlExpression }) - } - - /// Appends a sequence of expressions, wrapped in parentheses. - /// - /// // SELECT * FROM player WHERE a IN (b, c + 2) - /// let expressions = [Column("b"), Column("c") + 2] - /// let request: SQLRequest = """ - /// SELECT * FROM player WHERE a IN \(expressions) - /// """ - /// - /// If the sequence is empty, an empty subquery is appended: - /// - /// // SELECT * FROM player WHERE a IN (SELECT NULL WHERE NULL) - /// let expressions: [SQLExpression] = [] - /// let request: SQLRequest = """ - /// SELECT * FROM player WHERE a IN \(expressions) - /// """ - public mutating func appendInterpolation(_ sequence: S) where S: Sequence, S.Element == SQLExpression { - sql += "(" - var first = true - for element in sequence { - if first { - first = false - } else { - sql += "," - } - appendInterpolation(element) - } - if first { - sql += "SELECT NULL WHERE NULL" - } - sql += ")" - } - - /// Appends the ordering SQL. - /// - /// // SELECT name FROM player ORDER BY name DESC - /// let request: SQLRequest = """ - /// SELECT * FROM player ORDER BY \(Column("name").desc) - /// """ - public mutating func appendInterpolation(_ ordering: SQLOrderingTerm) { - sql += ordering.orderingTermSQL(&context) - } - - /// Appends the request SQL, wrapped in parentheses - /// - /// // SELECT name FROM player WHERE score = (SELECT MAX(score) FROM player) - /// let subQuery: SQLRequest = "SELECT MAX(score) FROM player" - /// let request: SQLRequest = """ - /// SELECT * FROM player WHERE score = \(subQuery) - /// """ - public mutating func appendInterpolation(_ request: SQLRequest) { - sql += "(" + request.sql + ")" - arguments += request.arguments - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Schema/TableDefinition.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Schema/TableDefinition.swift deleted file mode 100755 index d6203ca..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Schema/TableDefinition.swift +++ /dev/null @@ -1,950 +0,0 @@ -extension Database { - - // MARK: - Database Schema - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - /// Creates a database table. - /// - /// try db.create(table: "place") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("title", .text) - /// t.column("favorite", .boolean).notNull().default(false) - /// t.column("longitude", .double).notNull() - /// t.column("latitude", .double).notNull() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html and - /// https://www.sqlite.org/withoutrowid.html - /// - /// - parameters: - /// - name: The table name. - /// - temporary: If true, creates a temporary table. - /// - ifNotExists: If false (the default), an error is thrown if the - /// table already exists. Otherwise, the table is created unless it - /// already exists. - /// - withoutRowID: If true, uses WITHOUT ROWID optimization. - /// - body: A closure that defines table columns and constraints. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func create(table name: String, temporary: Bool = false, ifNotExists: Bool = false, withoutRowID: Bool = false, body: (TableDefinition) -> Void) throws { - let definition = TableDefinition(name: name, temporary: temporary, ifNotExists: ifNotExists, withoutRowID: withoutRowID) - body(definition) - let sql = try definition.sql(self) - try execute(sql: sql) - } - #else - /// Creates a database table. - /// - /// try db.create(table: "place") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("title", .text) - /// t.column("favorite", .boolean).notNull().default(false) - /// t.column("longitude", .double).notNull() - /// t.column("latitude", .double).notNull() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html and - /// https://www.sqlite.org/withoutrowid.html - /// - /// - parameters: - /// - name: The table name. - /// - temporary: If true, creates a temporary table. - /// - ifNotExists: If false (the default), an error is thrown if the - /// table already exists. Otherwise, the table is created unless it - /// already exists. - /// - withoutRowID: If true, uses WITHOUT ROWID optimization. - /// - body: A closure that defines table columns and constraints. - /// - throws: A DatabaseError whenever an SQLite error occurs. - @available(OSX 10.10, *) - public func create(table name: String, temporary: Bool = false, ifNotExists: Bool = false, withoutRowID: Bool, body: (TableDefinition) -> Void) throws { - // WITHOUT ROWID was added in SQLite 3.8.2 http://www.sqlite.org/changes.html#version_3_8_2 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - let definition = TableDefinition(name: name, temporary: temporary, ifNotExists: ifNotExists, withoutRowID: withoutRowID) - body(definition) - let sql = try definition.sql(self) - try execute(sql: sql) - } - - /// Creates a database table. - /// - /// try db.create(table: "place") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("title", .text) - /// t.column("favorite", .boolean).notNull().default(false) - /// t.column("longitude", .double).notNull() - /// t.column("latitude", .double).notNull() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html - /// - /// - parameters: - /// - name: The table name. - /// - temporary: If true, creates a temporary table. - /// - ifNotExists: If false (the default), an error is thrown if the - /// table already exists. Otherwise, the table is created unless it - /// already exists. - /// - body: A closure that defines table columns and constraints. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func create(table name: String, temporary: Bool = false, ifNotExists: Bool = false, body: (TableDefinition) -> Void) throws { - let definition = TableDefinition(name: name, temporary: temporary, ifNotExists: ifNotExists, withoutRowID: false) - body(definition) - let sql = try definition.sql(self) - try execute(sql: sql) - } - #endif - - /// Renames a database table. - /// - /// See https://www.sqlite.org/lang_altertable.html - /// - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func rename(table name: String, to newName: String) throws { - try execute(sql: "ALTER TABLE \(name.quotedDatabaseIdentifier) RENAME TO \(newName.quotedDatabaseIdentifier)") - } - - /// Modifies a database table. - /// - /// try db.alter(table: "player") { t in - /// t.add(column: "url", .text) - /// } - /// - /// See https://www.sqlite.org/lang_altertable.html - /// - /// - parameters: - /// - name: The table name. - /// - body: A closure that defines table alterations. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func alter(table name: String, body: (TableAlteration) -> Void) throws { - let alteration = TableAlteration(name: name) - body(alteration) - let sql = try alteration.sql(self) - try execute(sql: sql) - } - - /// Deletes a database table. - /// - /// See https://www.sqlite.org/lang_droptable.html - /// - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func drop(table name: String) throws { - try execute(sql: "DROP TABLE \(name.quotedDatabaseIdentifier)") - } - - #if GRDBCUSTOMSQLITE || GRDBCIPHER - /// Creates an index. - /// - /// try db.create(index: "playerByEmail", on: "player", columns: ["email"]) - /// - /// SQLite can also index expressions (https://www.sqlite.org/expridx.html) - /// and use specific collations. To create such an index, use a raw SQL - /// query. - /// - /// try db.execute(sql: "CREATE INDEX ...") - /// - /// See https://www.sqlite.org/lang_createindex.html - /// - /// - parameters: - /// - name: The index name. - /// - table: The name of the indexed table. - /// - columns: The indexed columns. - /// - unique: If true, creates a unique index. - /// - ifNotExists: If false, no error is thrown if index already exists. - /// - condition: If not nil, creates a partial index - /// (see https://www.sqlite.org/partialindex.html). - public func create(index name: String, on table: String, columns: [String], unique: Bool = false, ifNotExists: Bool = false, condition: SQLExpressible? = nil) throws { - // Partial indexes were introduced in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - let definition = IndexDefinition(name: name, table: table, columns: columns, unique: unique, ifNotExists: ifNotExists, condition: condition?.sqlExpression) - let sql = definition.sql() - try execute(sql: sql) - } - #else - /// Creates an index. - /// - /// try db.create(index: "playerByEmail", on: "player", columns: ["email"]) - /// - /// SQLite can also index expressions (https://www.sqlite.org/expridx.html) - /// and use specific collations. To create such an index, use a raw SQL - /// query. - /// - /// try db.execute(sql: "CREATE INDEX ...") - /// - /// See https://www.sqlite.org/lang_createindex.html - /// - /// - parameters: - /// - name: The index name. - /// - table: The name of the indexed table. - /// - columns: The indexed columns. - /// - unique: If true, creates a unique index. - /// - ifNotExists: If false, no error is thrown if index already exists. - public func create(index name: String, on table: String, columns: [String], unique: Bool = false, ifNotExists: Bool = false) throws { - // Partial indexes were introduced in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - let definition = IndexDefinition(name: name, table: table, columns: columns, unique: unique, ifNotExists: ifNotExists, condition: nil) - let sql = definition.sql() - try execute(sql: sql) - } - - /// Creates a partial index. - /// - /// try db.create(index: "playerByEmail", on: "player", columns: ["email"], condition: Column("email") != nil) - /// - /// See https://www.sqlite.org/lang_createindex.html, and - /// https://www.sqlite.org/partialindex.html - /// - /// - parameters: - /// - name: The index name. - /// - table: The name of the indexed table. - /// - columns: The indexed columns. - /// - unique: If true, creates a unique index. - /// - ifNotExists: If false, no error is thrown if index already exists. - /// - condition: The condition that indexed rows must verify. - @available(OSX 10.10, *) - public func create(index name: String, on table: String, columns: [String], unique: Bool = false, ifNotExists: Bool = false, condition: SQLExpressible) throws { - // Partial indexes were introduced in SQLite 3.8.0 http://www.sqlite.org/changes.html#version_3_8_0 - // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS) - let definition = IndexDefinition(name: name, table: table, columns: columns, unique: unique, ifNotExists: ifNotExists, condition: condition.sqlExpression) - let sql = definition.sql() - try execute(sql: sql) - } - #endif - - /// Deletes a database index. - /// - /// See https://www.sqlite.org/lang_dropindex.html - /// - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func drop(index name: String) throws { - try execute(sql: "DROP INDEX \(name.quotedDatabaseIdentifier)") - } - - /// Delete and recreate from scratch all indices that use this collation. - /// - /// This method is useful when the definition of a collation sequence - /// has changed. - /// - /// See https://www.sqlite.org/lang_reindex.html - /// - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func reindex(collation: Database.CollationName) throws { - try execute(sql: "REINDEX \(collation.rawValue)") - } - - /// Delete and recreate from scratch all indices that use this collation. - /// - /// This method is useful when the definition of a collation sequence - /// has changed. - /// - /// See https://www.sqlite.org/lang_reindex.html - /// - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func reindex(collation: DatabaseCollation) throws { - try reindex(collation: Database.CollationName(collation.name)) - } -} - -/// The TableDefinition class lets you define table columns and constraints. -/// -/// You don't create instances of this class. Instead, you use the Database -/// `create(table:)` method: -/// -/// try db.create(table: "player") { t in // t is TableDefinition -/// t.column(...) -/// } -/// -/// See https://www.sqlite.org/lang_createtable.html -public final class TableDefinition { - private typealias KeyConstraint = (columns: [String], conflictResolution: Database.ConflictResolution?) - - private let name: String - private let temporary: Bool - private let ifNotExists: Bool - private let withoutRowID: Bool - private var columns: [ColumnDefinition] = [] - private var primaryKeyConstraint: KeyConstraint? - private var uniqueKeyConstraints: [KeyConstraint] = [] - private var foreignKeyConstraints: [(columns: [String], table: String, destinationColumns: [String]?, deleteAction: Database.ForeignKeyAction?, updateAction: Database.ForeignKeyAction?, deferred: Bool)] = [] - private var checkConstraints: [SQLExpression] = [] - - init(name: String, temporary: Bool, ifNotExists: Bool, withoutRowID: Bool) { - self.name = name - self.temporary = temporary - self.ifNotExists = ifNotExists - self.withoutRowID = withoutRowID - } - - /// Defines the auto-incremented primary key. - /// - /// try db.create(table: "player") { t in - /// t.autoIncrementedPrimaryKey("id") - /// } - /// - /// The auto-incremented primary key is an integer primary key that - /// automatically generates unused values when you do not explicitly - /// provide one, and prevents the reuse of ids over the lifetime of - /// the database. - /// - /// **It is the preferred way to define a numeric primary key**. - /// - /// The fact that an auto-incremented primary key prevents the reuse of - /// ids is an excellent guard against data races that could happen when your - /// application processes ids in an asynchronous way. The auto-incremented - /// primary key provides the guarantee that a given id can't reference a row - /// that is different from the one it used to be at the beginning of the - /// asynchronous process, even if this row gets deleted and a new one is - /// inserted in between. - /// - /// See https://www.sqlite.org/lang_createtable.html#primkeyconst and - /// https://www.sqlite.org/lang_createtable.html#rowid - /// - /// - parameter conflictResolution: An optional conflict resolution - /// (see https://www.sqlite.org/lang_conflict.html). - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func autoIncrementedPrimaryKey(_ name: String, onConflict conflictResolution: Database.ConflictResolution? = nil) -> ColumnDefinition { - return column(name, .integer).primaryKey(onConflict: conflictResolution, autoincrement: true) - } - - /// Appends a table column. - /// - /// try db.create(table: "player") { t in - /// t.column("name", .text) - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#tablecoldef - /// - /// - parameter name: the column name. - /// - parameter type: the eventual column type. - /// - returns: An ColumnDefinition that allows you to refine the - /// column definition. - @discardableResult - public func column(_ name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition { - let column = ColumnDefinition(name: name, type: type) - columns.append(column) - return column - } - - /// Defines the table primary key. - /// - /// try db.create(table: "citizenship") { t in - /// t.column("citizenID", .integer) - /// t.column("countryCode", .text) - /// t.primaryKey(["citizenID", "countryCode"]) - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#primkeyconst and - /// https://www.sqlite.org/lang_createtable.html#rowid - /// - /// - parameter columns: The primary key columns. - /// - parameter conflictResolution: An optional conflict resolution - /// (see https://www.sqlite.org/lang_conflict.html). - public func primaryKey(_ columns: [String], onConflict conflictResolution: Database.ConflictResolution? = nil) { - guard primaryKeyConstraint == nil else { - // Programmer error - fatalError("can't define several primary keys") - } - primaryKeyConstraint = (columns: columns, conflictResolution: conflictResolution) - } - - /// Adds a unique key. - /// - /// try db.create(table: "place") { t in - /// t.column("latitude", .double) - /// t.column("longitude", .double) - /// t.uniqueKey(["latitude", "longitude"]) - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#uniqueconst - /// - /// - parameter columns: The unique key columns. - /// - parameter conflictResolution: An optional conflict resolution - /// (see https://www.sqlite.org/lang_conflict.html). - public func uniqueKey(_ columns: [String], onConflict conflictResolution: Database.ConflictResolution? = nil) { - uniqueKeyConstraints.append((columns: columns, conflictResolution: conflictResolution)) - } - - /// Adds a foreign key. - /// - /// try db.create(table: "passport") { t in - /// t.column("issueDate", .date) - /// t.column("citizenID", .integer) - /// t.column("countryCode", .text) - /// t.foreignKey(["citizenID", "countryCode"], references: "citizenship", onDelete: .cascade) - /// } - /// - /// See https://www.sqlite.org/foreignkeys.html - /// - /// - parameters: - /// - columns: The foreign key columns. - /// - table: The referenced table. - /// - destinationColumns: The columns in the referenced table. If not - /// specified, the columns of the primary key of the referenced table - /// are used. - /// - deleteAction: Optional action when the referenced row is deleted. - /// - updateAction: Optional action when the referenced row is updated. - /// - deferred: If true, defines a deferred foreign key constraint. - /// See https://www.sqlite.org/foreignkeys.html#fk_deferred. - public func foreignKey(_ columns: [String], references table: String, columns destinationColumns: [String]? = nil, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred: Bool = false) { - foreignKeyConstraints.append((columns: columns, table: table, destinationColumns: destinationColumns, deleteAction: deleteAction, updateAction: updateAction, deferred: deferred)) - } - - /// Adds a CHECK constraint. - /// - /// try db.create(table: "player") { t in - /// t.column("personalPhone", .text) - /// t.column("workPhone", .text) - /// let personalPhone = Column("personalPhone") - /// let workPhone = Column("workPhone") - /// t.check(personalPhone != nil || workPhone != nil) - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#ckconst - /// - /// - parameter condition: The checked condition - public func check(_ condition: SQLExpressible) { - checkConstraints.append(condition.sqlExpression) - } - - /// Adds a CHECK constraint. - /// - /// try db.create(table: "player") { t in - /// t.column("personalPhone", .text) - /// t.column("workPhone", .text) - /// t.check(sql: "personalPhone IS NOT NULL OR workPhone IS NOT NULL") - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#ckconst - /// - /// - parameter sql: An SQL snippet - public func check(sql: String) { - // We do not want to wrap the SQL snippet inside parentheses around the - // checked SQL. This is why we use the "unsafeLiteral" initializer. - checkConstraints.append(SQLExpressionLiteral(unsafeLiteral: SQLLiteral(sql: sql))) - } - - fileprivate func sql(_ db: Database) throws -> String { - var statements: [String] = [] - - do { - var chunks: [String] = [] - chunks.append("CREATE") - if temporary { - chunks.append("TEMPORARY") - } - chunks.append("TABLE") - if ifNotExists { - chunks.append("IF NOT EXISTS") - } - chunks.append(name.quotedDatabaseIdentifier) - - let primaryKeyColumns: [String] - if let (columns, _) = primaryKeyConstraint { - primaryKeyColumns = columns - } else if let index = columns.firstIndex(where: { $0.primaryKey != nil }) { - primaryKeyColumns = [columns[index].name] - } else { - // WITHOUT ROWID optimization requires a primary key. If the - // user sets withoutRowID, but does not define a primary key, - // this is undefined behavior. - // - // We thus can use the rowId column even when the withoutRowID - // flag is set ;-) - primaryKeyColumns = [Column.rowID.name] - } - - do { - var items: [String] = [] - try items.append(contentsOf: columns.map { try $0.sql(db, tableName: name, primaryKeyColumns: primaryKeyColumns) }) - - if let (columns, conflictResolution) = primaryKeyConstraint { - var chunks: [String] = [] - chunks.append("PRIMARY KEY") - chunks.append("(\((columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - if let conflictResolution = conflictResolution { - chunks.append("ON CONFLICT") - chunks.append(conflictResolution.rawValue) - } - items.append(chunks.joined(separator: " ")) - } - - for (columns, conflictResolution) in uniqueKeyConstraints { - var chunks: [String] = [] - chunks.append("UNIQUE") - chunks.append("(\((columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - if let conflictResolution = conflictResolution { - chunks.append("ON CONFLICT") - chunks.append(conflictResolution.rawValue) - } - items.append(chunks.joined(separator: " ")) - } - - for (columns, table, destinationColumns, deleteAction, updateAction, deferred) in foreignKeyConstraints { - var chunks: [String] = [] - chunks.append("FOREIGN KEY") - chunks.append("(\((columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - chunks.append("REFERENCES") - if let destinationColumns = destinationColumns { - chunks.append("\(table.quotedDatabaseIdentifier)(\((destinationColumns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - } else if table == name { - chunks.append("\(table.quotedDatabaseIdentifier)(\((primaryKeyColumns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - } else { - let primaryKey = try db.primaryKey(table) - chunks.append("\(table.quotedDatabaseIdentifier)(\((primaryKey.columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - } - if let deleteAction = deleteAction { - chunks.append("ON DELETE") - chunks.append(deleteAction.rawValue) - } - if let updateAction = updateAction { - chunks.append("ON UPDATE") - chunks.append(updateAction.rawValue) - } - if deferred { - chunks.append("DEFERRABLE INITIALLY DEFERRED") - } - items.append(chunks.joined(separator: " ")) - } - - for checkExpression in checkConstraints { - var chunks: [String] = [] - chunks.append("CHECK") - chunks.append("(" + checkExpression.quotedSQL() + ")") - items.append(chunks.joined(separator: " ")) - } - - chunks.append("(\(items.joined(separator: ", ")))") - } - - if withoutRowID { - chunks.append("WITHOUT ROWID") - } - statements.append(chunks.joined(separator: " ")) - } - - let indexStatements = columns - .compactMap { $0.indexDefinition(in: name) } - .map { $0.sql() } - statements.append(contentsOf: indexStatements) - return statements.joined(separator: "; ") - } -} - -/// The TableAlteration class lets you alter database tables. -/// -/// You don't create instances of this class. Instead, you use the Database -/// `alter(table:)` method: -/// -/// try db.alter(table: "player") { t in // t is TableAlteration -/// t.add(column: ...) -/// } -/// -/// See https://www.sqlite.org/lang_altertable.html -public final class TableAlteration { - private let name: String - private var addedColumns: [ColumnDefinition] = [] - - init(name: String) { - self.name = name - } - - /// Appends a column to the table. - /// - /// try db.alter(table: "player") { t in - /// t.add(column: "url", .text) - /// } - /// - /// See https://www.sqlite.org/lang_altertable.html - /// - /// - parameter name: the column name. - /// - parameter type: the column type. - /// - returns: An ColumnDefinition that allows you to refine the - /// column definition. - @discardableResult - public func add(column name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition { - let column = ColumnDefinition(name: name, type: type) - addedColumns.append(column) - return column - } - - fileprivate func sql(_ db: Database) throws -> String { - var statements: [String] = [] - - for column in addedColumns { - var chunks: [String] = [] - chunks.append("ALTER TABLE") - chunks.append(name.quotedDatabaseIdentifier) - chunks.append("ADD COLUMN") - try chunks.append(column.sql(db, tableName: name, primaryKeyColumns: nil)) - let statement = chunks.joined(separator: " ") - statements.append(statement) - - if let indexDefinition = column.indexDefinition(in: name) { - statements.append(indexDefinition.sql()) - } - } - - return statements.joined(separator: "; ") - } -} - -/// The ColumnDefinition class lets you refine a table column. -/// -/// You get instances of this class when you create or alter a database table: -/// -/// try db.create(table: "player") { t in -/// t.column(...) // ColumnDefinition -/// } -/// -/// try db.alter(table: "player") { t in -/// t.add(column: ...) // ColumnDefinition -/// } -/// -/// See https://www.sqlite.org/lang_createtable.html and -/// https://www.sqlite.org/lang_altertable.html -public final class ColumnDefinition { - enum Index { - case none - case index - case unique(Database.ConflictResolution) - } - fileprivate let name: String - private let type: Database.ColumnType? - fileprivate var primaryKey: (conflictResolution: Database.ConflictResolution?, autoincrement: Bool)? - private var index: Index = .none - private var notNullConflictResolution: Database.ConflictResolution? - private var checkConstraints: [SQLExpression] = [] - private var foreignKeyConstraints: [(table: String, column: String?, deleteAction: Database.ForeignKeyAction?, updateAction: Database.ForeignKeyAction?, deferred: Bool)] = [] - private var defaultExpression: SQLExpression? - private var collationName: String? - - init(name: String, type: Database.ColumnType?) { - self.name = name - self.type = type - } - - /// Adds a primary key constraint on the column. - /// - /// try db.create(table: "player") { t in - /// t.column("id", .integer).primaryKey() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#primkeyconst and - /// https://www.sqlite.org/lang_createtable.html#rowid - /// - /// - parameters: - /// - conflictResolution: An optional conflict resolution - /// (see https://www.sqlite.org/lang_conflict.html). - /// - autoincrement: If true, the primary key is autoincremented. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func primaryKey(onConflict conflictResolution: Database.ConflictResolution? = nil, autoincrement: Bool = false) -> Self { - primaryKey = (conflictResolution: conflictResolution, autoincrement: autoincrement) - return self - } - - /// Adds a NOT NULL constraint on the column. - /// - /// try db.create(table: "player") { t in - /// t.column("name", .text).notNull() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#notnullconst - /// - /// - parameter conflictResolution: An optional conflict resolution - /// (see https://www.sqlite.org/lang_conflict.html). - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func notNull(onConflict conflictResolution: Database.ConflictResolution? = nil) -> Self { - notNullConflictResolution = conflictResolution ?? .abort - return self - } - - /// Adds a UNIQUE constraint on the column. - /// - /// try db.create(table: "player") { t in - /// t.column("email", .text).unique() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#uniqueconst - /// - /// - parameter conflictResolution: An optional conflict resolution - /// (see https://www.sqlite.org/lang_conflict.html). - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func unique(onConflict conflictResolution: Database.ConflictResolution? = nil) -> Self { - index = .unique(conflictResolution ?? .abort) - return self - } - - /// Adds an index of the column. - /// - /// try db.create(table: "player") { t in - /// t.column("email", .text).indexed() - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#uniqueconst - /// - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func indexed() -> Self { - if case .none = index { - self.index = .index - } - return self - } - - /// Adds a CHECK constraint on the column. - /// - /// try db.create(table: "player") { t in - /// t.column("name", .text).check { length($0) > 0 } - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#ckconst - /// - /// - parameter condition: A closure whose argument is an Column that - /// represents the defined column, and returns the expression to check. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func check(_ condition: (Column) -> SQLExpressible) -> Self { - checkConstraints.append(condition(Column(name)).sqlExpression) - return self - } - - /// Adds a CHECK constraint on the column. - /// - /// try db.create(table: "player") { t in - /// t.column("name", .text).check(sql: "LENGTH(name) > 0") - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#ckconst - /// - /// - parameter sql: An SQL snippet. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func check(sql: String) -> Self { - // We do not want to wrap the SQL snippet inside parentheses around the - // checked SQL. This is why we use the "unsafeLiteral" initializer. - checkConstraints.append(SQLExpressionLiteral(unsafeLiteral: SQLLiteral(sql: sql))) - return self - } - - /// Defines the default column value. - /// - /// try db.create(table: "player") { t in - /// t.column("name", .text).defaults(to: "Anonymous") - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#dfltval - /// - /// - parameter value: A DatabaseValueConvertible value. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func defaults(to value: DatabaseValueConvertible) -> Self { - defaultExpression = value.sqlExpression - return self - } - - /// Defines the default column value. - /// - /// try db.create(table: "player") { t in - /// t.column("creationDate", .DateTime).defaults(sql: "CURRENT_TIMESTAMP") - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html#dfltval - /// - /// - parameter sql: An SQL snippet. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func defaults(sql: String) -> Self { - // We do not want to wrap the SQL snippet inside parentheses around the - // checked SQL. This is why we use the "unsafeLiteral" initializer. - defaultExpression = SQLExpressionLiteral(unsafeLiteral: SQLLiteral(sql: sql)) - return self - } - - // Defines the default column collation. - /// - /// try db.create(table: "player") { t in - /// t.column("email", .text).collate(.nocase) - /// } - /// - /// See https://www.sqlite.org/datatype3.html#collation - /// - /// - parameter collation: An Database.CollationName. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func collate(_ collation: Database.CollationName) -> Self { - collationName = collation.rawValue - return self - } - - // Defines the default column collation. - /// - /// try db.create(table: "player") { t in - /// t.column("name", .text).collate(.localizedCaseInsensitiveCompare) - /// } - /// - /// See https://www.sqlite.org/datatype3.html#collation - /// - /// - parameter collation: A custom DatabaseCollation. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func collate(_ collation: DatabaseCollation) -> Self { - collationName = collation.name - return self - } - - /// Defines a foreign key. - /// - /// try db.create(table: "book") { t in - /// t.column("authorId", .integer).references("author", onDelete: .cascade) - /// } - /// - /// See https://www.sqlite.org/foreignkeys.html - /// - /// - parameters - /// - table: The referenced table. - /// - column: The column in the referenced table. If not specified, the - /// column of the primary key of the referenced table is used. - /// - deleteAction: Optional action when the referenced row is deleted. - /// - updateAction: Optional action when the referenced row is updated. - /// - deferred: If true, defines a deferred foreign key constraint. - /// See https://www.sqlite.org/foreignkeys.html#fk_deferred. - /// - returns: Self so that you can further refine the column definition. - @discardableResult - public func references(_ table: String, column: String? = nil, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred: Bool = false) -> Self { - foreignKeyConstraints.append((table: table, column: column, deleteAction: deleteAction, updateAction: updateAction, deferred: deferred)) - return self - } - - fileprivate func sql(_ db: Database, tableName: String, primaryKeyColumns: [String]?) throws -> String { - var chunks: [String] = [] - chunks.append(name.quotedDatabaseIdentifier) - if let type = type { - chunks.append(type.rawValue) - } - - if let (conflictResolution, autoincrement) = primaryKey { - chunks.append("PRIMARY KEY") - if let conflictResolution = conflictResolution { - chunks.append("ON CONFLICT") - chunks.append(conflictResolution.rawValue) - } - if autoincrement { - chunks.append("AUTOINCREMENT") - } - } - - switch notNullConflictResolution { - case .none: - break - case .abort?: - chunks.append("NOT NULL") - case let conflictResolution?: - chunks.append("NOT NULL ON CONFLICT") - chunks.append(conflictResolution.rawValue) - } - - switch index { - case .none: - break - case .unique(let conflictResolution): - switch conflictResolution { - case .abort: - chunks.append("UNIQUE") - default: - chunks.append("UNIQUE ON CONFLICT") - chunks.append(conflictResolution.rawValue) - } - case .index: - break - } - - for checkConstraint in checkConstraints { - chunks.append("CHECK") - chunks.append("(" + checkConstraint.quotedSQL() + ")") - } - - if let defaultExpression = defaultExpression { - chunks.append("DEFAULT") - chunks.append(defaultExpression.quotedSQL()) - } - - if let collationName = collationName { - chunks.append("COLLATE") - chunks.append(collationName) - } - - for (table, column, deleteAction, updateAction, deferred) in foreignKeyConstraints { - chunks.append("REFERENCES") - if let column = column { - // explicit reference - chunks.append("\(table.quotedDatabaseIdentifier)(\(column.quotedDatabaseIdentifier))") - } else if table.lowercased() == tableName.lowercased() { - // implicit autoreference - let primaryKeyColumns = try primaryKeyColumns ?? db.primaryKey(table).columns - chunks.append("\(table.quotedDatabaseIdentifier)(\((primaryKeyColumns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - } else { - // implicit external reference - let primaryKeyColumns = try db.primaryKey(table).columns - chunks.append("\(table.quotedDatabaseIdentifier)(\((primaryKeyColumns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - } - if let deleteAction = deleteAction { - chunks.append("ON DELETE") - chunks.append(deleteAction.rawValue) - } - if let updateAction = updateAction { - chunks.append("ON UPDATE") - chunks.append(updateAction.rawValue) - } - if deferred { - chunks.append("DEFERRABLE INITIALLY DEFERRED") - } - } - - return chunks.joined(separator: " ") - } - - fileprivate func indexDefinition(in table: String) -> IndexDefinition? { - switch index { - case .none: return nil - case .unique: return nil - case .index: - return IndexDefinition( - name: "\(table)_on_\(name)", - table: table, - columns: [name], - unique: false, - ifNotExists: false, - condition: nil) - } - } -} - -private struct IndexDefinition { - let name: String - let table: String - let columns: [String] - let unique: Bool - let ifNotExists: Bool - let condition: SQLExpression? - - func sql() -> String { - var chunks: [String] = [] - chunks.append("CREATE") - if unique { - chunks.append("UNIQUE") - } - chunks.append("INDEX") - if ifNotExists { - chunks.append("IF NOT EXISTS") - } - chunks.append(name.quotedDatabaseIdentifier) - chunks.append("ON") - chunks.append("\(table.quotedDatabaseIdentifier)(\((columns.map { $0.quotedDatabaseIdentifier } as [String]).joined(separator: ", ")))") - if let condition = condition { - chunks.append("WHERE") - chunks.append(condition.quotedSQL()) - } - return chunks.joined(separator: " ") - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Schema/VirtualTableModule.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/Schema/VirtualTableModule.swift deleted file mode 100755 index 06eaa3e..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/Schema/VirtualTableModule.swift +++ /dev/null @@ -1,128 +0,0 @@ -/// The protocol for SQLite virtual table modules. It lets you define a DSL for -/// the `Database.create(virtualTable:using:)` method: -/// -/// let module = ... -/// try db.create(virtualTable: "item", using: module) { t in -/// ... -/// } -/// -/// GRDB ships with three concrete classes that implement this protocol: FTS3, -/// FTS4 and FTS5. -public protocol VirtualTableModule { - - /// The type of the closure argument in the - /// `Database.create(virtualTable:using:)` method: - /// - /// try db.create(virtualTable: "item", using: module) { t in - /// // t is TableDefinition - /// } - associatedtype TableDefinition - - /// The name of the module. - var moduleName: String { get } - - /// Returns a table definition that is passed as the closure argument in the - /// `Database.create(virtualTable:using:)` method: - /// - /// try db.create(virtualTable: "item", using: module) { t in - /// // t is the result of makeTableDefinition() - /// } - func makeTableDefinition() -> TableDefinition - - /// Returns the module arguments for the `CREATE VIRTUAL TABLE` query. - func moduleArguments(for definition: TableDefinition, in db: Database) throws -> [String] - - /// Execute any relevant database statement after the virtual table has - /// been created. - func database(_ db: Database, didCreate tableName: String, using definition: TableDefinition) throws -} - -extension Database { - - // MARK: - Database Schema - - /// Creates a virtual database table. - /// - /// try db.create(virtualTable: "vocabulary", using: "spellfix1") - /// - /// See https://www.sqlite.org/lang_createtable.html - /// - /// - parameters: - /// - name: The table name. - /// - ifNotExists: If false (the default), an error is thrown if the - /// table already exists. Otherwise, the table is created unless it - /// already exists. - /// - module: The name of an SQLite virtual table module. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func create(virtualTable name: String, ifNotExists: Bool = false, using module: String) throws { - var chunks: [String] = [] - chunks.append("CREATE VIRTUAL TABLE") - if ifNotExists { - chunks.append("IF NOT EXISTS") - } - chunks.append(name.quotedDatabaseIdentifier) - chunks.append("USING") - chunks.append(module) - let sql = chunks.joined(separator: " ") - try execute(sql: sql) - } - - /// Creates a virtual database table. - /// - /// let module = ... - /// try db.create(virtualTable: "book", using: module) { t in - /// ... - /// } - /// - /// The type of the closure argument `t` depends on the type of the module - /// argument: refer to this module's documentation. - /// - /// Use this method to create full-text tables using the FTS3, FTS4, or - /// FTS5 modules: - /// - /// try db.create(virtualTable: "book", using: FTS4()) { t in - /// t.column("title") - /// t.column("author") - /// t.column("body") - /// } - /// - /// See https://www.sqlite.org/lang_createtable.html - /// - /// - parameters: - /// - name: The table name. - /// - ifNotExists: If false (the default), an error is thrown if the - /// table already exists. Otherwise, the table is created unless it - /// already exists. - /// - module: a VirtualTableModule - /// - body: An optional closure that defines the virtual table. - /// - throws: A DatabaseError whenever an SQLite error occurs. - public func create(virtualTable tableName: String, ifNotExists: Bool = false, using module: Module, _ body: ((Module.TableDefinition) -> Void)? = nil) throws { - // Define virtual table - let definition = module.makeTableDefinition() - if let body = body { - body(definition) - } - - // Create virtual table - var chunks: [String] = [] - chunks.append("CREATE VIRTUAL TABLE") - if ifNotExists { - chunks.append("IF NOT EXISTS") - } - chunks.append(tableName.quotedDatabaseIdentifier) - chunks.append("USING") - let arguments = try module.moduleArguments(for: definition, in: self) - if arguments.isEmpty { - chunks.append(module.moduleName) - } else { - chunks.append(module.moduleName + "(" + arguments.joined(separator: ", ") + ")") - } - let sql = chunks.joined(separator: " ") - - try inSavepoint { - try execute(sql: sql) - try module.database(self, didCreate: tableName, using: definition) - return .commit - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/TableRecord+Association.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/TableRecord+Association.swift deleted file mode 100755 index 5ea8bf5..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/TableRecord+Association.swift +++ /dev/null @@ -1,627 +0,0 @@ -extension TableRecord { - /// Creates a "Belongs To" association between Self and the - /// destination type. - /// - /// struct Author: TableRecord { ... } - /// struct Book: TableRecord { - /// static let author = belongsTo(Author.self) - /// } - /// - /// The association will let you define requests that load both the source - /// and the destination type: - /// - /// // A request for all books with their authors: - /// let request = Book.including(optional: Book.author) - /// - /// To consume those requests, define a type that adopts both the - /// FetchableRecord and Decodable protocols: - /// - /// struct BookInfo: FetchableRecord, Decodable { - /// var book: Book - /// var author: Author? - /// } - /// - /// let bookInfos = try dbQueue.read { db in - /// return try BookInfo.fetchAll(db, request) - /// } - /// for bookInfo in bookInfos { - /// print("\(bookInfo.book.title) by \(bookInfo.author.name)") - /// } - /// - /// It is recommended that you define, alongside the static association, a - /// property with the same name: - /// - /// struct Book: TableRecord, EncodableRecord { - /// static let author = belongsTo(Author.self) - /// var author: QueryInterfaceRequest { - /// return request(for: Book.author) - /// } - /// } - /// - /// This property will let you navigate from the source type to the - /// destination type: - /// - /// try dbQueue.read { db in - /// let book: Book = ... - /// let author = try book.author.fetchOne(db) // Author? - /// } - /// - /// - parameters: - /// - destination: The record type at the other side of the association. - /// - key: An eventual decoding key for the association. By default, it - /// is `destination.databaseTableName`. - /// - foreignKey: An eventual foreign key. You need to provide an - /// explicit foreign key when GRDB can't infer one from the database - /// schema. This happens when the schema does not define any foreign - /// key to the destination table, or when the schema defines several - /// foreign keys to the destination table. - public static func belongsTo( - _ destination: Destination.Type, - key: String? = nil, - using foreignKey: ForeignKey? = nil) - -> BelongsToAssociation - where Destination: TableRecord - { - let foreignKeyRequest = SQLForeignKeyRequest( - originTable: databaseTableName, - destinationTable: Destination.databaseTableName, - foreignKey: foreignKey) - - let condition = SQLAssociationCondition( - foreignKeyRequest: foreignKeyRequest, - originIsLeft: true) - - let associationKey: SQLAssociationKey - if let key = key { - associationKey = .fixedSingular(key) - } else { - associationKey = .inflected(Destination.databaseTableName) - } - - return BelongsToAssociation(sqlAssociation: SQLAssociation( - key: associationKey, - condition: condition, - relation: Destination.all().relation, - cardinality: .toOne)) - } - - /// Creates a "Has many" association between Self and the - /// destination type. - /// - /// struct Book: TableRecord { ... } - /// struct Author: TableRecord { - /// static let books = hasMany(Book.self) - /// } - /// - /// The association will let you define requests that load both the source - /// and the destination type: - /// - /// // A request for all (author, book) pairs: - /// let request = Author.including(required: Author.books) - /// - /// To consume those requests, define a type that adopts both the - /// FetchableRecord and Decodable protocols: - /// - /// struct Authorship: FetchableRecord, Decodable { - /// var author: Author - /// var book: Book - /// } - /// - /// let authorships = try dbQueue.read { db in - /// return try Authorship.fetchAll(db, request) - /// } - /// for authorship in authorships { - /// print("\(authorship.author.name) wrote \(authorship.book.title)") - /// } - /// - /// It is recommended that you define, alongside the static association, a - /// property with the same name: - /// - /// struct Author: TableRecord, EncodableRecord { - /// static let books = hasMany(Book.self) - /// var books: QueryInterfaceRequest { - /// return request(for: Author.books) - /// } - /// } - /// - /// This property will let you navigate from the source type to the - /// destination type: - /// - /// try dbQueue.read { db in - /// let author: Author = ... - /// let books = try author.books.fetchAll(db) // [Book] - /// } - /// - /// - parameters: - /// - destination: The record type at the other side of the association. - /// - key: An eventual decoding key for the association. By default, it - /// is `destination.databaseTableName`. - /// - foreignKey: An eventual foreign key. You need to provide an - /// explicit foreign key when GRDB can't infer one from the database - /// schema. This happens when the schema does not define any foreign - /// key from the destination table, or when the schema defines several - /// foreign keys from the destination table. - public static func hasMany( - _ destination: Destination.Type, - key: String? = nil, - using foreignKey: ForeignKey? = nil) - -> HasManyAssociation - where Destination: TableRecord - { - let foreignKeyRequest = SQLForeignKeyRequest( - originTable: Destination.databaseTableName, - destinationTable: databaseTableName, - foreignKey: foreignKey) - - let condition = SQLAssociationCondition( - foreignKeyRequest: foreignKeyRequest, - originIsLeft: false) - - let associationKey: SQLAssociationKey - if let key = key { - associationKey = .fixedPlural(key) - } else { - associationKey = .inflected(Destination.databaseTableName) - } - - return HasManyAssociation(sqlAssociation: SQLAssociation( - key: associationKey, - condition: condition, - relation: Destination.all().relation, - cardinality: .toMany)) - } - - /// Creates a "Has Many Through" association between Self and the - /// destination type. - /// - /// struct Country: TableRecord { - /// static let passports = hasMany(Passport.self) - /// static let citizens = hasMany(Citizen.self, through: passports, using: Passport.citizen) - /// } - /// - /// struct Passport: TableRecord { - /// static let citizen = belongsTo(Citizen.self) - /// } - /// - /// struct Citizen: TableRecord { } - /// - /// The association will let you define requests that load both the source - /// and the destination type: - /// - /// // A request for all (country, citizen) pairs: - /// let request = Country.including(required: Coutry.citizens) - /// - /// To consume those requests, define a type that adopts both the - /// FetchableRecord and Decodable protocols: - /// - /// struct Citizenship: FetchableRecord, Decodable { - /// var country: Country - /// var citizen: Citizen - /// } - /// - /// let citizenships = try dbQueue.read { db in - /// return try Citizenship.fetchAll(db, request) - /// } - /// for citizenship in citizenships { - /// print("\(citizenship.citizen.name) is a citizen of \(citizenship.country.name)") - /// } - /// - /// It is recommended that you define, alongside the static association, a - /// property with the same name: - /// - /// struct Country: TableRecord, EncodableRecord { - /// static let passports = hasMany(Passport.self) - /// static let citizens = hasMany(Citizen.self, through: passports, using: Passport.citizen) - /// var citizens: QueryInterfaceRequest { - /// return request(for: Country.citizens) - /// } - /// } - /// - /// This property will let you navigate from the source type to the - /// destination type: - /// - /// try dbQueue.read { db in - /// let country: Country = ... - /// let citizens = try country.citizens.fetchAll(db) // [Country] - /// } - /// - /// - parameters: - /// - destination: The record type at the other side of the association. - /// - pivot: An association from Self to the intermediate type. - /// - target: A target association from the intermediate type to the - /// destination type. - /// - key: An eventual decoding key for the association. By default, it - /// is the same key as the target. - public static func hasMany( - _ destination: Target.RowDecoder.Type, - through pivot: Pivot, - using target: Target, - key: String? = nil) - -> HasManyThroughAssociation - where Pivot: Association, - Target: Association, - Pivot.OriginRowDecoder == Self, - Pivot.RowDecoder == Target.OriginRowDecoder - { - let association = HasManyThroughAssociation( - sqlAssociation: target.sqlAssociation.through(pivot.sqlAssociation)) - - if let key = key { - return association.forKey(key) - } else { - return association - } - } - - /// Creates a "Has one" association between Self and the - /// destination type. - /// - /// struct Demographics: TableRecord { ... } - /// struct Country: TableRecord { - /// static let demographics = hasOne(Demographics.self) - /// } - /// - /// The association will let you define requests that load both the source - /// and the destination type: - /// - /// // A request for all countries with their demographic profile: - /// let request = Country.including(optional: Country.demographics) - /// - /// To consume those requests, define a type that adopts both the - /// FetchableRecord and Decodable protocols: - /// - /// struct CountryInfo: FetchableRecord, Decodable { - /// var country: Country - /// var demographics: Demographics? - /// } - /// - /// let countryInfos = try dbQueue.read { db in - /// return try CountryInfo.fetchAll(db, request) - /// } - /// for countryInfo in countryInfos { - /// print("\(countryInfo.country.name) has \(countryInfo.demographics.population) citizens") - /// } - /// - /// It is recommended that you define, alongside the static association, a - /// property with the same name: - /// - /// struct Country: TableRecord, EncodableRecord { - /// static let demographics = hasOne(Demographics.self) - /// var demographics: QueryInterfaceRequest { - /// return request(for: Country.demographics) - /// } - /// } - /// - /// This property will let you navigate from the source type to the - /// destination type: - /// - /// try dbQueue.read { db in - /// let country: Country = ... - /// let demographics = try country.demographics.fetchOne(db) // Demographics? - /// } - /// - /// - parameters: - /// - destination: The record type at the other side of the association. - /// - key: An eventual decoding key for the association. By default, it - /// is `destination.databaseTableName`. - /// - foreignKey: An eventual foreign key. You need to provide an - /// explicit foreign key when GRDB can't infer one from the database - /// schema. This happens when the schema does not define any foreign - /// key from the destination table, or when the schema defines several - /// foreign keys from the destination table. - public static func hasOne( - _ destination: Destination.Type, - key: String? = nil, - using foreignKey: ForeignKey? = nil) - -> HasOneAssociation - where Destination: TableRecord - { - let foreignKeyRequest = SQLForeignKeyRequest( - originTable: Destination.databaseTableName, - destinationTable: databaseTableName, - foreignKey: foreignKey) - - let condition = SQLAssociationCondition( - foreignKeyRequest: foreignKeyRequest, - originIsLeft: false) - - let associationKey: SQLAssociationKey - if let key = key { - associationKey = .fixedSingular(key) - } else { - associationKey = .inflected(Destination.databaseTableName) - } - - return HasOneAssociation(sqlAssociation: SQLAssociation( - key: associationKey, - condition: condition, - relation: Destination.all().relation, - cardinality: .toOne)) - } - - /// Creates a "Has One Through" association between Self and the - /// destination type. - /// - /// struct Book: TableRecord { - /// static let library = belongsTo(Library.self) - /// static let returnAddress = hasOne(Address.self, through: library, using: library.address) - /// } - /// - /// struct Library: TableRecord { - /// static let address = hasOne(Address.self) - /// } - /// - /// struct Address: TableRecord { ... } - /// - /// The association will let you define requests that load both the source - /// and the destination type: - /// - /// // A request for all (book, returnAddress) pairs: - /// let request = Book.including(required: Book.returnAddress) - /// - /// To consume those requests, define a type that adopts both the - /// FetchableRecord and Decodable protocols: - /// - /// struct Todo: FetchableRecord, Decodable { - /// var book: Book - /// var address: Address - /// } - /// - /// let todos = try dbQueue.read { db in - /// return try Todo.fetchAll(db, request) - /// } - /// for todo in todos { - /// print("Please return \(todo.book) to \(todo.address)") - /// } - /// - /// It is recommended that you define, alongside the static association, a - /// property with the same name: - /// - /// struct Book: TableRecord, EncodableRecord { - /// static let library = belongsTo(Library.self) - /// static let returnAddress = hasOne(Address.self, through: library, using: library.address) - /// var returnAddress: QueryInterfaceRequest
{ - /// return request(for: Book.returnAddress) - /// } - /// } - /// - /// This property will let you navigate from the source type to the - /// destination type: - /// - /// try dbQueue.read { db in - /// let book: Book = ... - /// let address = try book.returnAddress.fetchOne(db) // Address? - /// } - /// - /// - parameters: - /// - destination: The record type at the other side of the association. - /// - pivot: An association from Self to the intermediate type. - /// - target: A target association from the intermediate type to the - /// destination type. - /// - key: An eventual decoding key for the association. By default, it - /// is the same key as the target. - public static func hasOne( - _ destination: Target.RowDecoder.Type, - through pivot: Pivot, - using target: Target, - key: String? = nil) - -> HasOneThroughAssociation - where Pivot: AssociationToOne, - Target: AssociationToOne, - Pivot.OriginRowDecoder == Self, - Pivot.RowDecoder == Target.OriginRowDecoder - { - let association = HasOneThroughAssociation( - sqlAssociation: target.sqlAssociation.through(pivot.sqlAssociation)) - - if let key = key { - return association.forKey(key) - } else { - return association - } - } -} - -/// A ForeignKey helps building associations when GRDB can't infer a foreign -/// key from the database schema. -/// -/// Sometimes the database schema does not define any foreign key between two -/// tables. And sometimes, there are several foreign keys from a table -/// to another: -/// -/// | Table book | | Table person | -/// | ------------ | | ------------ | -/// | id | +-->• id | -/// | authorId •---+ | name | -/// | translatorId •---+ -/// | title | -/// -/// When this happens, associations can't be automatically inferred from the -/// database schema. GRDB will complain with a fatal error such as "Ambiguous -/// foreign key from book to person", or "Could not infer foreign key from book -/// to person". -/// -/// Your help is needed. You have to instruct GRDB which foreign key to use: -/// -/// struct Book: TableRecord { -/// // Define foreign keys -/// static let authorForeignKey = ForeignKey(["authorId"])) -/// static let translatorForeignKey = ForeignKey(["translatorId"])) -/// -/// // Use foreign keys to define associations: -/// static let author = belongsTo(Person.self, using: authorForeignKey) -/// static let translator = belongsTo(Person.self, using: translatorForeignKey) -/// } -/// -/// Foreign keys are always defined from the table that contains the columns at -/// the origin of the foreign key. Person's symmetric HasMany associations reuse -/// Book's foreign keys: -/// -/// struct Person: TableRecord { -/// static let writtenBooks = hasMany(Book.self, using: Book.authorForeignKey) -/// static let translatedBooks = hasMany(Book.self, using: Book.translatorForeignKey) -/// } -/// -/// Foreign keys can also be defined from query interface columns: -/// -/// struct Book: TableRecord { -/// enum Columns: String, ColumnExpression { -/// case id, title, authorId, translatorId -/// } -/// -/// static let authorForeignKey = ForeignKey([Columns.authorId])) -/// static let translatorForeignKey = ForeignKey([Columns.translatorId])) -/// } -/// -/// When the destination table of a foreign key does not define any primary key, -/// you need to provide the full definition of a foreign key: -/// -/// struct Book: TableRecord { -/// static let authorForeignKey = ForeignKey(["authorId"], to: ["id"])) -/// static let author = belongsTo(Person.self, using: authorForeignKey) -/// } -public struct ForeignKey { - var originColumns: [String] - var destinationColumns: [String]? - - /// Creates a ForeignKey intended to define a record association. - /// - /// struct Book: TableRecord { - /// // Define foreign keys - /// static let authorForeignKey = ForeignKey(["authorId"])) - /// static let translatorForeignKey = ForeignKey(["translatorId"])) - /// - /// // Use foreign keys to define associations: - /// static let author = belongsTo(Person.self, using: authorForeignKey) - /// static let translator = belongsTo(Person.self, using: translatorForeignKey) - /// } - /// - /// - parameter originColumns: The columns at the origin of the foreign key. - /// - parameter destinationColumns: The columns at the destination of the - /// foreign key. When nil (the default), GRDB automatically uses the - /// primary key. - public init(_ originColumns: [String], to destinationColumns: [String]? = nil) { - self.originColumns = originColumns - self.destinationColumns = destinationColumns - } - - /// Creates a ForeignKey intended to define a record association. - /// - /// struct Book: TableRecord { - /// // Define columns - /// enum Columns: String, ColumnExpression { - /// case id, title, authorId, translatorId - /// } - /// - /// // Define foreign keys - /// static let authorForeignKey = ForeignKey([Columns.authorId])) - /// static let translatorForeignKey = ForeignKey([Columns.translatorId])) - /// - /// // Use foreign keys to define associations: - /// static let author = belongsTo(Person.self, using: authorForeignKey) - /// static let translator = belongsTo(Person.self, using: translatorForeignKey) - /// } - /// - /// - parameter originColumns: The columns at the origin of the foreign key. - /// - parameter destinationColumns: The columns at the destination of the - /// foreign key. When nil (the default), GRDB automatically uses the - /// primary key. - public init(_ originColumns: [ColumnExpression], to destinationColumns: [ColumnExpression]? = nil) { - self.init(originColumns.map { $0.name }, to: destinationColumns?.map { $0.name }) - } -} - -extension TableRecord where Self: EncodableRecord { - /// Creates a request that fetches the associated record(s). - /// - /// For example: - /// - /// struct Team: TableRecord, EncodableRecord { - /// static let players = hasMany(Player.self) - /// var players: QueryInterfaceRequest { - /// return request(for: Team.players) - /// } - /// } - /// - /// let team: Team = ... - /// let players = try team.players.fetchAll(db) // [Player] - public func request(for association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == Self { - let destinationRelation = association.sqlAssociation.destinationRelation(fromOriginRows: { db in - try [Row(PersistenceContainer(db, self))] - }) - return QueryInterfaceRequest(relation: destinationRelation) - } -} - -extension TableRecord { - - // MARK: - Associations - - /// Creates a request that prefetches an association. - public static func including(all association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == Self { - return all().including(all: association) - } - - /// Creates a request that includes an association. The columns of the - /// associated record are selected. The returned association does not - /// require that the associated database table contains a matching row. - public static func including(optional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == Self { - return all().including(optional: association) - } - - /// Creates a request that includes an association. The columns of the - /// associated record are selected. The returned association requires - /// that the associated database table contains a matching row. - public static func including(required association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == Self { - return all().including(required: association) - } - - /// Creates a request that includes an association. The columns of the - /// associated record are not selected. The returned association does not - /// require that the associated database table contains a matching row. - public static func joining(optional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == Self { - return all().joining(optional: association) - } - - /// Creates a request that includes an association. The columns of the - /// associated record are not selected. The returned association requires - /// that the associated database table contains a matching row. - public static func joining(required association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == Self { - return all().joining(required: association) - } - - // MARK: - Association Aggregates - - /// Creates a request with *aggregates* appended to the selection. - /// - /// // SELECT player.*, COUNT(DISTINCT book.rowid) AS bookCount - /// // FROM player LEFT JOIN book ... - /// var request = Player.annotated(with: Player.books.count) - public static func annotated(with aggregates: AssociationAggregate...) -> QueryInterfaceRequest { - return all().annotated(with: aggregates) - } - - /// Creates a request with *aggregates* appended to the selection. - /// - /// // SELECT player.*, COUNT(DISTINCT book.rowid) AS bookCount - /// // FROM player LEFT JOIN book ... - /// var request = Player.annotated(with: [Player.books.count]) - public static func annotated(with aggregates: [AssociationAggregate]) -> QueryInterfaceRequest { - return all().annotated(with: aggregates) - } - - /// Creates a request with the provided aggregate *predicate*. - /// - /// // SELECT player.* - /// // FROM player LEFT JOIN book ... - /// // HAVING COUNT(DISTINCT book.rowid) = 0 - /// var request = Player.all() - /// request = request.having(Player.books.isEmpty) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func having(_ predicate: AssociationAggregate) -> QueryInterfaceRequest { - return all().having(predicate) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/QueryInterface/TableRecord+QueryInterfaceRequest.swift b/Example/Pods/GRDB.swift/GRDB/QueryInterface/TableRecord+QueryInterfaceRequest.swift deleted file mode 100755 index e759a39..0000000 --- a/Example/Pods/GRDB.swift/GRDB/QueryInterface/TableRecord+QueryInterfaceRequest.swift +++ /dev/null @@ -1,324 +0,0 @@ -extension TableRecord { - - // MARK: Request Derivation - - /// Creates a request which fetches all records. - /// - /// // SELECT * FROM player - /// let request = Player.all() - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func all() -> QueryInterfaceRequest { - let relation = SQLRelation( - source: .table(tableName: databaseTableName, alias: nil), - selection: databaseSelection) - return QueryInterfaceRequest(relation: relation) - } - - /// Creates a request which fetches no record. - public static func none() -> QueryInterfaceRequest { - return all().none() // don't laugh - } - - /// Creates a request which selects *selection*. - /// - /// // SELECT id, email FROM player - /// let request = Player.select(Column("id"), Column("email")) - public static func select(_ selection: SQLSelectable...) -> QueryInterfaceRequest { - return all().select(selection) - } - - /// Creates a request which selects *selection*. - /// - /// // SELECT id, email FROM player - /// let request = Player.select([Column("id"), Column("email")]) - public static func select(_ selection: [SQLSelectable]) -> QueryInterfaceRequest { - return all().select(selection) - } - - /// Creates a request which selects *sql*. - /// - /// // SELECT id, email FROM player - /// let request = Player.select(sql: "id, email") - public static func select(sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { - return select(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request which selects an SQL *literal*. - /// - /// // SELECT id, email FROM player - /// let request = Player.select(literal: SQLLiteral(sql: "id, email")) - public static func select(literal sqlLiteral: SQLLiteral) -> QueryInterfaceRequest { - return all().select(literal: sqlLiteral) - } - - /// Creates a request which selects *selection*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.select([max(Column("score"))], as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public static func select(_ selection: [SQLSelectable], as type: RowDecoder.Type) -> QueryInterfaceRequest { - return all().select(selection, as: type) - } - - /// Creates a request which selects *selection*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.select(max(Column("score")), as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public static func select(_ selection: SQLSelectable..., as type: RowDecoder.Type) -> QueryInterfaceRequest { - return all().select(selection, as: type) - } - - /// Creates a request which selects *sql*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.select(sql: "max(score)", as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public static func select(sql: String, arguments: StatementArguments = StatementArguments(), as type: RowDecoder.Type) -> QueryInterfaceRequest { - return all().select(literal: SQLLiteral(sql: sql, arguments: arguments), as: type) - } - - /// Creates a request which selects an SQL *literal*, and fetches values of - /// type *type*. - /// - /// try dbQueue.read { db in - /// // SELECT max(score) FROM player - /// let request = Player.select(literal: SQLLiteral(sql: "max(score)"), as: Int.self) - /// let maxScore: Int? = try request.fetchOne(db) - /// } - public static func select(literal sqlLiteral: SQLLiteral, as type: RowDecoder.Type) -> QueryInterfaceRequest { - return all().select(literal: sqlLiteral, as: type) - } - - /// Creates a request which appends *selection*. - /// - /// // SELECT id, email, name FROM player - /// le request = Player - /// .select([Column("id"), Column("email")]) - /// .annotated(with: [Column("name")]) - public static func annotated(with selection: [SQLSelectable]) -> QueryInterfaceRequest { - return all().annotated(with: selection) - } - - /// Creates a request which appends *selection*. - /// - /// // SELECT id, email, name FROM player - /// le request = Player - /// .select([Column("id"), Column("email")]) - /// .annotated(with: Column("name")) - public static func annotated(with selection: SQLSelectable...) -> QueryInterfaceRequest { - return all().annotated(with: selection) - } - - /// Creates a request with the provided *predicate*. - /// - /// // SELECT * FROM player WHERE email = 'arthur@example.com' - /// let request = Player.filter(Column("email") == "arthur@example.com") - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(_ predicate: SQLExpressible) -> QueryInterfaceRequest { - return all().filter(predicate) - } - - /// Creates a request with the provided primary key *predicate*. - /// - /// // SELECT * FROM player WHERE id = 1 - /// let request = Player.filter(key: 1) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(key: PrimaryKeyType?) -> QueryInterfaceRequest { - return all().filter(key: key) - } - - /// Creates a request with the provided primary key *predicate*. - /// - /// // SELECT * FROM player WHERE id IN (1, 2, 3) - /// let request = Player.filter(keys: [1, 2, 3]) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(keys: Sequence) -> QueryInterfaceRequest where Sequence.Element: DatabaseValueConvertible { - return all().filter(keys: keys) - } - - /// Creates a request with the provided primary key *predicate*. - /// - /// // SELECT * FROM passport WHERE personId = 1 AND countryCode = 'FR' - /// let request = Passport.filter(key: ["personId": 1, "countryCode": "FR"]) - /// - /// When executed, this request raises a fatal error if there is no unique - /// index on the key columns. - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(key: [String: DatabaseValueConvertible?]?) -> QueryInterfaceRequest { - return all().filter(key: key) - } - - /// Creates a request with the provided primary key *predicate*. - /// - /// // SELECT * FROM passport WHERE (personId = 1 AND countryCode = 'FR') OR ... - /// let request = Passport.filter(keys: [["personId": 1, "countryCode": "FR"], ...]) - /// - /// When executed, this request raises a fatal error if there is no unique - /// index on the key columns. - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(keys: [[String: DatabaseValueConvertible?]]) -> QueryInterfaceRequest { - return all().filter(keys: keys) - } - - /// Creates a request with the provided *predicate*. - /// - /// // SELECT * FROM player WHERE email = 'arthur@example.com' - /// let request = Player.filter(sql: "email = ?", arguments: ["arthur@example.com"]) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { - return filter(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request with the provided *predicate*. - /// - /// // SELECT * FROM player WHERE email = 'arthur@example.com' - /// let request = Player.filter(literal: SQLLiteral(sql: "email = ?", arguments: ["arthur@example.com"])) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// let request = Player.filter(literal: "name = \("O'Brien")) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func filter(literal sqlLiteral: SQLLiteral) -> QueryInterfaceRequest { - // NOT TESTED - return all().filter(literal: sqlLiteral) - } - - /// Creates a request sorted according to the - /// provided *orderings*. - /// - /// // SELECT * FROM player ORDER BY name - /// let request = Player.order(Column("name")) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func order(_ orderings: SQLOrderingTerm...) -> QueryInterfaceRequest { - return all().order(orderings) - } - - /// Creates a request sorted according to the - /// provided *orderings*. - /// - /// // SELECT * FROM player ORDER BY name - /// let request = Player.order([Column("name")]) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func order(_ orderings: [SQLOrderingTerm]) -> QueryInterfaceRequest { - return all().order(orderings) - } - - /// Creates a request sorted by primary key. - /// - /// // SELECT * FROM player ORDER BY id - /// let request = Player.orderByPrimaryKey() - /// - /// // SELECT * FROM country ORDER BY code - /// let request = Country.orderByPrimaryKey() - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func orderByPrimaryKey() -> QueryInterfaceRequest { - return all().orderByPrimaryKey() - } - - /// Creates a request sorted according to *sql*. - /// - /// // SELECT * FROM player ORDER BY name - /// let request = Player.order(sql: "name") - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func order(sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { - return all().order(literal: SQLLiteral(sql: sql, arguments: arguments)) - } - - /// Creates a request sorted according to an SQL *literal*. - /// - /// // SELECT * FROM player ORDER BY name - /// let request = Player.order(literal: SQLLiteral(sql: "name")) - /// - /// With Swift 5, you can safely embed raw values in your SQL queries, - /// without any risk of syntax errors or SQL injection: - /// - /// // SELECT * FROM player ORDER BY name - /// let request = Player.order(literal: "name")) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func order(literal sqlLiteral: SQLLiteral) -> QueryInterfaceRequest { - return all().order(literal: sqlLiteral) - } - - /// Creates a request which fetches *limit* rows, starting at - /// *offset*. - /// - /// // SELECT * FROM player LIMIT 1 - /// let request = Player.limit(1) - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - public static func limit(_ limit: Int, offset: Int? = nil) -> QueryInterfaceRequest { - return all().limit(limit, offset: offset) - } - - /// Creates a request that allows you to define expressions that target - /// a specific database table. - /// - /// In the example below, the "team.avgScore < player.score" condition in - /// the ON clause could be not achieved without table aliases. - /// - /// struct Player: TableRecord { - /// static let team = belongsTo(Team.self) - /// } - /// - /// // SELECT player.*, team.* - /// // JOIN team ON ... AND team.avgScore < player.score - /// let playerAlias = TableAlias() - /// let request = Player - /// .aliased(playerAlias) - /// .including(required: Player.team.filter(Column("avgScore") < playerAlias[Column("score")]) - public static func aliased(_ alias: TableAlias) -> QueryInterfaceRequest { - return all().aliased(alias) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/EncodableRecord+Encodable.swift b/Example/Pods/GRDB.swift/GRDB/Record/EncodableRecord+Encodable.swift deleted file mode 100755 index f38439a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/EncodableRecord+Encodable.swift +++ /dev/null @@ -1,370 +0,0 @@ -import Foundation - -extension EncodableRecord where Self: Encodable { - public func encode(to container: inout PersistenceContainer) { - let encoder = RecordEncoder(persistenceContainer: container) - try! encode(to: encoder) - container = encoder.persistenceContainer - } -} - -// MARK: - RecordEncoder - -/// The encoder that encodes a record into GRDB's PersistenceContainer -private class RecordEncoder: Encoder { - var codingPath: [CodingKey] { return [] } - var userInfo: [CodingUserInfoKey: Any] { return Record.databaseEncodingUserInfo } - private var _persistenceContainer: PersistenceContainer - var persistenceContainer: PersistenceContainer { return _persistenceContainer } - - init(persistenceContainer: PersistenceContainer) { - _persistenceContainer = persistenceContainer - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer { - let container = KeyedContainer(recordEncoder: self) - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - fatalError("unkeyed encoding is not supported") - } - - func singleValueContainer() -> SingleValueEncodingContainer { - // @itaiferber on https://forums.swift.org/t/how-to-encode-objects-of-unknown-type/12253/11 - // - // > Encoding a value into a single-value container is equivalent to - // > encoding the value directly into the encoder, with the primary - // > difference being the above: encoding into the encoder writes the - // > contents of a type into the encoder, while encoding to a - // > single-value container gives the encoder a chance to intercept the - // > type as a whole. - // - // Wait for somebody hitting this fatal error so that we can write a - // meaningful regression test. - fatalError("single value encoding is not supported") - } - - private struct KeyedContainer : KeyedEncodingContainerProtocol { - var recordEncoder: RecordEncoder - var userInfo: [CodingUserInfoKey: Any] { return Record.databaseEncodingUserInfo } - - init(recordEncoder: RecordEncoder) { - self.recordEncoder = recordEncoder - } - - var codingPath: [CodingKey] { return [] } - - func encode(_ value: Bool, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int8, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int16, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int32, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int64, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt8, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt16, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt32, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt64, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Float, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Double, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: String, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - try recordEncoder.encode(value, forKey: key) - } - - func encodeNil(forKey key: Key) throws { recordEncoder.persist(nil, forKey: key) } - - func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Int?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Float?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: Double?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - func encodeIfPresent(_ value: String?, forKey key: Key) throws { recordEncoder.persist(value, forKey: key) } - - func encodeIfPresent(_ value: T?, forKey key: Key) throws where T : Encodable { - if let value = value { - try recordEncoder.encode(value, forKey: key) - } else { - recordEncoder.persist(nil, forKey: key) - } - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - fatalError("Not implemented") - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - fatalError("Not implemented") - } - - func superEncoder() -> Encoder { - fatalError("Not implemented") - } - - func superEncoder(forKey key: Key) -> Encoder { - fatalError("Not implemented") - } - } - - /// Helper methods - @inline(__always) - fileprivate func persist(_ value: DatabaseValueConvertible?, forKey key: CodingKey) { - _persistenceContainer[key.stringValue] = value - } - - @inline(__always) - fileprivate func encode(_ value: T, forKey key: CodingKey) throws where T : Encodable { - if let date = value as? Date { - persist(Record.databaseDateEncodingStrategy.encode(date), forKey: key) - } else if let uuid = value as? UUID { - persist(Record.databaseUUIDEncodingStrategy.encode(uuid), forKey: key) - } else if let value = value as? DatabaseValueConvertible { - // Prefer DatabaseValueConvertible encoding over Decodable. - persist(value.databaseValue, forKey: key) - } else { - do { - // This encoding will fail for types that encode into keyed - // or unkeyed containers, because we're encoding a single - // value here (string, int, double, data, null). If such an - // error happens, we'll switch to JSON encoding. - let encoder = ColumnEncoder(recordEncoder: self, key: key) - try value.encode(to: encoder) - if encoder.requiresJSON { - // Here we handle empty arrays and dictionaries. - throw JSONRequiredError() - } - } catch is JSONRequiredError { - // Encode to JSON - let jsonData = try Record.databaseJSONEncoder(for: key.stringValue).encode(value) - - // Store JSON String in the database for easier debugging and - // database inspection. Thanks to SQLite weak typing, we won't - // have any trouble decoding this string into data when we - // eventually perform JSON decoding. - // TODO: possible optimization: avoid this conversion to string, and store raw data bytes as an SQLite string - let jsonString = String(data: jsonData, encoding: .utf8)! // force unwrap because json data is guaranteed to convert to String - persist(jsonString, forKey: key) - } - } - } -} - -// MARK: - ColumnEncoder - -/// The encoder that encodes into a database column -private class ColumnEncoder: Encoder { - var recordEncoder: RecordEncoder - var key: CodingKey - var codingPath: [CodingKey] { return [key] } - var userInfo: [CodingUserInfoKey: Any] { return Record.databaseEncodingUserInfo } - var requiresJSON = false - - init(recordEncoder: RecordEncoder, key: CodingKey) { - self.recordEncoder = recordEncoder - self.key = key - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - // Keyed values require JSON encoding: we need to throw - // JSONRequiredError. Since we can't throw right from here, let's - // delegate the job to a dedicated container. - requiresJSON = true - let container = JSONRequiredEncoder.KeyedContainer(codingPath: codingPath) - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - // Keyed values require JSON encoding: we need to throw - // JSONRequiredError. Since we can't throw right from here, let's - // delegate the job to a dedicated container. - requiresJSON = true - return JSONRequiredEncoder(codingPath: codingPath) - } - - func singleValueContainer() -> SingleValueEncodingContainer { - return self - } -} - -extension ColumnEncoder: SingleValueEncodingContainer { - func encodeNil() throws { recordEncoder.persist(nil, forKey: key) } - - func encode(_ value: Bool ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int8 ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int16 ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int32 ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Int64 ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt8 ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt16) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt32) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: UInt64) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Float ) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: Double) throws { recordEncoder.persist(value, forKey: key) } - func encode(_ value: String) throws { recordEncoder.persist(value, forKey: key) } - - func encode(_ value: T) throws where T : Encodable { - try recordEncoder.encode(value, forKey: key) - } -} - -// MARK: - JSONRequiredEncoder - -/// The error that triggers JSON encoding -private struct JSONRequiredError: Error { } - -/// The encoder that always ends up with a JSONRequiredError -private struct JSONRequiredEncoder: Encoder { - var codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey: Any] { return Record.databaseEncodingUserInfo } - - init(codingPath: [CodingKey]) { - self.codingPath = codingPath - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let container = KeyedContainer(codingPath: codingPath) - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - return self - } - - func singleValueContainer() -> SingleValueEncodingContainer { - return self - } - - struct KeyedContainer: KeyedEncodingContainerProtocol { - var codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey: Any] { return Record.databaseEncodingUserInfo } - - func encodeNil(forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Bool, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Int, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Int8, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Int16, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Int32, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Int64, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: UInt, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: UInt8, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: UInt16, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: UInt32, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: UInt64, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Float, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: Double, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: String, forKey key: KeyType) throws { throw JSONRequiredError() } - func encode(_ value: T, forKey key: KeyType) throws where T : Encodable { throw JSONRequiredError() } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: KeyType) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = KeyedContainer(codingPath: codingPath + [key]) - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer(forKey key: KeyType) -> UnkeyedEncodingContainer { - return JSONRequiredEncoder(codingPath: codingPath) - } - - func superEncoder() -> Encoder { - return JSONRequiredEncoder(codingPath: codingPath) - } - - func superEncoder(forKey key: KeyType) -> Encoder { - return JSONRequiredEncoder(codingPath: codingPath) - } - } -} - -extension JSONRequiredEncoder: SingleValueEncodingContainer { - func encodeNil() throws { throw JSONRequiredError() } - func encode(_ value: Bool ) throws { throw JSONRequiredError() } - func encode(_ value: Int ) throws { throw JSONRequiredError() } - func encode(_ value: Int8 ) throws { throw JSONRequiredError() } - func encode(_ value: Int16 ) throws { throw JSONRequiredError() } - func encode(_ value: Int32 ) throws { throw JSONRequiredError() } - func encode(_ value: Int64 ) throws { throw JSONRequiredError() } - func encode(_ value: UInt ) throws { throw JSONRequiredError() } - func encode(_ value: UInt8 ) throws { throw JSONRequiredError() } - func encode(_ value: UInt16) throws { throw JSONRequiredError() } - func encode(_ value: UInt32) throws { throw JSONRequiredError() } - func encode(_ value: UInt64) throws { throw JSONRequiredError() } - func encode(_ value: Float ) throws { throw JSONRequiredError() } - func encode(_ value: Double) throws { throw JSONRequiredError() } - func encode(_ value: String) throws { throw JSONRequiredError() } - func encode(_ value: T) throws where T : Encodable { throw JSONRequiredError() } -} - -extension JSONRequiredEncoder: UnkeyedEncodingContainer { - var count: Int { return 0 } - - mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = KeyedContainer(codingPath: codingPath) - return KeyedEncodingContainer(container) - } - - mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - return self - } - - mutating func superEncoder() -> Encoder { - return self - } -} - -@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) -fileprivate var iso8601Formatter: ISO8601DateFormatter = { - let formatter = ISO8601DateFormatter() - formatter.formatOptions = .withInternetDateTime - return formatter -}() - -private extension DatabaseDateEncodingStrategy { - @inline(__always) - func encode(_ date: Date) -> DatabaseValueConvertible? { - switch self { - case .deferredToDate: - return date.databaseValue - case .timeIntervalSinceReferenceDate: - return date.timeIntervalSinceReferenceDate - case .timeIntervalSince1970: - return date.timeIntervalSince1970 - case .millisecondsSince1970: - return Int64(floor(1000.0 * date.timeIntervalSince1970)) - case .secondsSince1970: - return Int64(floor(date.timeIntervalSince1970)) - case .iso8601: - if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { - return iso8601Formatter.string(from: date) - } else { - fatalError("ISO8601DateFormatter is unavailable on this platform.") - } - case .formatted(let formatter): - return formatter.string(from: date) - case .custom(let format): - return format(date) - } - } -} - -private extension DatabaseUUIDEncodingStrategy { - @inline(__always) - func encode(_ uuid: UUID) -> DatabaseValueConvertible? { - switch self { - case .deferredToUUID: - return uuid.databaseValue - case .string: - return uuid.uuidString - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/EncodableRecord.swift b/Example/Pods/GRDB.swift/GRDB/Record/EncodableRecord.swift deleted file mode 100755 index 378fe79..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/EncodableRecord.swift +++ /dev/null @@ -1,387 +0,0 @@ -import Foundation // For JSONEncoder - -/// Types that adopt EncodableRecord can be encoded into the database. -public protocol EncodableRecord { - /// Encodes the record into database values. - /// - /// Store in the *container* argument all values that should be stored in - /// the columns of the database table (see databaseTableName()). - /// - /// Primary key columns, if any, must be included. - /// - /// struct Player: EncodableRecord { - /// var id: Int64? - /// var name: String? - /// - /// func encode(to container: inout PersistenceContainer) { - /// container["id"] = id - /// container["name"] = name - /// } - /// } - /// - /// It is undefined behavior to set different values for the same column. - /// Column names are case insensitive, so defining both "name" and "NAME" - /// is considered undefined behavior. - func encode(to container: inout PersistenceContainer) - - // MARK: - Customizing the Format of Database Columns - - /// When the EncodableRecord type also adopts the standard Encodable - /// protocol, you can use this dictionary to customize the encoding process - /// into database rows. - /// - /// For example: - /// - /// // A key that holds a encoder's name - /// let encoderName = CodingUserInfoKey(rawValue: "encoderName")! - /// - /// struct Player: PersistableRecord, Encodable { - /// // Customize the encoder name when encoding a database row - /// static let databaseEncodingUserInfo: [CodingUserInfoKey: Any] = [encoderName: "Database"] - /// - /// func encode(to encoder: Encoder) throws { - /// // Print the encoder name - /// print(encoder.userInfo[encoderName]) - /// ... - /// } - /// } - /// - /// let player = Player(...) - /// - /// // prints "Database" - /// try player.insert(db) - /// - /// // prints "JSON" - /// let encoder = JSONEncoder() - /// encoder.userInfo = [encoderName: "JSON"] - /// let data = try encoder.encode(player) - static var databaseEncodingUserInfo: [CodingUserInfoKey: Any] { get } - - /// When the EncodableRecord type also adopts the standard Encodable - /// protocol, this method controls the encoding process of nested properties - /// into JSON database columns. - /// - /// The default implementation returns a JSONEncoder with the - /// following properties: - /// - /// - dataEncodingStrategy: .base64 - /// - dateEncodingStrategy: .millisecondsSince1970 - /// - nonConformingFloatEncodingStrategy: .throw - /// - outputFormatting: .sortedKeys (iOS 11.0+, macOS 10.13+, watchOS 4.0+) - /// - /// You can override those defaults: - /// - /// struct Achievement: Encodable { - /// var name: String - /// var date: Date - /// } - /// - /// struct Player: Encodable, PersistableRecord { - /// // stored in a JSON column - /// var achievements: [Achievement] - /// - /// static func databaseJSONEncoder(for column: String) -> JSONEncoder { - /// let encoder = JSONEncoder() - /// encoder.dateEncodingStrategy = .iso8601 - /// return encoder - /// } - /// } - static func databaseJSONEncoder(for column: String) -> JSONEncoder - - /// When the EncodableRecord type also adopts the standard Encodable - /// protocol, this property controls the encoding of date properties. - /// - /// Default value is .deferredToDate - /// - /// For example: - /// - /// struct Player: PersistableRecord, Encodable { - /// static let databaseDateEncodingStrategy: DatabaseDateEncodingStrategy = .timeIntervalSince1970 - /// - /// var name: String - /// var registrationDate: Date // encoded as an epoch timestamp - /// } - static var databaseDateEncodingStrategy: DatabaseDateEncodingStrategy { get } - - /// When the EncodableRecord type also adopts the standard Encodable - /// protocol, this property controls the encoding of UUID properties. - /// - /// Default value is .deferredToUUID - /// - /// For example: - /// - /// struct Player: PersistableProtocol, Encodable { - /// static let databaseUUIDEncodingStrategy: DatabaseUUIDEncodingStrategy = .string - /// - /// // encoded in a string like "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" - /// var uuid: UUID - /// } - static var databaseUUIDEncodingStrategy: DatabaseUUIDEncodingStrategy { get } -} - -extension EncodableRecord { - public static var databaseEncodingUserInfo: [CodingUserInfoKey: Any] { - return [:] - } - - public static func databaseJSONEncoder(for column: String) -> JSONEncoder { - let encoder = JSONEncoder() - encoder.dataEncodingStrategy = .base64 - encoder.dateEncodingStrategy = .millisecondsSince1970 - encoder.nonConformingFloatEncodingStrategy = .throw - if #available(watchOS 4.0, OSX 10.13, iOS 11.0, *) { - // guarantee some stability in order to ease record comparison - encoder.outputFormatting = .sortedKeys - } - return encoder - } - - public static var databaseDateEncodingStrategy: DatabaseDateEncodingStrategy { - return .deferredToDate - } - - public static var databaseUUIDEncodingStrategy: DatabaseUUIDEncodingStrategy { - return .deferredToUUID - } -} - -extension EncodableRecord { - /// A dictionary whose keys are the columns encoded in the `encode(to:)` method. - public var databaseDictionary: [String: DatabaseValue] { - return Dictionary(PersistenceContainer(self).storage).mapValues { $0?.databaseValue ?? .null } - } -} - -extension EncodableRecord { - - // MARK: - Record Comparison - - /// Returns a boolean indicating whether this record and the other record - /// have the same database representation. - public func databaseEquals(_ record: Self) -> Bool { - return PersistenceContainer(self).changesIterator(from: PersistenceContainer(record)).next() == nil - } - - /// A dictionary of values changed from the other record. - /// - /// Its keys are column names. Its values come from the other record. - /// - /// Note that this method is not symmetrical, not only in terms of values, - /// but also in terms of columns. When the two records don't define the - /// same set of columns in their `encode(to:)` method, only the columns - /// defined by the receiver record are considered. - public func databaseChanges(from record: Record) -> [String: DatabaseValue] { - return Dictionary(uniqueKeysWithValues: PersistenceContainer(self).changesIterator(from: PersistenceContainer(record))) - } -} - -// MARK: - PersistenceContainer - -/// Use persistence containers in the `encode(to:)` method of your -/// encodable records: -/// -/// struct Player: EncodableRecord { -/// var id: Int64? -/// var name: String? -/// -/// func encode(to container: inout PersistenceContainer) { -/// container["id"] = id -/// container["name"] = name -/// } -/// } -public struct PersistenceContainer { - // fileprivate for Row(_:PersistenceContainer) - // The ordering of the OrderedDictionary helps generating always the same - // SQL queries, and hit the statement cache. - @usableFromInline var storage: OrderedDictionary - - /// Accesses the value associated with the given column. - /// - /// It is undefined behavior to set different values for the same column. - /// Column names are case insensitive, so defining both "name" and "NAME" - /// is considered undefined behavior. - @inlinable - public subscript(_ column: String) -> DatabaseValueConvertible? { - get { return storage[column] ?? nil } - set { storage.updateValue(newValue, forKey: column) } - } - - /// Accesses the value associated with the given column. - /// - /// It is undefined behavior to set different values for the same column. - /// Column names are case insensitive, so defining both "name" and "NAME" - /// is considered undefined behavior. - @inlinable - public subscript(_ column: Column) -> DatabaseValueConvertible? { - get { return self[column.name] } - set { self[column.name] = newValue } - } - - init() { - storage = OrderedDictionary() - } - - init(minimumCapacity: Int) { - storage = OrderedDictionary(minimumCapacity: minimumCapacity) - } - - /// Convenience initializer from a record - init(_ record: Record) { - self.init() - record.encode(to: &self) - } - - /// Columns stored in the container, ordered like values. - var columns: [String] { - return Array(storage.keys) - } - - /// Values stored in the container, ordered like columns. - var values: [DatabaseValueConvertible?] { - return Array(storage.values) - } - - /// Accesses the value associated with the given column, in a - /// case-insensitive fashion. - /// - /// :nodoc: - subscript(caseInsensitive column: String) -> DatabaseValueConvertible? { - get { - if let value = storage[column] { - return value - } - let lowercaseColumn = column.lowercased() - for (key, value) in storage where key.lowercased() == lowercaseColumn { - return value - } - return nil - } - set { - if storage[column] != nil { - storage[column] = newValue - return - } - let lowercaseColumn = column.lowercased() - for key in storage.keys where key.lowercased() == lowercaseColumn { - storage[key] = newValue - return - } - - storage[column] = newValue - } - } - - // Returns nil if column is not defined - func value(forCaseInsensitiveColumn column: String) -> DatabaseValue? { - let lowercaseColumn = column.lowercased() - for (key, value) in storage where key.lowercased() == lowercaseColumn { - return value?.databaseValue ?? .null - } - return nil - } - - var isEmpty: Bool { - return storage.isEmpty - } - - /// An iterator over the (column, value) pairs - func makeIterator() -> IndexingIterator> { - return storage.makeIterator() - } - - func changesIterator(from container: PersistenceContainer) -> AnyIterator<(String, DatabaseValue)> { - var newValueIterator = makeIterator() - return AnyIterator { - // Loop until we find a change, or exhaust columns: - while let (column, newValue) = newValueIterator.next() { - let oldValue = container[caseInsensitive: column] - let oldDbValue = oldValue?.databaseValue ?? .null - let newDbValue = newValue?.databaseValue ?? .null - if newDbValue != oldDbValue { - return (column, oldDbValue) - } - } - return nil - } - } -} - -extension Row { - convenience init(_ record: Record) { - self.init(PersistenceContainer(record)) - } - - convenience init(_ container: PersistenceContainer) { - self.init(Dictionary(container.storage)) - } -} - -// MARK: - DatabaseDateEncodingStrategy - -/// DatabaseDateEncodingStrategy specifies how EncodableRecord types that also -/// adopt the standard Encodable protocol encode their date properties. -/// -/// For example: -/// -/// struct Player: EncodableRecord, Encodable { -/// static let databaseDateEncodingStrategy: DatabaseDateEncodingStrategy = .timeIntervalSince1970 -/// -/// var name: String -/// var registrationDate: Date // encoded as an epoch timestamp -/// } -public enum DatabaseDateEncodingStrategy { - /// The strategy that uses formatting from the Date structure. - /// - /// It encodes dates using the format "YYYY-MM-DD HH:MM:SS.SSS" in the - /// UTC time zone. - case deferredToDate - - /// Encodes a Double: the number of seconds between the date and - /// midnight UTC on 1 January 2001 - case timeIntervalSinceReferenceDate - - /// Encodes a Double: the number of seconds between the date and - /// midnight UTC on 1 January 1970 - case timeIntervalSince1970 - - /// Encodes an Int64: the number of seconds between the date and - /// midnight UTC on 1 January 1970 - case secondsSince1970 - - /// Encodes an Int64: the number of milliseconds between the date and - /// midnight UTC on 1 January 1970 - case millisecondsSince1970 - - /// Encodes dates according to the ISO 8601 and RFC 3339 standards - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) - case iso8601 - - /// Encodes a String, according to the provided formatter - case formatted(DateFormatter) - - /// Encodes the result of the user-provided function - case custom((Date) -> DatabaseValueConvertible?) -} - -// MARK: - DatabaseUUIDEncodingStrategy - -/// DatabaseUUIDEncodingStrategy specifies how EncodableRecord types that also -/// adopt the standard Encodable protocol encode their UUID properties. -/// -/// For example: -/// -/// struct Player: EncodableProtocol, Encodable { -/// static let databaseUUIDEncodingStrategy: DatabaseUUIDEncodingStrategy = .string -/// -/// // encoded in a string like "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" -/// var uuid: UUID -/// } -public enum DatabaseUUIDEncodingStrategy { - /// The strategy that uses formatting from the UUID type. - /// - /// It encodes UUIDs as 16-bytes data blobs. - case deferredToUUID - - /// Encodes UUIDs as strings such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" - case string -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord+Decodable.swift b/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord+Decodable.swift deleted file mode 100755 index e379631..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord+Decodable.swift +++ /dev/null @@ -1,522 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -extension FetchableRecord where Self: Decodable { - public init(row: Row) { - let decoder = RowDecoder(row: row, codingPath: []) - try! self.init(from: decoder) - } -} - -// MARK: - RowDecoder - -/// The decoder that decodes a record from a database row -private struct RowDecoder: Decoder { - var row: Row - var codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey: Any] { return R.databaseDecodingUserInfo } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - return KeyedDecodingContainer(KeyedContainer(decoder: self)) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - guard let codingKey = codingPath.last else { - fatalError("unkeyed decoding from database row is not supported") - } - let keys = row.prefetchedRows.keys - let debugDescription: String - if keys.isEmpty { - debugDescription = "No available prefetched rows" - } else { - debugDescription = "Available keys for prefetched rows: \(keys.sorted())" - } - throw DecodingError.keyNotFound( - codingKey, - DecodingError.Context( - codingPath: Array(codingPath.dropLast()), // TODO: remove Array initializer when Swift >= 5 - debugDescription: debugDescription)) - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - guard let key = codingPath.last else { - fatalError("single value decoding from database row is not supported") - } - guard let index = row.index(ofColumn: key.stringValue) else { - // Don't use DecodingError.keyNotFound: - // We need to specifically recognize missing columns in order to - // provide correct feedback. - throw MissingColumnError(column: key.stringValue) - } - // TODO: test - // See DatabaseValueConversionErrorTests.testDecodableFetchableRecord2 - return ColumnDecoder(row: row, columnIndex: index, codingPath: codingPath) - } - - struct KeyedContainer: KeyedDecodingContainerProtocol { - let decoder: RowDecoder - var codingPath: [CodingKey] { return decoder.codingPath } - - init(decoder: RowDecoder) { - self.decoder = decoder - } - - var allKeys: [Key] { - let row = decoder.row - return Set(row.columnNames) - .union(Set(row.scopesTree.names)) - .union(Set(row.prefetchedRows.keys)) - .compactMap { Key(stringValue: $0) } - } - - func contains(_ key: Key) -> Bool { - let row = decoder.row - return row.hasColumn(key.stringValue) || (row.scopesTree[key.stringValue] != nil) - } - - func decodeNil(forKey key: Key) throws -> Bool { - let row = decoder.row - return row[key.stringValue] == nil && (row.scopesTree[key.stringValue] == nil) - } - - func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { return decoder.row[key.stringValue] } - func decode(_ type: Int.Type, forKey key: Key) throws -> Int { return decoder.row[key.stringValue] } - func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { return decoder.row[key.stringValue] } - func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { return decoder.row[key.stringValue] } - func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { return decoder.row[key.stringValue] } - func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { return decoder.row[key.stringValue] } - func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { return decoder.row[key.stringValue] } - func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { return decoder.row[key.stringValue] } - func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { return decoder.row[key.stringValue] } - func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { return decoder.row[key.stringValue] } - func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { return decoder.row[key.stringValue] } - func decode(_ type: Float.Type, forKey key: Key) throws -> Float { return decoder.row[key.stringValue] } - func decode(_ type: Double.Type, forKey key: Key) throws -> Double { return decoder.row[key.stringValue] } - func decode(_ type: String.Type, forKey key: Key) throws -> String { return decoder.row[key.stringValue] } - - func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T : Decodable { - let row = decoder.row - let keyName = key.stringValue - - // Column? - if let index = row.index(ofColumn: keyName) { - // Prefer DatabaseValueConvertible decoding over Decodable. - // This allows decoding Date from String, or DatabaseValue from NULL. - if type == Date.self { - return R.databaseDateDecodingStrategy.decodeIfPresent(fromRow: row, columnAtIndex: index) as! T? - } else if let type = T.self as? (DatabaseValueConvertible & StatementColumnConvertible).Type { - return type.fastDecodeIfPresent(from: row, atUncheckedIndex: index) as! T? - } else if let type = T.self as? DatabaseValueConvertible.Type { - return type.decodeIfPresent(from: row, atUncheckedIndex: index) as! T? - } else if row.impl.hasNull(atUncheckedIndex: index) { - return nil - } else { - return try decode(type, fromRow: row, columnAtIndex: index, key: key) - } - } - - // Scope? (beware left joins: check if scoped row contains non-null values) - if let scopedRow = row.scopesTree[keyName], scopedRow.containsNonNullValue { - return try decode(type, fromRow: scopedRow, codingPath: codingPath + [key]) - } - - // Key is not a column, and not a scope. - return nil - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { - let row = decoder.row - let keyName = key.stringValue - - // Column? - if let index = row.index(ofColumn: keyName) { - // Prefer DatabaseValueConvertible decoding over Decodable. - // This allows decoding Date from String, or DatabaseValue from NULL. - if type == Date.self { - return R.databaseDateDecodingStrategy.decode(fromRow: row, columnAtIndex: index) as! T - } else if let type = T.self as? (DatabaseValueConvertible & StatementColumnConvertible).Type { - return type.fastDecode(from: row, atUncheckedIndex: index) as! T - } else if let type = T.self as? DatabaseValueConvertible.Type { - return type.decode(from: row, atUncheckedIndex: index) as! T - } else { - return try decode(type, fromRow: row, columnAtIndex: index, key: key) - } - } - - // Scope? - if let scopedRow = row.scopesTree[keyName] { - return try decode(type, fromRow: scopedRow, codingPath: codingPath + [key]) - } - - // Prefetched Rows? - if let prefetchedRows = row.prefetchedRows[keyName] { - let decoder = PrefetchedRowsDecoder(rows: prefetchedRows, codingPath: codingPath) - return try T(from: decoder) - } - - // Key is not a column, and not a scope. - // - // Should be throw an error? Well... The use case is the following: - // - // // SELECT book.*, author.* FROM book - // // JOIN author ON author.id = book.authorId - // let request = Book.including(required: Book.author) - // - // Rows loaded from this request don't have any "book" key: - // - // let row = try Row.fetchOne(db, request)! - // print(row.debugDescription) - // // ▿ [id:1 title:"Moby-Dick" authorId:2] - // // unadapted: [id:1 title:"Moby-Dick" authorId:2 id:2 name:"Melville"] - // // author: [id:2 name:"Melville"] - // - // And yet we have to decode the "book" key when we decode the - // BookInfo type below: - // - // struct BookInfo { - // var book: Book // <- decodes from the "book" key - // var author: Author - // } - // let infos = try BookInfos.fetchAll(db, request) - // - // Our current strategy is to assume that a missing key (such as - // "book", which is not the name of a column, and not the name of a - // scope) has to be decoded right from the base row. - // - // Yeah, there may be better ways to handle this. - return try decode(type, fromRow: row, codingPath: codingPath + [key]) - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - fatalError("not implemented") - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError.typeMismatch( - UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: codingPath, debugDescription: "unkeyed decoding is not supported")) - } - - func superDecoder() throws -> Decoder { - // Not sure - return decoder - } - - func superDecoder(forKey key: Key) throws -> Decoder { - fatalError("not implemented") - } - - // Helper methods - - @inline(__always) - private func decode(_ type: T.Type, fromRow row: Row, codingPath: [CodingKey]) throws -> T where T: Decodable { - if let type = T.self as? FetchableRecord.Type { - // Prefer FetchableRecord decoding over Decodable. - return type.init(row: row) as! T - } else { - do { - let decoder = RowDecoder(row: row, codingPath: codingPath) - return try T(from: decoder) - } catch let error as MissingColumnError { - // Support for DatabaseValueConversionErrorTests.testDecodableFetchableRecord2 - fatalConversionError( - to: type, - from: nil, - conversionContext: ValueConversionContext(row).atColumn(error.column)) - } - } - } - - @inline(__always) - private func decode(_ type: T.Type, fromRow row: Row, columnAtIndex index: Int, key: Key) throws -> T where T: Decodable { - do { - // This decoding will fail for types that decode from keyed - // or unkeyed containers, because we're decoding a single - // value here (string, int, double, data, null). If such an - // error happens, we'll switch to JSON decoding. - let columnDecoder = ColumnDecoder( - row: row, - columnIndex: index, - codingPath: codingPath + [key]) - return try T(from: columnDecoder) - } catch is JSONRequiredError { - // Decode from JSON - guard let data = row.dataNoCopy(atIndex: index) else { - fatalConversionError(to: T.self, from: row[index], conversionContext: ValueConversionContext(row).atColumn(index)) - } - return try R - .databaseJSONDecoder(for: key.stringValue) - .decode(type.self, from: data) - } - } - } -} - -// MARK: - PrefetchedRowsDecoder - -private struct PrefetchedRowsDecoder: Decoder { - var rows: [Row] - var codingPath: [CodingKey] - var currentIndex: Int - var userInfo: [CodingUserInfoKey: Any] { return R.databaseDecodingUserInfo } - - init(rows: [Row], codingPath: [CodingKey]) { - self.rows = rows - self.codingPath = codingPath - self.currentIndex = 0 - } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - fatalError("keyed decoding from prefetched rows is not supported") - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - return self - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - fatalError("single value decoding from prefetched rows is not supported") - } -} - -extension PrefetchedRowsDecoder: UnkeyedDecodingContainer { - var count: Int? { - return rows.count - } - - var isAtEnd: Bool { - return currentIndex >= rows.count - } - - mutating func decodeNil() throws -> Bool { - return false - } - - mutating func decode(_ type: T.Type) throws -> T where T : Decodable { - defer { currentIndex += 1 } - let decoder = RowDecoder(row: rows[currentIndex], codingPath: codingPath) - return try T(from: decoder) - } - - mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - fatalError("not implemented") - } - - mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - fatalError("not implemented") - } - - mutating func superDecoder() throws -> Decoder { - fatalError("not implemented") - } -} - -// MARK: - ColumnDecoder - -/// The decoder that decodes from a database column -private struct ColumnDecoder: Decoder { - var row: Row - var columnIndex: Int - var codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey: Any] { return R.databaseDecodingUserInfo } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - // We need to switch to JSON decoding - throw JSONRequiredError() - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - // We need to switch to JSON decoding - throw JSONRequiredError() - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - return self - } -} - -extension ColumnDecoder: SingleValueDecodingContainer { - func decodeNil() -> Bool { - return row.hasNull(atIndex: columnIndex) - } - - func decode(_ type: Bool.Type ) throws -> Bool { return row[columnIndex] } - func decode(_ type: Int.Type ) throws -> Int { return row[columnIndex] } - func decode(_ type: Int8.Type ) throws -> Int8 { return row[columnIndex] } - func decode(_ type: Int16.Type ) throws -> Int16 { return row[columnIndex] } - func decode(_ type: Int32.Type ) throws -> Int32 { return row[columnIndex] } - func decode(_ type: Int64.Type ) throws -> Int64 { return row[columnIndex] } - func decode(_ type: UInt.Type ) throws -> UInt { return row[columnIndex] } - func decode(_ type: UInt8.Type ) throws -> UInt8 { return row[columnIndex] } - func decode(_ type: UInt16.Type) throws -> UInt16 { return row[columnIndex] } - func decode(_ type: UInt32.Type) throws -> UInt32 { return row[columnIndex] } - func decode(_ type: UInt64.Type) throws -> UInt64 { return row[columnIndex] } - func decode(_ type: Float.Type ) throws -> Float { return row[columnIndex] } - func decode(_ type: Double.Type) throws -> Double { return row[columnIndex] } - func decode(_ type: String.Type) throws -> String { return row[columnIndex] } - - func decode(_ type: T.Type) throws -> T where T : Decodable { - // Prefer DatabaseValueConvertible decoding over Decodable. - // This allows decoding Date from String, or DatabaseValue from NULL. - if type == Date.self { - return R.databaseDateDecodingStrategy.decode(fromRow: row, columnAtIndex: columnIndex) as! T - } else if let type = T.self as? (DatabaseValueConvertible & StatementColumnConvertible).Type { - return type.fastDecode(from: row, atUncheckedIndex: columnIndex) as! T - } else if let type = T.self as? DatabaseValueConvertible.Type { - return type.decode(from: row, atUncheckedIndex: columnIndex) as! T - } else { - return try T(from: self) - } - } -} - -/// The error that triggers JSON decoding -private struct JSONRequiredError: Error { } - -/// The error for missing columns -private struct MissingColumnError: Error { - var column: String -} - -@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) -fileprivate var iso8601Formatter: ISO8601DateFormatter = { - let formatter = ISO8601DateFormatter() - formatter.formatOptions = .withInternetDateTime - return formatter -}() - -private extension DatabaseDateDecodingStrategy { - @inline(__always) - func decodeIfPresent(fromRow row: Row, columnAtIndex index: Int) -> Date? { - if let sqliteStatement = row.sqliteStatement { - return decodeIfPresent( - sqliteStatement: sqliteStatement, - index: Int32(index)) - } else { - return decodeIfPresent( - from: row[index], - conversionContext: ValueConversionContext(row).atColumn(index)) - } - } - - @inline(__always) - func decode(fromRow row: Row, columnAtIndex index: Int) -> Date { - if let sqliteStatement = row.sqliteStatement { - return decode( - sqliteStatement: sqliteStatement, - index: Int32(index)) - } else { - return decode( - from: row[index], - conversionContext: ValueConversionContext(row).atColumn(index)) - } - } - - @inline(__always) - func decode(sqliteStatement: SQLiteStatement, index: Int32) -> Date { - switch self { - case .deferredToDate: - return Date(sqliteStatement: sqliteStatement, index: index) - case .timeIntervalSinceReferenceDate: - let timeInterval = TimeInterval(sqliteStatement: sqliteStatement, index: index) - return Date(timeIntervalSinceReferenceDate: timeInterval) - case .timeIntervalSince1970: - let timeInterval = TimeInterval(sqliteStatement: sqliteStatement, index: index) - return Date(timeIntervalSince1970: timeInterval) - case .millisecondsSince1970: - let timeInterval = TimeInterval(sqliteStatement: sqliteStatement, index: index) - return Date(timeIntervalSince1970: timeInterval / 1000.0) - case .iso8601: - if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { - let string = String(sqliteStatement: sqliteStatement, index: index) - guard let date = iso8601Formatter.date(from: string) else { - fatalConversionError(to: Date.self, sqliteStatement: sqliteStatement, index: index) - } - return date - } else { - fatalError("ISO8601DateFormatter is unavailable on this platform.") - } - case .formatted(let formatter): - let string = String(sqliteStatement: sqliteStatement, index: index) - guard let date = formatter.date(from: string) else { - fatalConversionError(to: Date.self, sqliteStatement: sqliteStatement, index: index) - } - return date - case .custom(let format): - let dbValue = DatabaseValue(sqliteStatement: sqliteStatement, index: index) - guard let date = format(dbValue) else { - fatalConversionError(to: Date.self, sqliteStatement: sqliteStatement, index: index) - } - return date - } - } - - @inline(__always) - func decodeIfPresent(sqliteStatement: SQLiteStatement, index: Int32) -> Date? { - if sqlite3_column_type(sqliteStatement, index) == SQLITE_NULL { - return nil - } - return decode(sqliteStatement: sqliteStatement, index: index) - } - - @inline(__always) - func decode(from dbValue: DatabaseValue, conversionContext: @autoclosure () -> ValueConversionContext?) -> Date { - if let date = dateFromDatabaseValue(dbValue) { - return date - } else { - fatalConversionError(to: Date.self, from: dbValue, conversionContext: conversionContext()) - } - } - - @inline(__always) - func decodeIfPresent(from dbValue: DatabaseValue, conversionContext: @autoclosure () -> ValueConversionContext?) -> Date? { - if dbValue.isNull { - return nil - } else if let date = dateFromDatabaseValue(dbValue) { - return date - } else { - fatalConversionError(to: Date.self, from: dbValue, conversionContext: conversionContext()) - } - } - - // Returns nil if decoding fails - @inline(__always) - private func dateFromDatabaseValue(_ dbValue: DatabaseValue) -> Date? { - switch self { - case .deferredToDate: - return Date.fromDatabaseValue(dbValue) - case .timeIntervalSinceReferenceDate: - return TimeInterval - .fromDatabaseValue(dbValue) - .map { Date(timeIntervalSinceReferenceDate: $0) } - case .timeIntervalSince1970: - return TimeInterval - .fromDatabaseValue(dbValue) - .map { Date(timeIntervalSince1970: $0) } - case .millisecondsSince1970: - return TimeInterval - .fromDatabaseValue(dbValue) - .map { Date(timeIntervalSince1970: $0 / 1000.0) } - case .iso8601: - if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { - return String - .fromDatabaseValue(dbValue) - .flatMap { iso8601Formatter.date(from: $0) } - } else { - fatalError("ISO8601DateFormatter is unavailable on this platform.") - } - case .formatted(let formatter): - return String - .fromDatabaseValue(dbValue) - .flatMap { formatter.date(from: $0) } - case .custom(let format): - return format(dbValue) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord+TableRecord.swift b/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord+TableRecord.swift deleted file mode 100755 index 9377364..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord+TableRecord.swift +++ /dev/null @@ -1,182 +0,0 @@ -extension FetchableRecord where Self: TableRecord { - - // MARK: Fetching All - - /// A cursor over all records fetched from the database. - /// - /// // SELECT * FROM player - /// let players = try Player.fetchCursor(db) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// Records are iterated in the natural ordering of the table. - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database) throws -> RecordCursor { - return try all().fetchCursor(db) - } - - /// An array of all records fetched from the database. - /// - /// // SELECT * FROM player - /// let players = try Player.fetchAll(db) // [Player] - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database) throws -> [Self] { - return try all().fetchAll(db) - } - - /// The first found record. - /// - /// // SELECT * FROM player - /// let player = try Player.fetchOne(db) // Player? - /// - /// The selection defaults to all columns. This default can be changed for - /// all requests by the `TableRecord.databaseSelection` property, or - /// for individual requests with the `TableRecord.select` method. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database) throws -> Self? { - return try all().fetchOne(db) - } -} - -extension FetchableRecord where Self: TableRecord { - - // MARK: Fetching by Single-Column Primary Key - - /// Returns a cursor over records, given their primary keys. - /// - /// let players = try Player.fetchCursor(db, keys: [1, 2, 3]) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// Records are iterated in unspecified order. - /// - /// - parameters: - /// - db: A database connection. - /// - keys: A sequence of primary keys. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, keys: Sequence) throws -> RecordCursor where Sequence.Element: DatabaseValueConvertible { - return try filter(keys: keys).fetchCursor(db) - } - - /// Returns an array of records, given their primary keys. - /// - /// let players = try Player.fetchAll(db, keys: [1, 2, 3]) // [Player] - /// - /// The order of records in the returned array is undefined. - /// - /// - parameters: - /// - db: A database connection. - /// - keys: A sequence of primary keys. - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, keys: Sequence) throws -> [Self] where Sequence.Element: DatabaseValueConvertible { - let keys = Array(keys) - if keys.isEmpty { - // Avoid hitting the database - return [] - } - return try filter(keys: keys).fetchAll(db) - } - - /// Returns a single record given its primary key. - /// - /// let player = try Player.fetchOne(db, key: 123) // Player? - /// - /// - parameters: - /// - db: A database connection. - /// - key: A primary key value. - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, key: PrimaryKeyType?) throws -> Self? { - guard let key = key else { - // Avoid hitting the database - return nil - } - return try filter(key: key).fetchOne(db) - } -} - -extension FetchableRecord where Self: TableRecord { - - // MARK: Fetching by Key - - /// Returns a cursor over records identified by the provided unique keys - /// (primary key or any key with a unique index on it). - /// - /// let players = try Player.fetchCursor(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// Records are iterated in unspecified order. - /// - /// - parameters: - /// - db: A database connection. - /// - keys: An array of key dictionaries. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> RecordCursor { - return try filter(keys: keys).fetchCursor(db) - } - - /// Returns an array of records identified by the provided unique keys - /// (primary key or any key with a unique index on it). - /// - /// let players = try Player.fetchAll(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) // [Player] - /// - /// The order of records in the returned array is undefined. - /// - /// - parameters: - /// - db: A database connection. - /// - keys: An array of key dictionaries. - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> [Self] { - if keys.isEmpty { - // Avoid hitting the database - return [] - } - return try filter(keys: keys).fetchAll(db) - } - - /// Returns a single record identified by a unique key (the primary key or - /// any key with a unique index on it). - /// - /// let player = try Player.fetchOne(db, key: ["name": Arthur"]) // Player? - /// - /// - parameters: - /// - db: A database connection. - /// - key: A dictionary of values. - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, key: [String: DatabaseValueConvertible?]?) throws -> Self? { - guard let key = key else { - // Avoid hitting the database - return nil - } - return try filter(key: key).fetchOne(db) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord.swift b/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord.swift deleted file mode 100755 index da3f358..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/FetchableRecord.swift +++ /dev/null @@ -1,468 +0,0 @@ -import Foundation -#if SWIFT_PACKAGE - import CSQLite -#elseif GRDBCIPHER - import SQLCipher -#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER - import SQLite3 -#endif - -/// Types that adopt FetchableRecord can be initialized from a database Row. -/// -/// let row = try Row.fetchOne(db, sql: "SELECT ...")! -/// let player = Player(row) -/// -/// The protocol comes with built-in methods that allow to fetch cursors, -/// arrays, or single records: -/// -/// try Player.fetchCursor(db, sql: "SELECT ...", arguments:...) // Cursor of Player -/// try Player.fetchAll(db, sql: "SELECT ...", arguments:...) // [Player] -/// try Player.fetchOne(db, sql: "SELECT ...", arguments:...) // Player? -/// -/// let statement = try db.makeSelectStatement(sql: "SELECT ...") -/// try Player.fetchCursor(statement, arguments:...) // Cursor of Player -/// try Player.fetchAll(statement, arguments:...) // [Player] -/// try Player.fetchOne(statement, arguments:...) // Player? -/// -/// FetchableRecord is adopted by Record. -public protocol FetchableRecord { - - // MARK: - Row Decoding - - /// Creates a record from `row`. - /// - /// For performance reasons, the row argument may be reused during the - /// iteration of a fetch query. If you want to keep the row for later use, - /// make sure to store a copy: `self.row = row.copy()`. - init(row: Row) - - // MARK: - Customizing the Format of Database Columns - - /// When the FetchableRecord type also adopts the standard Decodable - /// protocol, you can use this dictionary to customize the decoding process - /// from database rows. - /// - /// For example: - /// - /// // A key that holds a decoder's name - /// let decoderName = CodingUserInfoKey(rawValue: "decoderName")! - /// - /// // A FetchableRecord + Decodable record - /// struct Player: FetchableRecord, Decodable { - /// // Customize the decoder name when decoding a database row - /// static let databaseDecodingUserInfo: [CodingUserInfoKey: Any] = [decoderName: "Database"] - /// - /// init(from decoder: Decoder) throws { - /// // Print the decoder name - /// print(decoder.userInfo[decoderName]) - /// ... - /// } - /// } - /// - /// // prints "Database" - /// let player = try Player.fetchOne(db, ...) - /// - /// // prints "JSON" - /// let decoder = JSONDecoder() - /// decoder.userInfo = [decoderName: "JSON"] - /// let player = try decoder.decode(Player.self, from: ...) - static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] { get } - - /// When the FetchableRecord type also adopts the standard Decodable - /// protocol, this method controls the decoding process of nested properties - /// from JSON database columns. - /// - /// The default implementation returns a JSONDecoder with the - /// following properties: - /// - /// - dataDecodingStrategy: .base64 - /// - dateDecodingStrategy: .millisecondsSince1970 - /// - nonConformingFloatDecodingStrategy: .throw - /// - /// You can override those defaults: - /// - /// struct Achievement: Decodable { - /// var name: String - /// var date: Date - /// } - /// - /// struct Player: Decodable, FetchableRecord { - /// // stored in a JSON column - /// var achievements: [Achievement] - /// - /// static func databaseJSONDecoder(for column: String) -> JSONDecoder { - /// let decoder = JSONDecoder() - /// decoder.dateDecodingStrategy = .iso8601 - /// return decoder - /// } - /// } - static func databaseJSONDecoder(for column: String) -> JSONDecoder - - /// When the FetchableRecord type also adopts the standard Decodable - /// protocol, this property controls the decoding of date properties. - /// - /// Default value is .deferredToDate - /// - /// For example: - /// - /// struct Player: FetchableRecord, Decodable { - /// static let databaseDateDecodingStrategy: DatabaseDateDecodingStrategy = .timeIntervalSince1970 - /// - /// var name: String - /// var registrationDate: Date // decoded from epoch timestamp - /// } - static var databaseDateDecodingStrategy: DatabaseDateDecodingStrategy { get } -} - -extension FetchableRecord { - public static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] { - return [:] - } - - /// Returns a JSONDecoder with the following properties: - /// - /// - dataDecodingStrategy: .base64 - /// - dateDecodingStrategy: .millisecondsSince1970 - /// - nonConformingFloatDecodingStrategy: .throw - public static func databaseJSONDecoder(for column: String) -> JSONDecoder { - let decoder = JSONDecoder() - decoder.dataDecodingStrategy = .base64 - decoder.dateDecodingStrategy = .millisecondsSince1970 - decoder.nonConformingFloatDecodingStrategy = .throw - return decoder - } - - public static var databaseDateDecodingStrategy: DatabaseDateDecodingStrategy { - return .deferredToDate - } -} - -extension FetchableRecord { - - // MARK: Fetching From SelectStatement - - /// A cursor over records fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT * FROM player") - /// let players = try Player.fetchCursor(statement) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> RecordCursor { - return try RecordCursor(statement: statement, arguments: arguments, adapter: adapter) - } - - /// Returns an array of records fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT * FROM player") - /// let players = try Player.fetchAll(statement) // [Player] - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> [Self] { - return try Array(fetchCursor(statement, arguments: arguments, adapter: adapter)) - } - - /// Returns a single record fetched from a prepared statement. - /// - /// let statement = try db.makeSelectStatement(sql: "SELECT * FROM player") - /// let player = try Player.fetchOne(statement) // Player? - /// - /// - parameters: - /// - statement: The statement to run. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws -> Self? { - return try fetchCursor(statement, arguments: arguments, adapter: adapter).next() - } -} - -extension FetchableRecord { - - // MARK: Fetching From SQL - - /// Returns a cursor over records fetched from an SQL query. - /// - /// let players = try Player.fetchCursor(db, sql: "SELECT * FROM player") // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> RecordCursor { - return try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns an array of records fetched from an SQL query. - /// - /// let players = try Player.fetchAll(db, sql: "SELECT * FROM player") // [Player] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> [Self] { - return try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Returns a single record fetched from an SQL query. - /// - /// let player = try Player.fetchOne(db, sql: "SELECT * FROM player") // Player? - /// - /// - parameters: - /// - db: A database connection. - /// - sql: An SQL query. - /// - arguments: Statement arguments. - /// - adapter: Optional RowAdapter - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws -> Self? { - return try fetchOne(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } -} - -extension FetchableRecord { - - // MARK: Fetching From FetchRequest - - /// Returns a cursor over records fetched from a fetch request. - /// - /// let request = try Player.all() - /// let players = try Player.fetchCursor(db, request) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameters: - /// - db: A database connection. - /// - sql: a FetchRequest. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchCursor(_ db: Database, _ request: R) throws -> RecordCursor { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchCursor(statement, adapter: adapter) - } - - /// Returns an array of records fetched from a fetch request. - /// - /// let request = try Player.all() - /// let players = try Player.fetchAll(db, request) // [Player] - /// - /// - parameters: - /// - db: A database connection. - /// - sql: a FetchRequest. - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchAll(_ db: Database, _ request: R) throws -> [Self] { - let (statement, adapter) = try request.prepare(db, forSingleResult: false) - return try fetchAll(statement, adapter: adapter) - } - - /// Returns a single record fetched from a fetch request. - /// - /// let request = try Player.filter(key: 1) - /// let player = try Player.fetchOne(db, request) // Player? - /// - /// - parameters: - /// - db: A database connection. - /// - sql: a FetchRequest. - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public static func fetchOne(_ db: Database, _ request: R) throws -> Self? { - let (statement, adapter) = try request.prepare(db, forSingleResult: true) - return try fetchOne(statement, adapter: adapter) - } -} - -// MARK: - FetchRequest - -extension FetchRequest where RowDecoder: FetchableRecord { - - // MARK: Fetching Records - - /// A cursor over fetched records. - /// - /// let request: ... // Some FetchRequest that fetches Player - /// let players = try request.fetchCursor(db) // Cursor of Player - /// while let player = try players.next() { // Player - /// ... - /// } - /// - /// If the database is modified during the cursor iteration, the remaining - /// elements are undefined. - /// - /// The cursor must be iterated in a protected dispath queue. - /// - /// - parameter db: A database connection. - /// - returns: A cursor over fetched records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchCursor(_ db: Database) throws -> RecordCursor { - return try RowDecoder.fetchCursor(db, self) - } - - /// An array of fetched records. - /// - /// let request: ... // Some FetchRequest that fetches Player - /// let players = try request.fetchAll(db) // [Player] - /// - /// - parameter db: A database connection. - /// - returns: An array of records. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchAll(_ db: Database) throws -> [RowDecoder] { - return try RowDecoder.fetchAll(db, self) - } - - /// The first fetched record. - /// - /// let request: ... // Some FetchRequest that fetches Player - /// let player = try request.fetchOne(db) // Player? - /// - /// - parameter db: A database connection. - /// - returns: An optional record. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - public func fetchOne(_ db: Database) throws -> RowDecoder? { - return try RowDecoder.fetchOne(db, self) - } -} - -// MARK: - RecordCursor - -/// A cursor of records. For example: -/// -/// struct Player : FetchableRecord { ... } -/// try dbQueue.read { db in -/// let players: RecordCursor = try Player.fetchCursor(db, sql: "SELECT * FROM player") -/// } -public final class RecordCursor : Cursor { - @usableFromInline let _statement: SelectStatement - @usableFromInline let _row: Row // Reused for performance - @usableFromInline let _sqliteStatement: SQLiteStatement - @usableFromInline var _done = false - - init(statement: SelectStatement, arguments: StatementArguments? = nil, adapter: RowAdapter? = nil) throws { - _statement = statement - _row = try Row(statement: statement).adapted(with: adapter, layout: statement) - _sqliteStatement = statement.sqliteStatement - _statement.reset(withArguments: arguments) - } - - deinit { - // Statement reset fails when sqlite3_step has previously failed. - // Just ignore reset error. - try? _statement.reset() - } - - /// :nodoc: - @inlinable - public func next() throws -> Record? { - if _done { - // make sure this instance never yields a value again, even if the - // statement is reset by another cursor. - return nil - } - switch sqlite3_step(_sqliteStatement) { - case SQLITE_DONE: - _done = true - return nil - case SQLITE_ROW: - return Record(row: _row) - case let code: - try _statement.didFail(withResultCode: code) - } - } -} - -// MARK: - DatabaseDateDecodingStrategy - -/// DatabaseDateDecodingStrategy specifies how FetchableRecord types that also -/// adopt the standard Decodable protocol decode their date properties. -/// -/// For example: -/// -/// struct Player: FetchableRecord, Decodable { -/// static let databaseDateDecodingStrategy: DatabaseDateDecodingStrategy = .timeIntervalSince1970 -/// -/// var name: String -/// var registrationDate: Date // decoded from epoch timestamp -/// } -public enum DatabaseDateDecodingStrategy { - /// The strategy that uses formatting from the Date structure. - /// - /// It decodes numeric values as a nunber of seconds since Epoch - /// (midnight UTC on January 1st, 1970). - /// - /// It decodes strings in the following formats, assuming UTC time zone. - /// Missing components are assumed to be zero: - /// - /// - `YYYY-MM-DD` - /// - `YYYY-MM-DD HH:MM` - /// - `YYYY-MM-DD HH:MM:SS` - /// - `YYYY-MM-DD HH:MM:SS.SSS` - /// - `YYYY-MM-DDTHH:MM` - /// - `YYYY-MM-DDTHH:MM:SS` - /// - `YYYY-MM-DDTHH:MM:SS.SSS` - case deferredToDate - - /// Decodes numeric values as a number of seconds between the date and - /// midnight UTC on 1 January 2001 - case timeIntervalSinceReferenceDate - - /// Decodes numeric values as a number of seconds between the date and - /// midnight UTC on 1 January 1970 - case timeIntervalSince1970 - - /// Decodes numeric values as a number of milliseconds between the date and - /// midnight UTC on 1 January 1970 - case millisecondsSince1970 - - /// Decodes dates according to the ISO 8601 standards - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) - case iso8601 - - /// Decodes a String, according to the provided formatter - case formatted(DateFormatter) - - /// Decodes according to the user-provided function. - /// - /// If the database value does not contain a suitable value, the function - /// must return nil (GRDB will interpret this nil result as a conversion - /// error, and react accordingly). - case custom((DatabaseValue) -> Date?) -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/FetchedRecordsController.swift b/Example/Pods/GRDB.swift/GRDB/Record/FetchedRecordsController.swift deleted file mode 100755 index 8238fc9..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/FetchedRecordsController.swift +++ /dev/null @@ -1,1030 +0,0 @@ -import Foundation - -#if os(iOS) - import UIKit -#endif - -/// You use FetchedRecordsController to track changes in the results of an -/// SQLite request. -/// -/// See https://github.com/groue/GRDB.swift#fetchedrecordscontroller for -/// more information. -public final class FetchedRecordsController { - - // MARK: - Initialization - - /// Creates a fetched records controller initialized from a SQL query and - /// its eventual arguments. - /// - /// let controller = FetchedRecordsController( - /// dbQueue, - /// sql: "SELECT * FROM wine WHERE color = ? ORDER BY name", - /// arguments: [Color.red], - /// isSameRecord: { (wine1, wine2) in wine1.id == wine2.id }) - /// - /// - parameters: - /// - databaseWriter: A DatabaseWriter (DatabaseQueue, or DatabasePool) - /// - sql: An SQL query. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - queue: A serial dispatch queue (defaults to the main queue) - /// - /// The fetched records controller tracking callbacks will be - /// notified of changes in this queue. The controller itself must be - /// used from this queue. - /// - /// - isSameRecord: Optional function that compares two records. - /// - /// This function should return true if the two records have the - /// same identity. For example, they have the same id. - public convenience init( - _ databaseWriter: DatabaseWriter, - sql: String, - arguments: StatementArguments = StatementArguments(), - adapter: RowAdapter? = nil, - queue: DispatchQueue = .main, - isSameRecord: ((Record, Record) -> Bool)? = nil) throws - { - try self.init( - databaseWriter, - request: SQLRequest(sql: sql, arguments: arguments, adapter: adapter), - queue: queue, - isSameRecord: isSameRecord) - } - - /// Creates a fetched records controller initialized from a fetch request - /// from the [Query Interface](https://github.com/groue/GRDB.swift#the-query-interface). - /// - /// let request = Wine.order(Column("name")) - /// let controller = FetchedRecordsController( - /// dbQueue, - /// request: request, - /// isSameRecord: { (wine1, wine2) in wine1.id == wine2.id }) - /// - /// - parameters: - /// - databaseWriter: A DatabaseWriter (DatabaseQueue, or DatabasePool) - /// - request: A fetch request. - /// - queue: A serial dispatch queue (defaults to the main queue) - /// - /// The fetched records controller tracking callbacks will be - /// notified of changes in this queue. The controller itself must be - /// used from this queue. - /// - /// - isSameRecord: Optional function that compares two records. - /// - /// This function should return true if the two records have the - /// same identity. For example, they have the same id. - public convenience init( - _ databaseWriter: DatabaseWriter, - request: Request, - queue: DispatchQueue = .main, - isSameRecord: ((Record, Record) -> Bool)? = nil) throws - where Request: FetchRequest, Request.RowDecoder == Record - { - let itemsAreIdenticalFactory: ItemComparatorFactory - if let isSameRecord = isSameRecord { - itemsAreIdenticalFactory = { _ in { isSameRecord($0.record, $1.record) } } - } else { - itemsAreIdenticalFactory = { _ in { _,_ in false } } - } - - try self.init( - databaseWriter, - request: request, - queue: queue, - itemsAreIdenticalFactory: itemsAreIdenticalFactory) - } - - private init( - _ databaseWriter: DatabaseWriter, - request: Request, - queue: DispatchQueue, - itemsAreIdenticalFactory: @escaping ItemComparatorFactory) throws - where Request: FetchRequest, Request.RowDecoder == Record - { - self.itemsAreIdenticalFactory = itemsAreIdenticalFactory - self.request = ItemRequest(request) - (self.region, self.itemsAreIdentical) = try databaseWriter.unsafeRead { db in - let region = try request.databaseRegion(db) - let itemsAreIdentical = try itemsAreIdenticalFactory(db) - return (region, itemsAreIdentical) - } - self.databaseWriter = databaseWriter - self.queue = queue - } - - /// Executes the controller's fetch request. - /// - /// After executing this method, you can access the the fetched objects with - /// the `fetchedRecords` property. - /// - /// This method must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - public func performFetch() throws { - // If some changes are currently processed, make sure they are - // discarded. But preserve eventual changes processing for future - // changes. - let fetchAndNotifyChanges = observer?.fetchAndNotifyChanges - observer?.invalidate() - observer = nil - - // Fetch items on the writing dispatch queue, so that the transaction - // observer is added on the same serialized queue as transaction - // callbacks. - try databaseWriter.write { db in - let initialItems = try request.fetchAll(db) - fetchedItems = initialItems - if let fetchAndNotifyChanges = fetchAndNotifyChanges { - let observer = FetchedRecordsObserver(region: self.region, fetchAndNotifyChanges: fetchAndNotifyChanges) - self.observer = observer - observer.items = initialItems - db.add(transactionObserver: observer) - } - } - } - - - // MARK: - Configuration - - /// The database writer used to fetch records. - /// - /// The controller registers as a transaction observer in order to respond - /// to changes. - public let databaseWriter: DatabaseWriter - - /// The dispatch queue on which the controller must be used. - /// - /// Unless specified otherwise at initialization time, it is the main queue. - public let queue: DispatchQueue - - /// Updates the fetch request, and eventually notifies the tracking - /// callbacks if performFetch() has been called. - /// - /// This method must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - public func setRequest(_ request: Request) throws where Request: FetchRequest, Request.RowDecoder == Record { - self.request = ItemRequest(request) - (self.region, self.itemsAreIdentical) = try databaseWriter.unsafeRead { db in - let region = try request.databaseRegion(db) - let itemsAreIdentical = try itemsAreIdenticalFactory(db) - return (region, itemsAreIdentical) - } - - // No observer: don't look for changes - guard let observer = observer else { return } - - // If some changes are currently processed, make sure they are - // discarded. But preserve eventual changes processing. - let fetchAndNotifyChanges = observer.fetchAndNotifyChanges - observer.invalidate() - self.observer = nil - - // Replace observer so that it tracks a new set of columns, - // and notify eventual changes - let initialItems = fetchedItems - databaseWriter.writeWithoutTransaction { db in - let observer = FetchedRecordsObserver(region: region, fetchAndNotifyChanges: fetchAndNotifyChanges) - self.observer = observer - observer.items = initialItems - db.add(transactionObserver: observer) - observer.fetchAndNotifyChanges(observer) - } - } - - /// Updates the fetch request, and eventually notifies the tracking - /// callbacks if performFetch() has been called. - /// - /// This method must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - public func setRequest(sql: String, arguments: StatementArguments = StatementArguments(), adapter: RowAdapter? = nil) throws { - try setRequest(SQLRequest(sql: sql, arguments: arguments, adapter: adapter)) - } - - /// Registers changes notification callbacks. - /// - /// This method must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - /// - /// - parameters: - /// - willChange: Invoked before records are updated. - /// - onChange: Invoked for each record that has been added, - /// removed, moved, or updated. - /// - didChange: Invoked after records have been updated. - public func trackChanges( - willChange: ((FetchedRecordsController) -> ())? = nil, - onChange: ((FetchedRecordsController, Record, FetchedRecordChange) -> ())? = nil, - didChange: ((FetchedRecordsController) -> ())? = nil) - { - // I hate you SE-0110. - let wrappedWillChange: ((FetchedRecordsController, Void) -> ())? - if let willChange = willChange { - wrappedWillChange = { (controller, _) in willChange(controller) } - } else { - wrappedWillChange = nil - } - - let wrappedDidChange: ((FetchedRecordsController, Void) -> ())? - if let didChange = didChange { - wrappedDidChange = { (controller, _) in didChange(controller) } - } else { - wrappedDidChange = nil - } - - trackChanges( - fetchAlongside: { _ in }, - willChange: wrappedWillChange, - onChange: onChange, - didChange: wrappedDidChange) - - // Without bloody SE-0110: -// trackChanges( -// fetchAlongside: { _ in }, -// willChange: willChange.map { callback in { (controller, _) in callback(controller) } }, -// onChange: onChange, -// didChange: didChange.map { callback in { (controller, _) in callback(controller) } }) - } - - /// Registers changes notification callbacks. - /// - /// This method must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - /// - /// - parameters: - /// - fetchAlongside: The value returned from this closure is given to - /// willChange and didChange callbacks, as their - /// `fetchedAlongside` argument. The closure is guaranteed to see the - /// database in the state it has just after eventual changes to the - /// fetched records have been performed. Use it in order to fetch - /// values that must be consistent with the fetched records. - /// - willChange: Invoked before records are updated. - /// - onChange: Invoked for each record that has been added, - /// removed, moved, or updated. - /// - didChange: Invoked after records have been updated. - public func trackChanges( - fetchAlongside: @escaping (Database) throws -> T, - willChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil, - onChange: ((FetchedRecordsController, Record, FetchedRecordChange) -> ())? = nil, - didChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())? = nil) - { - // If some changes are currently processed, make sure they are - // discarded because they would trigger previously set callbacks. - observer?.invalidate() - observer = nil - - guard (willChange != nil) || (onChange != nil) || (didChange != nil) else { - // Stop tracking - return - } - - var willProcessTransaction: () -> () = { } - var didProcessTransaction: () -> () = { } - #if os(iOS) - if let application = application { - var backgroundTaskID: UIBackgroundTaskIdentifier! = nil - willProcessTransaction = { - backgroundTaskID = application.beginBackgroundTask { - application.endBackgroundTask(backgroundTaskID) - } - } - didProcessTransaction = { - application.endBackgroundTask(backgroundTaskID) - } - } - #endif - - let initialItems = fetchedItems - databaseWriter.writeWithoutTransaction { db in - let fetchAndNotifyChanges = makeFetchAndNotifyChangesFunction( - controller: self, - fetchAlongside: fetchAlongside, - itemsAreIdentical: itemsAreIdentical, - willProcessTransaction: willProcessTransaction, - willChange: willChange, - onChange: onChange, - didChange: didChange, - didProcessTransaction: didProcessTransaction) - let observer = FetchedRecordsObserver(region: region, fetchAndNotifyChanges: fetchAndNotifyChanges) - self.observer = observer - if let initialItems = initialItems { - observer.items = initialItems - db.add(transactionObserver: observer) - observer.fetchAndNotifyChanges(observer) - } - } - } - - /// Registers an error callback. - /// - /// Whenever the controller could not look for changes after a transaction - /// has potentially modified the tracked request, this error handler is - /// called. - /// - /// The request observation is not stopped, though: future transactions may - /// successfully be handled, and the notified changes will then be based on - /// the last successful fetch. - /// - /// This method must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - public func trackErrors(_ errorHandler: @escaping (FetchedRecordsController, Error) -> ()) { - self.errorHandler = errorHandler - } - - #if os(iOS) - /// Call this method when changes performed while the application is - /// in the background should be processed before the application enters the - /// suspended state. - /// - /// Whenever the tracked request is changed, the fetched records controller - /// sets up a background task using - /// `UIApplication.beginBackgroundTask(expirationHandler:)` which is ended - /// after the `didChange` callback has completed. - public func allowBackgroundChangesTracking(in application: UIApplication) { - self.application = application - } - #endif - - // MARK: - Accessing Records - - /// The fetched records. - /// - /// The value of this property is nil until performFetch() has been called. - /// - /// The records reflect the state of the database after the initial - /// call to performFetch, and after each database transaction that affects - /// the results of the fetch request. - /// - /// This property must be used from the controller's dispatch queue (the - /// main queue unless stated otherwise in the controller's initializer). - public var fetchedRecords: [Record] { - guard let fetchedItems = fetchedItems else { - fatalError("the performFetch() method must be called before accessing fetched records") - } - return fetchedItems.map { $0.record } - } - - - // MARK: - Not public - - #if os(iOS) - /// Support for allowBackgroundChangeTracking(in:) - var application: UIApplication? - #endif - - /// The items - fileprivate var fetchedItems: [Item]? - - /// The record comparator - private var itemsAreIdentical: ItemComparator - - /// The record comparator factory (support for request change) - private let itemsAreIdenticalFactory: ItemComparatorFactory - - /// The request - fileprivate typealias ItemRequest = AnyFetchRequest> - fileprivate var request: ItemRequest - - /// The observed database region - private var region : DatabaseRegion - - /// The eventual current database observer - private var observer: FetchedRecordsObserver? - - /// The eventual error handler - fileprivate var errorHandler: ((FetchedRecordsController, Error) -> ())? -} - -extension FetchedRecordsController where Record: TableRecord { - - // MARK: - Initialization - - /// Creates a fetched records controller initialized from a SQL query and - /// its eventual arguments. - /// - /// let controller = FetchedRecordsController( - /// dbQueue, - /// sql: "SELECT * FROM wine WHERE color = ? ORDER BY name", - /// arguments: [Color.red]) - /// - /// The records are compared by primary key (single-column primary key, - /// compound primary key, or implicit rowid). For a database table which - /// has an `id` primary key, this initializer is equivalent to: - /// - /// // Assuming the wine table has an `id` primary key: - /// let controller = FetchedRecordsController( - /// dbQueue, - /// sql: "SELECT * FROM wine WHERE color = ? ORDER BY name", - /// arguments: [Color.red], - /// isSameRecord: { (wine1, wine2) in wine1.id == wine2.id }) - /// - /// - parameters: - /// - databaseWriter: A DatabaseWriter (DatabaseQueue, or DatabasePool) - /// - sql: An SQL query. - /// - arguments: Optional statement arguments. - /// - adapter: Optional RowAdapter - /// - queue: A serial dispatch queue (defaults to the main queue) - /// - /// The fetched records controller tracking callbacks will be - /// notified of changes in this queue. The controller itself must be - /// used from this queue. - public convenience init( - _ databaseWriter: DatabaseWriter, - sql: String, - arguments: StatementArguments = StatementArguments(), - adapter: RowAdapter? = nil, - queue: DispatchQueue = .main) throws - { - try self.init( - databaseWriter, - request: SQLRequest(sql: sql, arguments: arguments, adapter: adapter), - queue: queue) - } - - /// Creates a fetched records controller initialized from a fetch request - /// from the [Query Interface](https://github.com/groue/GRDB.swift#the-query-interface). - /// - /// let request = Wine.order(Column("name")) - /// let controller = FetchedRecordsController( - /// dbQueue, - /// request: request) - /// - /// The records are compared by primary key (single-column primary key, - /// compound primary key, or implicit rowid). For a database table which - /// has an `id` primary key, this initializer is equivalent to: - /// - /// // Assuming the wine table has an `id` primary key: - /// let controller = FetchedRecordsController( - /// dbQueue, - /// request: request, - /// isSameRecord: { (wine1, wine2) in wine1.id == wine2.id }) - /// - /// - parameters: - /// - databaseWriter: A DatabaseWriter (DatabaseQueue, or DatabasePool) - /// - request: A fetch request. - /// - queue: A serial dispatch queue (defaults to the main queue) - /// - /// The fetched records controller tracking callbacks will be - /// notified of changes in this queue. The controller itself must be - /// used from this queue. - public convenience init( - _ databaseWriter: DatabaseWriter, - request: Request, - queue: DispatchQueue = .main) throws - where Request: FetchRequest, Request.RowDecoder == Record - { - // Builds a function that returns true if and only if two items - // have the same primary key and primary keys contain at least one - // non-null value. - let itemsAreIdenticalFactory: ItemComparatorFactory = { db in - // Extract primary key columns from database table - let columns = try db.primaryKey(Record.databaseTableName).columns - - // Compare primary keys - assert(!columns.isEmpty) - return { (lItem, rItem) in - var notNullValue = false - for column in columns { - let lValue: DatabaseValue = lItem.row[column] - let rValue: DatabaseValue = rItem.row[column] - if lValue != rValue { - // different primary keys - return false - } - if !lValue.isNull || !rValue.isNull { - notNullValue = true - } - } - // identical primary keys iff at least one value is not null - return notNullValue - } - } - try self.init( - databaseWriter, - request: request, - queue: queue, - itemsAreIdenticalFactory: itemsAreIdenticalFactory) - } -} - - -// MARK: - FetchedRecordsObserver - -/// FetchedRecordsController adopts TransactionObserverType so that it can -/// monitor changes to its fetched records. -private final class FetchedRecordsObserver : TransactionObserver { - var isValid: Bool - var needsComputeChanges: Bool - var items: [Item]! // ought to be not nil when observer has started tracking transactions - let queue: DispatchQueue // protects items - let region: DatabaseRegion - var fetchAndNotifyChanges: (FetchedRecordsObserver) -> () - - init(region: DatabaseRegion, fetchAndNotifyChanges: @escaping (FetchedRecordsObserver) -> ()) { - self.isValid = true - self.items = nil - self.needsComputeChanges = false - self.queue = DispatchQueue(label: "GRDB.FetchedRecordsObserver") - self.region = region - self.fetchAndNotifyChanges = fetchAndNotifyChanges - } - - func invalidate() { - isValid = false - } - - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - return region.isModified(byEventsOfKind: eventKind) - } - - /// Part of the TransactionObserverType protocol - func databaseDidChange(with event: DatabaseEvent) { - if region.isModified(by: event) { - needsComputeChanges = true - stopObservingDatabaseChangesUntilNextTransaction() - } - } - - /// Part of the TransactionObserverType protocol - func databaseDidRollback(_ db: Database) { - needsComputeChanges = false - } - - /// Part of the TransactionObserverType protocol - func databaseDidCommit(_ db: Database) { - // The databaseDidCommit callback is called in the database writer - // dispatch queue, which is serialized: it is guaranteed to process the - // last database transaction. - - // Were observed tables modified? - guard needsComputeChanges else { return } - needsComputeChanges = false - - fetchAndNotifyChanges(self) - } -} - - -// MARK: - Changes - -private func makeFetchFunction( - controller: FetchedRecordsController, - fetchAlongside: @escaping (Database) throws -> T, - willProcessTransaction: @escaping () -> (), - completion: @escaping (Result<(fetchedItems: [Item], fetchedAlongside: T, observer: FetchedRecordsObserver)>) -> () - ) -> (FetchedRecordsObserver) -> () -{ - // Make sure we keep a weak reference to the fetched records controller, - // so that the user can use unowned references in callbacks: - // - // controller.trackChanges { [unowned self] ... } - // - // Should controller become strong at any point before callbacks are - // called, such unowned reference would have an opportunity to crash. - return { [weak controller] observer in - // Return if observer has been invalidated, or if fetched records - // controller has been deallocated - guard observer.isValid, - let request = controller?.request, - let databaseWriter = controller?.databaseWriter - else { return } - - willProcessTransaction() - - // Perform a concurrent read so that writer dispatch queue is released - // as soon as possible. - let future = databaseWriter.concurrentRead { db in - try (fetchedItems: request.fetchAll(db), - fetchedAlongside: fetchAlongside(db)) - } - - // Dispatch processing immediately on observer.queue in order to - // process fetched values in the same order as transactions: - observer.queue.async { [weak observer] in - // Return if observer has been deallocated or invalidated - guard let observer = observer, - observer.isValid - else { return } - - do { - // Wait for concurrent read to complete - let values = try future.wait() - - completion(.success(( - fetchedItems: values.fetchedItems, - fetchedAlongside: values.fetchedAlongside, - observer: observer))) - } catch { - completion(.failure(error)) - } - } - } -} - -private func makeFetchAndNotifyChangesFunction( - controller: FetchedRecordsController, - fetchAlongside: @escaping (Database) throws -> T, - itemsAreIdentical: @escaping ItemComparator, - willProcessTransaction: @escaping () -> (), - willChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())?, - onChange: ((FetchedRecordsController, Record, FetchedRecordChange) -> ())?, - didChange: ((FetchedRecordsController, _ fetchedAlongside: T) -> ())?, - didProcessTransaction: @escaping () -> () - ) -> (FetchedRecordsObserver) -> () -{ - // Make sure we keep a weak reference to the fetched records controller, - // so that the user can use unowned references in callbacks: - // - // controller.trackChanges { [unowned self] ... } - // - // Should controller become strong at any point before callbacks are - // called, such unowned reference would have an opportunity to crash. - return makeFetchFunction(controller: controller, fetchAlongside: fetchAlongside, willProcessTransaction: willProcessTransaction) { [weak controller] result in - // Return if fetched records controller has been deallocated - guard let callbackQueue = controller?.queue else { return } - - switch result { - case .failure(let error): - callbackQueue.async { - // Now we can retain controller - guard let strongController = controller else { return } - strongController.errorHandler?(strongController, error) - didProcessTransaction() - } - - case .success((fetchedItems: let fetchedItems, fetchedAlongside: let fetchedAlongside, observer: let observer)): - // Return if there is no change - let changes: [ItemChange] - if onChange != nil { - // Compute table view changes - changes = computeChanges(from: observer.items, to: fetchedItems, itemsAreIdentical: itemsAreIdentical) - if changes.isEmpty { return } - } else { - // Don't compute changes: just look for a row difference: - if identicalItemArrays(fetchedItems, observer.items) { return } - changes = [] - } - - // Ready for next check - observer.items = fetchedItems - - callbackQueue.async { [weak observer] in - // Return if observer has been invalidated - guard let strongObserver = observer else { return } - guard strongObserver.isValid else { return } - - // Now we can retain controller - guard let strongController = controller else { return } - - // Notify changes - willChange?(strongController, fetchedAlongside) - strongController.fetchedItems = fetchedItems - if let onChange = onChange { - for change in changes { - onChange(strongController, change.record, change.fetchedRecordChange) - } - } - didChange?(strongController, fetchedAlongside) - didProcessTransaction() - } - } - } -} - -private func computeChanges(from s: [Item], to t: [Item], itemsAreIdentical: ItemComparator) -> [ItemChange] { - let m = s.count - let n = t.count - - // Fill first row and column of insertions and deletions. - - var d: [[[ItemChange]]] = Array(repeating: Array(repeating: [], count: n + 1), count: m + 1) - - var changes = [ItemChange]() - for (row, item) in s.enumerated() { - let deletion = ItemChange.deletion(item: item, indexPath: IndexPath(indexes: [0, row])) - changes.append(deletion) - d[row + 1][0] = changes - } - - changes.removeAll() - for (col, item) in t.enumerated() { - let insertion = ItemChange.insertion(item: item, indexPath: IndexPath(indexes: [0, col])) - changes.append(insertion) - d[0][col + 1] = changes - } - - if m == 0 || n == 0 { - // Pure deletions or insertions - return d[m][n] - } - - // Fill body of matrix. - for tx in 0..], itemsAreIdentical: ItemComparator) -> [ItemChange] { - - /// Returns a potential .move or .update if *change* has a matching change in *changes*: - /// If *change* is a deletion or an insertion, and there is a matching inverse - /// insertion/deletion with the same value in *changes*, a corresponding .move or .update is returned. - /// As a convenience, the index of the matched change is returned as well. - func merge(change: ItemChange, in changes: [ItemChange], itemsAreIdentical: ItemComparator) -> (mergedChange: ItemChange, mergedIndex: Int)? { - - /// Returns the changes between two rows: a dictionary [key: oldValue] - /// Precondition: both rows have the same columns - func changedValues(from oldRow: Row, to newRow: Row) -> [String: DatabaseValue] { - var changedValues: [String: DatabaseValue] = [:] - for (column, newValue) in newRow { - let oldValue: DatabaseValue? = oldRow[column] - if newValue != oldValue { - changedValues[column] = oldValue - } - } - return changedValues - } - - switch change { - case .insertion(let newItem, let newIndexPath): - // Look for a matching deletion - for (index, otherChange) in changes.enumerated() { - guard case .deletion(let oldItem, let oldIndexPath) = otherChange else { continue } - guard itemsAreIdentical(oldItem, newItem) else { continue } - let rowChanges = changedValues(from: oldItem.row, to: newItem.row) - if oldIndexPath == newIndexPath { - return (ItemChange.update(item: newItem, indexPath: oldIndexPath, changes: rowChanges), index) - } else { - return (ItemChange.move(item: newItem, indexPath: oldIndexPath, newIndexPath: newIndexPath, changes: rowChanges), index) - } - } - return nil - - case .deletion(let oldItem, let oldIndexPath): - // Look for a matching insertion - for (index, otherChange) in changes.enumerated() { - guard case .insertion(let newItem, let newIndexPath) = otherChange else { continue } - guard itemsAreIdentical(oldItem, newItem) else { continue } - let rowChanges = changedValues(from: oldItem.row, to: newItem.row) - if oldIndexPath == newIndexPath { - return (ItemChange.update(item: newItem, indexPath: oldIndexPath, changes: rowChanges), index) - } else { - return (ItemChange.move(item: newItem, indexPath: oldIndexPath, newIndexPath: newIndexPath, changes: rowChanges), index) - } - } - return nil - - default: - return nil - } - } - - // Updates must be pushed at the end - var mergedChanges: [ItemChange] = [] - var updateChanges: [ItemChange] = [] - for change in changes { - if let (mergedChange, mergedIndex) = merge(change: change, in: mergedChanges, itemsAreIdentical: itemsAreIdentical) { - mergedChanges.remove(at: mergedIndex) - switch mergedChange { - case .update: - updateChanges.append(mergedChange) - default: - mergedChanges.append(mergedChange) - } - } else { - mergedChanges.append(change) - } - } - return mergedChanges + updateChanges - } - - return standardize(changes: d[m][n], itemsAreIdentical: itemsAreIdentical) -} - -private func identicalItemArrays(_ lhs: [Item], _ rhs: [Item]) -> Bool { - guard lhs.count == rhs.count else { - return false - } - for (lhs, rhs) in zip(lhs, rhs) { - if lhs.row != rhs.row { - return false - } - } - return true -} - - -// MARK: - UITableView Support - -private typealias ItemComparator = (Item, Item) -> Bool -private typealias ItemComparatorFactory = (Database) throws -> ItemComparator - -extension FetchedRecordsController { - - // MARK: - Accessing Records - - /// Returns the object at the given index path. - /// - /// - parameter indexPath: An index path in the fetched records. - /// - /// If indexPath does not describe a valid index path in the fetched - /// records, a fatal error is raised. - public func record(at indexPath: IndexPath) -> Record { - guard let fetchedItems = fetchedItems else { - // Programmer error - fatalError("performFetch() has not been called.") - } - return fetchedItems[indexPath[1]].record - } - - - // MARK: - Querying Sections Information - - /// The sections for the fetched records. - /// - /// You typically use the sections array when implementing - /// UITableViewDataSource methods, such as `numberOfSectionsInTableView`. - /// - /// The sections array is never empty, even when there are no fetched - /// records. In this case, there is a single empty section. - public var sections: [FetchedRecordsSectionInfo] { - // We only support a single section so far. - // We also return a single section when there are no fetched - // records, just like NSFetchedResultsController. - return [FetchedRecordsSectionInfo(controller: self)] - } -} - -extension FetchedRecordsController where Record: EncodableRecord { - - /// Returns the indexPath of a given record. - /// - /// - returns: The index path of *record* in the fetched records, or nil - /// if record could not be found. - public func indexPath(for record: Record) -> IndexPath? { - let item = Item(row: Row(record)) - guard let fetchedItems = fetchedItems, let index = fetchedItems.firstIndex(where: { itemsAreIdentical($0, item) }) else { - return nil - } - return IndexPath(indexes: [0, index]) - } -} - -private enum ItemChange { - case insertion(item: Item, indexPath: IndexPath) - case deletion(item: Item, indexPath: IndexPath) - case move(item: Item, indexPath: IndexPath, newIndexPath: IndexPath, changes: [String: DatabaseValue]) - case update(item: Item, indexPath: IndexPath, changes: [String: DatabaseValue]) -} - -extension ItemChange { - var record: T { - switch self { - case .insertion(item: let item, indexPath: _): - return item.record - case .deletion(item: let item, indexPath: _): - return item.record - case .move(item: let item, indexPath: _, newIndexPath: _, changes: _): - return item.record - case .update(item: let item, indexPath: _, changes: _): - return item.record - } - } - - var fetchedRecordChange: FetchedRecordChange { - switch self { - case .insertion(item: _, indexPath: let indexPath): - return .insertion(indexPath: indexPath) - case .deletion(item: _, indexPath: let indexPath): - return .deletion(indexPath: indexPath) - case .move(item: _, indexPath: let indexPath, newIndexPath: let newIndexPath, changes: let changes): - return .move(indexPath: indexPath, newIndexPath: newIndexPath, changes: changes) - case .update(item: _, indexPath: let indexPath, changes: let changes): - return .update(indexPath: indexPath, changes: changes) - } - } -} - -extension ItemChange: CustomStringConvertible { - var description: String { - switch self { - case .insertion(let item, let indexPath): - return "Insert \(item) at \(indexPath)" - - case .deletion(let item, let indexPath): - return "Delete \(item) from \(indexPath)" - - case .move(let item, let indexPath, let newIndexPath, changes: let changes): - return "Move \(item) from \(indexPath) to \(newIndexPath) with changes: \(changes)" - - case .update(let item, let indexPath, let changes): - return "Update \(item) at \(indexPath) with changes: \(changes)" - } - } -} - -/// A record change, given by a FetchedRecordsController to its change callback. -/// -/// The move and update events hold a *changes* dictionary, whose keys are -/// column names, and values the old values that have been changed. -public enum FetchedRecordChange { - - /// An insertion event, at given indexPath. - case insertion(indexPath: IndexPath) - - /// A deletion event, at given indexPath. - case deletion(indexPath: IndexPath) - - /// A move event, from indexPath to newIndexPath. The *changes* are a - /// dictionary whose keys are column names, and values the old values that - /// have been changed. - case move(indexPath: IndexPath, newIndexPath: IndexPath, changes: [String: DatabaseValue]) - - /// An update event, at given indexPath. The *changes* are a dictionary - /// whose keys are column names, and values the old values that have - /// been changed. - case update(indexPath: IndexPath, changes: [String: DatabaseValue]) -} - -extension FetchedRecordChange: CustomStringConvertible { - public var description: String { - switch self { - case .insertion(let indexPath): - return "Insertion at \(indexPath)" - - case .deletion(let indexPath): - return "Deletion from \(indexPath)" - - case .move(let indexPath, let newIndexPath, changes: let changes): - return "Move from \(indexPath) to \(newIndexPath) with changes: \(changes)" - - case .update(let indexPath, let changes): - return "Update at \(indexPath) with changes: \(changes)" - } - } -} - -/// A section given by a FetchedRecordsController. -public struct FetchedRecordsSectionInfo { - fileprivate let controller: FetchedRecordsController - - /// The number of records (rows) in the section. - public var numberOfRecords: Int { - guard let items = controller.fetchedItems else { - // Programmer error - fatalError("the performFetch() method must be called before accessing section contents") - } - return items.count - } - - /// The array of records in the section. - public var records: [Record] { - guard let items = controller.fetchedItems else { - // Programmer error - fatalError("the performFetch() method must be called before accessing section contents") - } - return items.map { $0.record } - } -} - - -// MARK: - Item - -private final class Item : FetchableRecord, Equatable { - let row: Row - - // Records are lazily loaded - lazy var record: T = T(row: self.row) - - init(row: Row) { - self.row = row.copy() - } - - static func == (lhs: Item, rhs: Item) -> Bool { - return lhs.row == rhs.row - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/PersistableRecord.swift b/Example/Pods/GRDB.swift/GRDB/Record/PersistableRecord.swift deleted file mode 100755 index 1fb4772..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/PersistableRecord.swift +++ /dev/null @@ -1,986 +0,0 @@ -extension Database.ConflictResolution { - @usableFromInline - var invalidatesLastInsertedRowID: Bool { - switch self { - case .abort, .fail, .rollback, .replace: - return false - case .ignore: - // Statement may have succeeded without inserting any row - return true - } - } -} - -/// An error thrown by a type that adopts PersistableRecord. -public enum PersistenceError: Error, CustomStringConvertible { - - /// Thrown by MutablePersistableRecord.update() when no matching row could be - /// found in the database. - /// - /// - databaseTableName: the table of the unfound record - /// - key: the key of the unfound record (column and values) - case recordNotFound(databaseTableName: String, key: [String: DatabaseValue]) -} - -// CustomStringConvertible -extension PersistenceError { - /// :nodoc: - public var description: String { - switch self { - case let .recordNotFound(databaseTableName: databaseTableName, key: key): - let row = Row(key) // For nice output - return "Key not found in table \(databaseTableName): \(row.description)" - } - } -} - -/// The MutablePersistableRecord protocol uses this type in order to handle SQLite -/// conflicts when records are inserted or updated. -/// -/// See `MutablePersistableRecord.persistenceConflictPolicy`. -/// -/// See https://www.sqlite.org/lang_conflict.html -public struct PersistenceConflictPolicy { - /// The conflict resolution algorithm for insertions - public let conflictResolutionForInsert: Database.ConflictResolution - - /// The conflict resolution algorithm for updates - public let conflictResolutionForUpdate: Database.ConflictResolution - - /// Creates a policy - public init(insert: Database.ConflictResolution = .abort, update: Database.ConflictResolution = .abort) { - self.conflictResolutionForInsert = insert - self.conflictResolutionForUpdate = update - } -} - -/// Types that adopt MutablePersistableRecord can be inserted, updated, and deleted. -public protocol MutablePersistableRecord : EncodableRecord, TableRecord { - /// The policy that handles SQLite conflicts when records are inserted - /// or updated. - /// - /// This property is optional: its default value uses the ABORT policy - /// for both insertions and updates, and has GRDB generate regular - /// INSERT and UPDATE queries. - /// - /// If insertions are resolved with .ignore policy, the - /// `didInsert(with:for:)` method is not called upon successful insertion, - /// even if a row was actually inserted without any conflict. - /// - /// See https://www.sqlite.org/lang_conflict.html - static var persistenceConflictPolicy: PersistenceConflictPolicy { get } - - /// Notifies the record that it was succesfully inserted. - /// - /// Do not call this method directly: it is called for you, in a protected - /// dispatch queue, with the inserted RowID and the eventual - /// INTEGER PRIMARY KEY column name. - /// - /// This method is optional: the default implementation does nothing. - /// - /// struct Player : MutablePersistableRecord { - /// var id: Int64? - /// var name: String? - /// - /// mutating func didInsert(with rowID: Int64, for column: String?) { - /// self.id = rowID - /// } - /// } - /// - /// - parameters: - /// - rowID: The inserted rowID. - /// - column: The name of the eventual INTEGER PRIMARY KEY column. - mutating func didInsert(with rowID: Int64, for column: String?) - - // MARK: - CRUD - - /// Executes an INSERT statement. - /// - /// This method is guaranteed to have inserted a row in the database if it - /// returns without error. - /// - /// Upon successful insertion, the didInsert(with:for:) method - /// is called with the inserted RowID and the eventual INTEGER PRIMARY KEY - /// column name. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of insert(). In their implementation, it is recommended - /// that they invoke the performInsert() method. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError whenever an SQLite error occurs. - mutating func insert(_ db: Database) throws - - /// Executes an UPDATE statement. - /// - /// This method is guaranteed to have updated a row in the database if it - /// returns without error. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of update(). In their implementation, it is recommended - /// that they invoke the performUpdate() method. - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database. - func update(_ db: Database, columns: Set) throws - - /// Executes an INSERT or an UPDATE statement so that `self` is saved in - /// the database. - /// - /// If the receiver has a non-nil primary key and a matching row in the - /// database, this method performs an update. - /// - /// Otherwise, performs an insert. - /// - /// This method is guaranteed to have inserted or updated a row in the - /// database if it returns without error. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of save(). In their implementation, it is recommended - /// that they invoke the performSave() method. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError whenever an SQLite error occurs, or errors - /// thrown by update(). - mutating func save(_ db: Database) throws - - /// Executes a DELETE statement. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of delete(). In their implementation, it is recommended - /// that they invoke the performDelete() method. - /// - /// - parameter db: A database connection. - /// - returns: Whether a database row was deleted. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult - func delete(_ db: Database) throws -> Bool - - /// Returns true if and only if the primary key matches a row in - /// the database. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of exists(). In their implementation, it is recommended - /// that they invoke the performExists() method. - /// - /// - parameter db: A database connection. - /// - returns: Whether the primary key matches a row in the database. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - func exists(_ db: Database) throws -> Bool -} - -extension MutablePersistableRecord { - /// Describes the conflict policy for insertions and updates. - /// - /// The default value specifies ABORT policy for both insertions and - /// updates, which has GRDB generate regular INSERT and UPDATE queries. - public static var persistenceConflictPolicy: PersistenceConflictPolicy { - return PersistenceConflictPolicy(insert: .abort, update: .abort) - } - - /// Notifies the record that it was succesfully inserted. - /// - /// The default implementation does nothing. - public mutating func didInsert(with rowID: Int64, for column: String?) { - } - - // MARK: - CRUD - - /// Executes an INSERT statement. - /// - /// The default implementation for insert() invokes performInsert(). - public mutating func insert(_ db: Database) throws { - try performInsert(db) - } - - /// Executes an UPDATE statement. - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database. - public func update(_ db: Database, columns: Set) throws { - try performUpdate(db, columns: columns) - } - - /// Executes an UPDATE statement. - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database. - public func update(_ db: Database, columns: Sequence) throws where Sequence.Element: ColumnExpression { - try update(db, columns: Set(columns.map { $0.name })) - } - - /// Executes an UPDATE statement. - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database. - public func update(_ db: Database, columns: Sequence) throws where Sequence.Element == String { - try update(db, columns: Set(columns)) - } - - /// Executes an UPDATE statement that updates all table columns. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database. - public func update(_ db: Database) throws { - let databaseTableName = type(of: self).databaseTableName - let columns = try db.columns(in: databaseTableName) - try update(db, columns: Set(columns.map { $0.name })) - } - - /// If the record has any difference from the other record, executes an - /// UPDATE statement so that those differences and only those difference are - /// saved in the database. - /// - /// This method is guaranteed to have saved the eventual differences in the - /// database if it returns without error. - /// - /// For example: - /// - /// if let oldPlayer = try Player.fetchOne(db, key: 42) { - /// var newPlayer = oldPlayer - /// newPlayer.score += 10 - /// newPlayer.hasAward = true - /// try newPlayer.updateChanges(db, from: oldRecord) - /// } - /// - /// - parameter db: A database connection. - /// - parameter record: The comparison record. - /// - returns: Whether the record had changes. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database and record could not be updated. - /// - SeeAlso: updateChanges(_:with:) - @discardableResult - public func updateChanges(_ db: Database, from record: Record) throws -> Bool { - return try updateChanges(db, from: PersistenceContainer(db, record)) - } - - /// Mutates the record according to the provided closure, and then, if the - /// record has any difference from its previous version, executes an - /// UPDATE statement so that those differences and only those difference are - /// saved in the database. - /// - /// This method is guaranteed to have saved the eventual differences in the - /// database if it returns without error. - /// - /// For example: - /// - /// if var player = try Player.fetchOne(db, key: 42) { - /// try player.updateChanges(db) { - /// $0.score += 10 - /// $0.hasAward = true - /// } - /// } - /// - /// - parameter db: A database connection. - /// - parameter change: A closure that modifies the record. - /// - returns: Whether the record had changes. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database and record could not be updated. - @discardableResult - public mutating func updateChanges(_ db: Database, with change: (inout Self) throws -> Void) throws -> Bool { - let container = try PersistenceContainer(db, self) - try change(&self) - return try updateChanges(db, from: container) - } - - /// Executes an INSERT or an UPDATE statement so that `self` is saved in - /// the database. - /// - /// The default implementation for save() invokes performSave(). - public mutating func save(_ db: Database) throws { - try performSave(db) - } - - /// Executes a DELETE statement. - /// - /// The default implementation for delete() invokes performDelete(). - @discardableResult - public func delete(_ db: Database) throws -> Bool { - return try performDelete(db) - } - - /// Returns true if and only if the primary key matches a row in - /// the database. - /// - /// The default implementation for exists() invokes performExists(). - public func exists(_ db: Database) throws -> Bool { - return try performExists(db) - } - - // MARK: - Record Comparison - - @discardableResult - fileprivate func updateChanges(_ db: Database, from container: PersistenceContainer) throws -> Bool { - let changes = try PersistenceContainer(db, self).changesIterator(from: container) - let changedColumns: Set = changes.reduce(into: []) { $0.insert($1.0) } - if changedColumns.isEmpty { - return false - } - try update(db, columns: changedColumns) - return true - } - - // MARK: - CRUD Internals - - /// Return a non-nil dictionary if record has a non-null primary key - @usableFromInline - func primaryKey(_ db: Database) throws -> [String: DatabaseValue]? { - let databaseTableName = type(of: self).databaseTableName - let primaryKeyInfo = try db.primaryKey(databaseTableName) - let container = try PersistenceContainer(db, self) - let primaryKey = Dictionary(uniqueKeysWithValues: primaryKeyInfo.columns.map { - ($0, container[caseInsensitive: $0]?.databaseValue ?? .null) - }) - if primaryKey.allSatisfy({ $0.value.isNull }) { - return nil - } - return primaryKey - } - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt MutablePersistableRecord. - /// - /// performInsert() provides the default implementation for insert(). Types - /// that adopt MutablePersistableRecord can invoke performInsert() in their - /// implementation of insert(). They should not provide their own - /// implementation of performInsert(). - @inlinable - public mutating func performInsert(_ db: Database) throws { - let conflictResolutionForInsert = type(of: self).persistenceConflictPolicy.conflictResolutionForInsert - let dao = try DAO(db, self) - try dao.insertStatement(onConflict: conflictResolutionForInsert).execute() - - if !conflictResolutionForInsert.invalidatesLastInsertedRowID { - didInsert(with: db.lastInsertedRowID, for: dao.primaryKey.rowIDColumn) - } - } - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt MutablePersistableRecord. - /// - /// performUpdate() provides the default implementation for update(). Types - /// that adopt MutablePersistableRecord can invoke performUpdate() in their - /// implementation of update(). They should not provide their own - /// implementation of performUpdate(). - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database. - @inlinable - public func performUpdate(_ db: Database, columns: Set) throws { - let dao = try DAO(db, self) - guard let statement = try dao.updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else { - // Nil primary key - throw dao.makeRecordNotFoundError() - } - try statement.execute() - if db.changesCount == 0 { - throw dao.makeRecordNotFoundError() - } - } - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt MutablePersistableRecord. - /// - /// performSave() provides the default implementation for save(). Types - /// that adopt MutablePersistableRecord can invoke performSave() in their - /// implementation of save(). They should not provide their own - /// implementation of performSave(). - /// - /// This default implementation forwards the job to `update` or `insert`. - @inlinable - public mutating func performSave(_ db: Database) throws { - // Call self.insert and self.update so that we support classes that - // override those methods. - if let key = try primaryKey(db) { - do { - try update(db) - } catch PersistenceError.recordNotFound(databaseTableName: type(of: self).databaseTableName, key: key) { - try insert(db) - } - } else { - try insert(db) - } - } - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt MutablePersistableRecord. - /// - /// performDelete() provides the default implementation for deelte(). Types - /// that adopt MutablePersistableRecord can invoke performDelete() in - /// their implementation of delete(). They should not provide their own - /// implementation of performDelete(). - @inlinable - public func performDelete(_ db: Database) throws -> Bool { - guard let statement = try DAO(db, self).deleteStatement() else { - // Nil primary key - return false - } - try statement.execute() - return db.changesCount > 0 - } - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt MutablePersistableRecord. - /// - /// performExists() provides the default implementation for exists(). Types - /// that adopt MutablePersistableRecord can invoke performExists() in - /// their implementation of exists(). They should not provide their own - /// implementation of performExists(). - @inlinable - public func performExists(_ db: Database) throws -> Bool { - guard let statement = try DAO(db, self).existsStatement() else { - // Nil primary key - return false - } - return try Row.fetchOne(statement) != nil - } -} - -extension MutablePersistableRecord where Self: AnyObject { - - // MARK: - Record Comparison - - /// Mutates the record according to the provided closure, and then, if the - /// record has any difference from its previous version, executes an - /// UPDATE statement so that those differences and only those difference are - /// saved in the database. - /// - /// This method is guaranteed to have saved the eventual differences in the - /// database if it returns without error. - /// - /// For example: - /// - /// if let player = try Player.fetchOne(db, key: 42) { - /// try player.updateChanges(db) { - /// $0.score += 10 - /// $0.hasAward = true - /// } - /// } - /// - /// - parameter db: A database connection. - /// - parameter change: A closure that modifies the record. - /// - returns: Whether the record had changes. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database and record could not be updated. - @discardableResult - public func updateChanges(_ db: Database, with change: (Self) throws -> Void) throws -> Bool { - let container = try PersistenceContainer(db, self) - try change(self) - return try updateChanges(db, from: container) - } -} - -extension MutablePersistableRecord { - - // MARK: - Deleting All - - /// Deletes all records; returns the number of deleted rows. - /// - /// - parameter db: A database connection. - /// - returns: The number of deleted rows - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult - public static func deleteAll(_ db: Database) throws -> Int { - return try all().deleteAll(db) - } -} - -extension MutablePersistableRecord { - - // MARK: - Deleting by Single-Column Primary Key - - /// Delete records identified by their primary keys; returns the number of - /// deleted rows. - /// - /// // DELETE FROM player WHERE id IN (1, 2, 3) - /// try Player.deleteAll(db, keys: [1, 2, 3]) - /// - /// // DELETE FROM country WHERE code IN ('FR', 'US', 'DE') - /// try Country.deleteAll(db, keys: ["FR", "US", "DE"]) - /// - /// When the table has no explicit primary key, GRDB uses the hidden - /// "rowid" column: - /// - /// // DELETE FROM document WHERE rowid IN (1, 2, 3) - /// try Document.deleteAll(db, keys: [1, 2, 3]) - /// - /// - parameters: - /// - db: A database connection. - /// - keys: A sequence of primary keys. - /// - returns: The number of deleted rows - @discardableResult - public static func deleteAll(_ db: Database, keys: Sequence) throws -> Int where Sequence.Element: DatabaseValueConvertible { - let keys = Array(keys) - if keys.isEmpty { - // Avoid hitting the database - return 0 - } - return try filter(keys: keys).deleteAll(db) - } - - /// Delete a record, identified by its primary key; returns whether a - /// database row was deleted. - /// - /// // DELETE FROM player WHERE id = 123 - /// try Player.deleteOne(db, key: 123) - /// - /// // DELETE FROM country WHERE code = 'FR' - /// try Country.deleteOne(db, key: "FR") - /// - /// When the table has no explicit primary key, GRDB uses the hidden - /// "rowid" column: - /// - /// // DELETE FROM document WHERE rowid = 1 - /// try Document.deleteOne(db, key: 1) - /// - /// - parameters: - /// - db: A database connection. - /// - key: A primary key value. - /// - returns: Whether a database row was deleted. - @discardableResult - public static func deleteOne(_ db: Database, key: PrimaryKeyType?) throws -> Bool { - guard let key = key else { - // Avoid hitting the database - return false - } - return try deleteAll(db, keys: [key]) > 0 - } -} - -extension MutablePersistableRecord { - - // MARK: - Deleting by Key - - /// Delete records identified by the provided unique keys (primary key or - /// any key with a unique index on it); returns the number of deleted rows. - /// - /// try Player.deleteAll(db, keys: [["email": "a@example.com"], ["email": "b@example.com"]]) - /// - /// - parameters: - /// - db: A database connection. - /// - keys: An array of key dictionaries. - /// - returns: The number of deleted rows - @discardableResult - public static func deleteAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> Int { - if keys.isEmpty { - // Avoid hitting the database - return 0 - } - return try filter(keys: keys).deleteAll(db) - } - - /// Delete a record, identified by a unique key (the primary key or any key - /// with a unique index on it); returns whether a database row was deleted. - /// - /// Player.deleteOne(db, key: ["name": Arthur"]) - /// - /// - parameters: - /// - db: A database connection. - /// - key: A dictionary of values. - /// - returns: Whether a database row was deleted. - @discardableResult - public static func deleteOne(_ db: Database, key: [String: DatabaseValueConvertible?]) throws -> Bool { - return try deleteAll(db, keys: [key]) > 0 - } -} - -// MARK: - PersistableRecord - -/// Types that adopt PersistableRecord can be inserted, updated, and deleted. -/// -/// This protocol is intented for types that don't have an INTEGER PRIMARY KEY. -/// -/// Unlike MutablePersistableRecord, the insert() and save() methods are not -/// mutating methods. -public protocol PersistableRecord : MutablePersistableRecord { - - /// Notifies the record that it was succesfully inserted. - /// - /// Do not call this method directly: it is called for you, in a protected - /// dispatch queue, with the inserted RowID and the eventual - /// INTEGER PRIMARY KEY column name. - /// - /// This method is optional: the default implementation does nothing. - /// - /// If you need a mutating variant of this method, adopt the - /// MutablePersistableRecord protocol instead. - /// - /// - parameters: - /// - rowID: The inserted rowID. - /// - column: The name of the eventual INTEGER PRIMARY KEY column. - func didInsert(with rowID: Int64, for column: String?) - - /// Executes an INSERT statement. - /// - /// This method is guaranteed to have inserted a row in the database if it - /// returns without error. - /// - /// Upon successful insertion, the didInsert(with:for:) method - /// is called with the inserted RowID and the eventual INTEGER PRIMARY KEY - /// column name. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of insert(). In their implementation, it is recommended - /// that they invoke the performInsert() method. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError whenever an SQLite error occurs. - func insert(_ db: Database) throws - - /// Executes an INSERT or an UPDATE statement so that `self` is saved in - /// the database. - /// - /// If the receiver has a non-nil primary key and a matching row in the - /// database, this method performs an update. - /// - /// Otherwise, performs an insert. - /// - /// This method is guaranteed to have inserted or updated a row in the - /// database if it returns without error. - /// - /// This method has a default implementation, so your adopting types don't - /// have to implement it. Yet your types can provide their own - /// implementation of save(). In their implementation, it is recommended - /// that they invoke the performSave() method. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError whenever an SQLite error occurs, or errors - /// thrown by update(). - func save(_ db: Database) throws -} - -extension PersistableRecord { - - /// Notifies the record that it was succesfully inserted. - /// - /// The default implementation does nothing. - public func didInsert(with rowID: Int64, for column: String?) { - } - - // MARK: - Immutable CRUD - - /// Executes an INSERT statement. - /// - /// The default implementation for insert() invokes performInsert(). - public func insert(_ db: Database) throws { - try performInsert(db) - } - - /// Executes an INSERT or an UPDATE statement so that `self` is saved in - /// the database. - /// - /// The default implementation for save() invokes performSave(). - public func save(_ db: Database) throws { - try performSave(db) - } - - // MARK: - Immutable CRUD Internals - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt PersistableRecord. - /// - /// performInsert() provides the default implementation for insert(). Types - /// that adopt PersistableRecord can invoke performInsert() in their - /// implementation of insert(). They should not provide their own - /// implementation of performInsert(). - @inlinable - public func performInsert(_ db: Database) throws { - let conflictResolutionForInsert = type(of: self).persistenceConflictPolicy.conflictResolutionForInsert - let dao = try DAO(db, self) - try dao.insertStatement(onConflict: conflictResolutionForInsert).execute() - - if !conflictResolutionForInsert.invalidatesLastInsertedRowID { - didInsert(with: db.lastInsertedRowID, for: dao.primaryKey.rowIDColumn) - } - } - - /// Don't invoke this method directly: it is an internal method for types - /// that adopt PersistableRecord. - /// - /// performSave() provides the default implementation for save(). Types - /// that adopt PersistableRecord can invoke performSave() in their - /// implementation of save(). They should not provide their own - /// implementation of performSave(). - /// - /// This default implementation forwards the job to `update` or `insert`. - @inlinable - public func performSave(_ db: Database) throws { - // Call self.insert and self.update so that we support classes that - // override those methods. - if let key = try primaryKey(db) { - do { - try update(db) - } catch PersistenceError.recordNotFound(databaseTableName: type(of: self).databaseTableName, key: key) { - try insert(db) - } - } else { - try insert(db) - } - } -} - -// MARK: - DAO - -extension PersistenceContainer { - /// Convenience initializer from a database connection and a record - init(_ db: Database,_ record: Record) throws { - let databaseTableName = type(of: record).databaseTableName - let columnCount = try db.columns(in: databaseTableName).count - self.init(minimumCapacity: columnCount) - record.encode(to: &self) - } -} - -/// DAO takes care of PersistableRecord CRUD -@usableFromInline -final class DAO { - /// The database - let db: Database - - /// DAO keeps a copy the record's persistenceContainer, so that this - /// dictionary is built once whatever the database operation. It is - /// guaranteed to have at least one (key, value) pair. - let persistenceContainer: PersistenceContainer - - /// The table name - let databaseTableName: String - - /// The table primary key info - @usableFromInline let primaryKey: PrimaryKeyInfo - - @usableFromInline - init(_ db: Database, _ record: Record) throws { - self.db = db - databaseTableName = type(of: record).databaseTableName - primaryKey = try db.primaryKey(databaseTableName) - persistenceContainer = try PersistenceContainer(db, record) - GRDBPrecondition(!persistenceContainer.isEmpty, "\(type(of: record)): invalid empty persistence container") - } - - @usableFromInline - func insertStatement(onConflict: Database.ConflictResolution) throws -> UpdateStatement { - let query = InsertQuery( - onConflict: onConflict, - tableName: databaseTableName, - insertedColumns: persistenceContainer.columns) - let statement = try db.internalCachedUpdateStatement(sql: query.sql) - statement.unsafeSetArguments(StatementArguments(persistenceContainer.values)) - return statement - } - - /// Returns nil if and only if primary key is nil - @usableFromInline - func updateStatement(columns: Set, onConflict: Database.ConflictResolution) throws -> UpdateStatement? { - // Fail early if primary key does not resolve to a database row. - let primaryKeyColumns = primaryKey.columns - let primaryKeyValues = primaryKeyColumns.map { - persistenceContainer[caseInsensitive: $0]?.databaseValue ?? .null - } - if primaryKeyValues.allSatisfy({ $0.isNull }) { - return nil - } - - // Don't update columns not present in columns - // Don't update columns not present in the persistenceContainer - // Don't update primary key columns - let lowercaseUpdatedColumns = Set(columns.map { $0.lowercased() }) - .intersection(persistenceContainer.columns.map { $0.lowercased() }) - .subtracting(primaryKeyColumns.map { $0.lowercased() }) - - var updatedColumns: [String] = try db - .columns(in: databaseTableName) - .map { $0.name } - .filter { lowercaseUpdatedColumns.contains($0.lowercased()) } - - if updatedColumns.isEmpty { - // IMPLEMENTATION NOTE - // - // It is important to update something, so that - // TransactionObserver can observe a change even though this - // change is useless. - // - // The goal is to be able to write tests with minimal tables, - // including tables made of a single primary key column. - updatedColumns = primaryKeyColumns - } - - let updatedValues = updatedColumns.map { - persistenceContainer[caseInsensitive: $0]?.databaseValue ?? .null - } - - let query = UpdateQuery( - onConflict: onConflict, - tableName: databaseTableName, - updatedColumns: updatedColumns, - conditionColumns: primaryKeyColumns) - let statement = try db.internalCachedUpdateStatement(sql: query.sql) - statement.unsafeSetArguments(StatementArguments(updatedValues + primaryKeyValues)) - return statement - } - - /// Returns nil if and only if primary key is nil - @usableFromInline - func deleteStatement() throws -> UpdateStatement? { - // Fail early if primary key does not resolve to a database row. - let primaryKeyColumns = primaryKey.columns - let primaryKeyValues = primaryKeyColumns.map { - persistenceContainer[caseInsensitive: $0]?.databaseValue ?? .null - } - if primaryKeyValues.allSatisfy({ $0.isNull }) { - return nil - } - - let query = DeleteQuery( - tableName: databaseTableName, - conditionColumns: primaryKeyColumns) - let statement = try db.internalCachedUpdateStatement(sql: query.sql) - statement.unsafeSetArguments(StatementArguments(primaryKeyValues)) - return statement - } - - /// Returns nil if and only if primary key is nil - @usableFromInline - func existsStatement() throws -> SelectStatement? { - // Fail early if primary key does not resolve to a database row. - let primaryKeyColumns = primaryKey.columns - let primaryKeyValues = primaryKeyColumns.map { - persistenceContainer[caseInsensitive: $0]?.databaseValue ?? .null - } - if primaryKeyValues.allSatisfy({ $0.isNull }) { - return nil - } - - let query = ExistsQuery( - tableName: databaseTableName, - conditionColumns: primaryKeyColumns) - let statement = try db.internalCachedSelectStatement(sql: query.sql) - statement.unsafeSetArguments(StatementArguments(primaryKeyValues)) - return statement - } - - /// Throws a PersistenceError.recordNotFound error - @usableFromInline - func makeRecordNotFoundError() -> Error { - let key = Dictionary(uniqueKeysWithValues: primaryKey.columns.map { - ($0, persistenceContainer[caseInsensitive: $0]?.databaseValue ?? .null) - }) - return PersistenceError.recordNotFound( - databaseTableName: databaseTableName, - key: key) - } -} - - -// MARK: - InsertQuery - -private struct InsertQuery: Hashable { - let onConflict: Database.ConflictResolution - let tableName: String - let insertedColumns: [String] -} - -extension InsertQuery { - static let sqlCache = ReadWriteBox([InsertQuery: String]()) - var sql: String { - if let sql = InsertQuery.sqlCache.read({ $0[self] }) { - return sql - } - let columnsSQL = insertedColumns.map { $0.quotedDatabaseIdentifier }.joined(separator: ", ") - let valuesSQL = databaseQuestionMarks(count: insertedColumns.count) - let sql: String - switch onConflict { - case .abort: - sql = "INSERT INTO \(tableName.quotedDatabaseIdentifier) (\(columnsSQL)) VALUES (\(valuesSQL))" - default: - sql = "INSERT OR \(onConflict.rawValue) INTO \(tableName.quotedDatabaseIdentifier) (\(columnsSQL)) VALUES (\(valuesSQL))" - } - InsertQuery.sqlCache.write { $0[self] = sql } - return sql - } -} - - -// MARK: - UpdateQuery - -private struct UpdateQuery: Hashable { - let onConflict: Database.ConflictResolution - let tableName: String - let updatedColumns: [String] - let conditionColumns: [String] -} - -extension UpdateQuery { - static let sqlCache = ReadWriteBox([UpdateQuery: String]()) - var sql: String { - if let sql = UpdateQuery.sqlCache.read({ $0[self] }) { - return sql - } - let updateSQL = updatedColumns.map { "\($0.quotedDatabaseIdentifier)=?" }.joined(separator: ", ") - let whereSQL = conditionColumns.map { "\($0.quotedDatabaseIdentifier)=?" }.joined(separator: " AND ") - let sql: String - switch onConflict { - case .abort: - sql = "UPDATE \(tableName.quotedDatabaseIdentifier) SET \(updateSQL) WHERE \(whereSQL)" - default: - sql = "UPDATE OR \(onConflict.rawValue) \(tableName.quotedDatabaseIdentifier) SET \(updateSQL) WHERE \(whereSQL)" - } - UpdateQuery.sqlCache.write { $0[self] = sql } - return sql - } -} - - -// MARK: - DeleteQuery - -private struct DeleteQuery { - let tableName: String - let conditionColumns: [String] -} - -extension DeleteQuery { - var sql: String { - let whereSQL = conditionColumns.map { "\($0.quotedDatabaseIdentifier)=?" }.joined(separator: " AND ") - return "DELETE FROM \(tableName.quotedDatabaseIdentifier) WHERE \(whereSQL)" - } -} - - -// MARK: - ExistsQuery - -private struct ExistsQuery { - let tableName: String - let conditionColumns: [String] -} - -extension ExistsQuery { - var sql: String { - let whereSQL = conditionColumns.map { "\($0.quotedDatabaseIdentifier)=?" }.joined(separator: " AND ") - return "SELECT 1 FROM \(tableName.quotedDatabaseIdentifier) WHERE \(whereSQL)" - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/Record.swift b/Example/Pods/GRDB.swift/GRDB/Record/Record.swift deleted file mode 100755 index 4b22021..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/Record.swift +++ /dev/null @@ -1,355 +0,0 @@ -// MARK: - Record - -/// Record is a class that wraps a table row, or the result of any query. It is -/// designed to be subclassed. -open class Record : FetchableRecord, TableRecord, PersistableRecord { - - // MARK: - Initializers - - /// Creates a Record. - public init() { - } - - /// Creates a Record from a row. - required public init(row: Row) { - if row.isFetched { - // Take care of the hasDatabaseChanges flag. - // - // Row may be a reused row which will turn invalid as soon as the - // SQLite statement is iterated. We need to store an - // immutable copy. - referenceRow = row.copy() - } - } - - - // MARK: - Core methods - - /// The name of a database table. - /// - /// This table name is required by the insert, update, save, delete, - /// and exists methods. - /// - /// class Player : Record { - /// override class var databaseTableName: String { - /// return "player" - /// } - /// } - /// - /// The implementation of the base class Record raises a fatal error. - /// - /// - returns: The name of a database table. - open class var databaseTableName: String { - // Programmer error - fatalError("subclass must override") - } - - /// The policy that handles SQLite conflicts when records are inserted - /// or updated. - /// - /// This property is optional: its default value uses the ABORT policy - /// for both insertions and updates, and has GRDB generate regular - /// INSERT and UPDATE queries. - /// - /// If insertions are resolved with .ignore policy, the - /// `didInsert(with:for:)` method is not called upon successful insertion, - /// even if a row was actually inserted without any conflict. - /// - /// See https://www.sqlite.org/lang_conflict.html - open class var persistenceConflictPolicy: PersistenceConflictPolicy { - return PersistenceConflictPolicy(insert: .abort, update: .abort) - } - - /// The default request selection. - /// - /// Unless this method is overriden, requests select all columns: - /// - /// // SELECT * FROM player - /// try Player.fetchAll(db) - /// - /// You can override this property and provide an explicit list - /// of columns: - /// - /// class RestrictedPlayer : Record { - /// override static var databaseSelection: [SQLSelectable] { - /// return [Column("id"), Column("name")] - /// } - /// } - /// - /// // SELECT id, name FROM player - /// try RestrictedPlayer.fetchAll(db) - /// - /// You can also add extra columns such as the `rowid` column: - /// - /// class ExtendedPlayer : Player { - /// override static var databaseSelection: [SQLSelectable] { - /// return [AllColumns(), Column.rowID] - /// } - /// } - /// - /// // SELECT *, rowid FROM player - /// try ExtendedPlayer.fetchAll(db) - open class var databaseSelection: [SQLSelectable] { - return [AllColumns()] - } - - - /// Defines the values persisted in the database. - /// - /// Store in the *container* argument all values that should be stored in - /// the columns of the database table (see Record.databaseTableName()). - /// - /// Primary key columns, if any, must be included. - /// - /// class Player : Record { - /// var id: Int64? - /// var name: String? - /// - /// override func encode(to container: inout PersistenceContainer) { - /// container["id"] = id - /// container["name"] = name - /// } - /// } - /// - /// The implementation of the base class Record does not store any value in - /// the container. - open func encode(to container: inout PersistenceContainer) { - } - - /// Notifies the record that it was succesfully inserted. - /// - /// Do not call this method directly: it is called for you, in a protected - /// dispatch queue, with the inserted RowID and the eventual - /// INTEGER PRIMARY KEY column name. - /// - /// The implementation of the base Record class does nothing. - /// - /// class Player : Record { - /// var id: Int64? - /// var name: String? - /// - /// func didInsert(with rowID: Int64, for column: String?) { - /// id = rowID - /// } - /// } - /// - /// - parameters: - /// - rowID: The inserted rowID. - /// - column: The name of the eventual INTEGER PRIMARY KEY column. - open func didInsert(with rowID: Int64, for column: String?) { - } - - - // MARK: - Copy - - /// Returns a copy of `self`, initialized from all values encoded in the - /// `encode(to:)` method. - /// - /// The eventual primary key is copied, as well as the - /// `hasDatabaseChanges` flag. - /// - /// - returns: A copy of self. - open func copy() -> Self { - let copy = type(of: self).init(row: Row(self)) - copy.referenceRow = referenceRow - return copy - } - - - // MARK: - Compare with Previous Versions - - /// A boolean that indicates whether the record has changes that have not - /// been saved. - /// - /// This flag is purely informative, and does not prevent insert(), - /// update(), and save() from performing their database queries. - /// - /// A record is *edited* if has been changed since last database - /// synchronization (fetch, update, insert). Comparison - /// is performed between *values* (values stored in the `encode(to:)` - /// method, and values loaded from the database). Property setters do not - /// trigger this flag. - /// - /// You can rely on the Record base class to compute this flag for you, or - /// you may set it to true or false when you know better. Setting it to - /// false does not prevent it from turning true on subsequent modifications - /// of the record. - public var hasDatabaseChanges: Bool { - get { return databaseChangesIterator().next() != nil } - set { referenceRow = newValue ? nil : Row(self) } - } - - /// A dictionary of changes that have not been saved. - /// - /// Its keys are column names, and values the old values that have been - /// changed since last fetching or saving of the record. - /// - /// Unless the record has actually been fetched or saved, the old values - /// are nil. - /// - /// See `hasDatabaseChanges` for more information. - public var databaseChanges: [String: DatabaseValue?] { - return Dictionary(uniqueKeysWithValues: databaseChangesIterator()) - } - - // A change iterator that is used by both hasDatabaseChanges and - // persistentChangedValues properties. - private func databaseChangesIterator() -> AnyIterator<(String, DatabaseValue?)> { - let oldRow = referenceRow - var newValueIterator = PersistenceContainer(self).makeIterator() - return AnyIterator { - // Loop until we find a change, or exhaust columns: - while let (column, newValue) = newValueIterator.next() { - let newDbValue = newValue?.databaseValue ?? .null - guard let oldRow = oldRow, let oldDbValue: DatabaseValue = oldRow[column] else { - return (column, nil) - } - if newDbValue != oldDbValue { - return (column, oldDbValue) - } - } - return nil - } - } - - - /// Reference row for the *hasDatabaseChanges* property. - var referenceRow: Row? - - - // MARK: - CRUD - - /// Executes an INSERT statement. - /// - /// On success, this method sets the *hasDatabaseChanges* flag to false. - /// - /// This method is guaranteed to have inserted a row in the database if it - /// returns without error. - /// - /// Records whose primary key is declared as "INTEGER PRIMARY KEY" have - /// their id automatically set after successful insertion, if it was nil - /// before the insertion. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError whenever an SQLite error occurs. - open func insert(_ db: Database) throws { - let conflictResolutionForInsert = type(of: self).persistenceConflictPolicy.conflictResolutionForInsert - let dao = try DAO(db, self) - var persistenceContainer = dao.persistenceContainer - try dao.insertStatement(onConflict: conflictResolutionForInsert).execute() - - if !conflictResolutionForInsert.invalidatesLastInsertedRowID { - let rowID = db.lastInsertedRowID - let rowIDColumn = dao.primaryKey.rowIDColumn - didInsert(with: rowID, for: rowIDColumn) - - // Update persistenceContainer with inserted id, so that we can - // set hasDatabaseChanges to false: - if let rowIDColumn = rowIDColumn { - persistenceContainer[caseInsensitive: rowIDColumn] = rowID - } - } - - // Set hasDatabaseChanges to false - referenceRow = Row(persistenceContainer) - } - - /// Executes an UPDATE statement. - /// - /// On success, this method sets the *hasDatabaseChanges* flag to false. - /// - /// This method is guaranteed to have updated a row in the database if it - /// returns without error. - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database and record could not be updated. - open func update(_ db: Database, columns: Set) throws { - // The simplest code would be: - // - // try performUpdate(db, columns: columns) - // hasDatabaseChanges = false - // - // But this would trigger two calls to `encode(to:)`. - let dao = try DAO(db, self) - guard let statement = try dao.updateStatement(columns: columns, onConflict: type(of: self).persistenceConflictPolicy.conflictResolutionForUpdate) else { - // Nil primary key - throw dao.makeRecordNotFoundError() - } - try statement.execute() - if db.changesCount == 0 { - throw dao.makeRecordNotFoundError() - } - - // Set hasDatabaseChanges to false - referenceRow = Row(dao.persistenceContainer) - } - - /// If the record has been changed, executes an UPDATE statement so that - /// those changes and only those changes are saved in the database. - /// - /// On success, this method sets the *hasDatabaseChanges* flag to false. - /// - /// This method is guaranteed to have saved the eventual changes in the - /// database if it returns without error. - /// - /// - parameter db: A database connection. - /// - parameter columns: The columns to update. - /// - returns: Whether the record had changes. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - /// PersistenceError.recordNotFound is thrown if the primary key does not - /// match any row in the database and record could not be updated. - @discardableResult - final public func updateChanges(_ db: Database) throws -> Bool { - let changedColumns = Set(databaseChanges.keys) - if changedColumns.isEmpty { - return false - } else { - try update(db, columns: changedColumns) - return true - } - } - - /// Executes an INSERT or an UPDATE statement so that `self` is saved in - /// the database. - /// - /// If the record has a non-nil primary key and a matching row in the - /// database, this method performs an update. - /// - /// Otherwise, performs an insert. - /// - /// On success, this method sets the *hasDatabaseChanges* flag to false. - /// - /// This method is guaranteed to have inserted or updated a row in the - /// database if it returns without error. - /// - /// You can't override this method. Instead, override `insert(_:)` - /// or `update(_:columns:)`. - /// - /// - parameter db: A database connection. - /// - throws: A DatabaseError whenever an SQLite error occurs, or errors - /// thrown by update(). - final public func save(_ db: Database) throws { - try performSave(db) - } - - /// Executes a DELETE statement. - /// - /// On success, this method sets the *hasDatabaseChanges* flag to true. - /// - /// - parameter db: A database connection. - /// - returns: Whether a database row was deleted. - /// - throws: A DatabaseError is thrown whenever an SQLite error occurs. - @discardableResult - open func delete(_ db: Database) throws -> Bool { - defer { - // Future calls to update() will throw NotFound. Make the user - // a favor and make sure this error is thrown even if she checks the - // hasDatabaseChanges flag: - hasDatabaseChanges = true - } - return try performDelete(db) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Record/TableRecord.swift b/Example/Pods/GRDB.swift/GRDB/Record/TableRecord.swift deleted file mode 100755 index d7ea751..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Record/TableRecord.swift +++ /dev/null @@ -1,155 +0,0 @@ -/// Types that adopt TableRecord declare a particular relationship with -/// a database table. -/// -/// Types that adopt both TableRecord and FetchableRecord are granted with -/// built-in methods that allow to fetch instances identified by key: -/// -/// try Player.fetchOne(db, key: 123) // Player? -/// try Citizenship.fetchOne(db, key: ["citizenId": 12, "countryId": 45]) // Citizenship? -/// -/// TableRecord is adopted by Record. -public protocol TableRecord { - /// The name of the database table used to build requests. - /// - /// struct Player : TableRecord { - /// static var databaseTableName = "player" - /// } - /// - /// // SELECT * FROM player - /// try Player.fetchAll(db) - static var databaseTableName: String { get } - - /// The default request selection. - /// - /// Unless said otherwise, requests select all columns: - /// - /// // SELECT * FROM player - /// try Player.fetchAll(db) - /// - /// You can provide a custom implementation and provide an explicit list - /// of columns: - /// - /// struct RestrictedPlayer : TableRecord { - /// static var databaseTableName = "player" - /// static var databaseSelection = [Column("id"), Column("name")] - /// } - /// - /// // SELECT id, name FROM player - /// try RestrictedPlayer.fetchAll(db) - /// - /// You can also add extra columns such as the `rowid` column: - /// - /// struct ExtendedPlayer : TableRecord { - /// static var databaseTableName = "player" - /// static let databaseSelection: [SQLSelectable] = [AllColumns(), Column.rowID] - /// } - /// - /// // SELECT *, rowid FROM player - /// try ExtendedPlayer.fetchAll(db) - static var databaseSelection: [SQLSelectable] { get } -} - -extension TableRecord { - - /// The default name of the database table used to build requests. - /// - /// - Player -> "player" - /// - Place -> "place" - /// - PostalAddress -> "postalAddress" - /// - HTTPRequest -> "httpRequest" - /// - TOEFL -> "toefl" - internal static var defaultDatabaseTableName: String { - let typeName = "\(Self.self)".replacingOccurrences(of: "(.)\\b.*$", with: "$1", options: [.regularExpression]) - let initial = typeName.replacingOccurrences(of: "^([A-Z]+).*$", with: "$1", options: [.regularExpression]) - switch initial.count { - case typeName.count: - return initial.lowercased() - case 0: - return typeName - case 1: - return initial.lowercased() + typeName.dropFirst() - default: - return initial.dropLast().lowercased() + typeName.dropFirst(initial.count - 1) - } - } - - /// The default name of the database table used to build requests. - /// - /// - Player -> "player" - /// - Place -> "place" - /// - PostalAddress -> "postalAddress" - /// - HTTPRequest -> "httpRequest" - /// - TOEFL -> "toefl" - public static var databaseTableName: String { - return defaultDatabaseTableName - } - - /// Default value: `[AllColumns()]`. - public static var databaseSelection: [SQLSelectable] { - return [AllColumns()] - } -} - -extension TableRecord { - - // MARK: - Counting All - - /// The number of records. - /// - /// - parameter db: A database connection. - public static func fetchCount(_ db: Database) throws -> Int { - return try all().fetchCount(db) - } -} - -extension TableRecord { - - // MARK: - SQL Generation - - /// The selection as an SQL String. - /// - /// For example: - /// - /// struct Player: TableRecord { - /// static let databaseTableName = "player" - /// } - /// - /// // SELECT "player".* FROM player - /// let sql = "SELECT \(Player.selectionSQL()) FROM player" - /// - /// // SELECT "p".* FROM player AS p - /// let sql = "SELECT \(Player.selectionSQL(alias: "p")) FROM player p" - public static func selectionSQL(alias: String? = nil) -> String { - let alias = TableAlias(tableName: databaseTableName, userName: alias) - let selection = databaseSelection.map { $0.qualifiedSelectable(with: alias) } - var context = SQLGenerationContext.recordSelectionGenerationContext() - return selection - .map { $0.resultColumnSQL(&context) } - .joined(separator: ", ") - } - - /// Returns the number of selected columns. - /// - /// For example: - /// - /// struct Player: TableRecord { - /// static let databaseTableName = "player" - /// } - /// - /// try dbQueue.write { db in - /// try db.create(table: "player") { t in - /// t.autoIncrementedPrimaryKey("id") - /// t.column("name", .text) - /// t.column("score", .integer) - /// } - /// - /// // 3 - /// try Player.numberOfSelectedColumns(db) - /// } - public static func numberOfSelectedColumns(_ db: Database) throws -> Int { - let alias = TableAlias(tableName: databaseTableName) - return try databaseSelection - .map { try $0.qualifiedSelectable(with: alias).columnCount(db) } - .reduce(0, +) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/Inflections+English.swift b/Example/Pods/GRDB.swift/GRDB/Utils/Inflections+English.swift deleted file mode 100755 index c9f5270..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/Inflections+English.swift +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (C) 2019 Gwendal Roué -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// ============================================================================= -// -// Copyright (c) 2005-2019 David Heinemeier Hansson -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -extension Inflections { - /// The default inflections - public static var `default`: Inflections = { - // Defines the standard inflection rules. These are the starting point - // for new projects and are not considered complete. The current set of - // inflection rules is frozen. This means, we do not change them to - // become more complete. This is a safety measure to keep existing - // applications from breaking. - // - // https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activesupport/lib/active_support/inflections.rb - var inflections = Inflections() - - inflections.plural("$", "s") - inflections.plural("s$", "s") - inflections.plural("^(ax|test)is$", "$1es") - inflections.plural("(octop|vir)us$", "$1i") - inflections.plural("(octop|vir)i$", "$1i") - inflections.plural("(alias|status)$", "$1es") - inflections.plural("(bu)s$", "$1ses") - inflections.plural("(buffal|tomat|her)o$", "$1oes") - inflections.plural("([ti])um$", "$1a") - inflections.plural("([ti])a$", "$1a") - inflections.plural("sis$", "ses") - inflections.plural("(?:([^f])fe|([lr])f)$", "$1$2ves") - inflections.plural("(hive)$", "$1s") - inflections.plural("([^aeiouy]|qu)y$", "$1ies") - inflections.plural("(x|ch|ss|sh)$", "$1es") - inflections.plural("(matr|vert|ind)(?:ix|ex)$", "$1ices") - inflections.plural("^(m|l)ouse$", "$1ice") - inflections.plural("^(m|l)ice$", "$1ice") - inflections.plural("^(ox)$", "$1en") - inflections.plural("^(oxen)$", "$1") - inflections.plural("(quiz)$", "$1zes") - inflections.plural("(canva)s$", "$1ses") - - inflections.singular("s$", "") - inflections.singular("(ss)$", "$1") - inflections.singular("(n)ews$", "$1ews") - inflections.singular("([ti])a$", "$1um") - inflections.singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "$1sis") - inflections.singular("(^analy)(sis|ses)$", "$1sis") - inflections.singular("([^f])ves$", "$1fe") - inflections.singular("(hive)s$", "$1") - inflections.singular("(tive)s$", "$1") - inflections.singular("([lr])ves$", "$1f") - inflections.singular("([^aeiouy]|qu)ies$", "$1y") - inflections.singular("(s)eries$", "$1eries") - inflections.singular("(m)ovies$", "$1ovie") - inflections.singular("(x|ch|ss|sh)es$", "$1") - inflections.singular("^(m|l)ice$", "$1ouse") - inflections.singular("(bus)(es)?$", "$1") - inflections.singular("(o)es$", "$1") - inflections.singular("(shoe)s$", "$1") - inflections.singular("(cris|test)(is|es)$", "$1is") - inflections.singular("^(a)x[ie]s$", "$1xis") - inflections.singular("(octop|vir)(us|i)$", "$1us") - inflections.singular("(alias|status)(es)?$", "$1") - inflections.singular("^(ox)en$", "$1") - inflections.singular("(vert|ind)ices$", "$1ex") - inflections.singular("(matr)ices$", "$1ix") - inflections.singular("(quiz)zes$", "$1") - inflections.singular("(database)s$", "$1") - inflections.singular("(canvas)(es)?$", "$1") - - inflections.uncountableWords([ - "advice", - "corps", - "dice", - "equipment", - "fish", - "information", - "jeans", - "kudos", - "money", - "offspring", - "police", - "rice", - "sheep", - "species", - ]) - - inflections.irregularSuffix("child", "children") - inflections.irregularSuffix("foot", "feet") - inflections.irregularSuffix("leaf", "leaves") - inflections.irregularSuffix("man", "men") - inflections.irregularSuffix("move", "moves") - inflections.irregularSuffix("person", "people") - inflections.irregularSuffix("sex", "sexes") - inflections.irregularSuffix("specimen", "specimens") - inflections.irregularSuffix("zombie", "zombies") - - return inflections - }() -} diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/Inflections.swift b/Example/Pods/GRDB.swift/GRDB/Utils/Inflections.swift deleted file mode 100755 index f8dbabf..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/Inflections.swift +++ /dev/null @@ -1,238 +0,0 @@ -import Foundation - -extension String { - /// "player" -> "Player" - var uppercasingFirstCharacter: String { - guard let first = first else { - return self - } - return String(first).uppercased() + dropFirst() - } - - /// "player" -> "players" - /// "players" -> "players" - var pluralized: String { - return Inflections.default.pluralize(self) - } - - /// "player" -> "player" - /// "players" -> "player" - var singularized: String { - return Inflections.default.singularize(self) - } - - /// "bar" -> "bar" - /// "foo12" -> "foo" - var digitlessRadical: String { - return String(prefix(upTo: Inflections.endIndexOfDigitlessRadical(self))) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A type that controls GRDB string inflections. -public struct Inflections { - private var pluralizeRules: [(NSRegularExpression, String)] = [] - private var singularizeRules: [(NSRegularExpression, String)] = [] - private var uncountablesRegularExpressions: [String: NSRegularExpression] = [:] - - // For testability - var uncountables: Set { - return Set(uncountablesRegularExpressions.keys) - } - - // MARK: - Initialization - - public init() { - } - - // MARK: - Configuration - - /// Appends a pluralization rule. - /// - /// var inflections = Inflections() - /// inflections.plural("$", "s") - /// inflections.pluralize("player") // "players" - /// - /// - parameters: - /// - pattern: A regular expression pattern. - /// - options: Regular expression options (defaults to - /// `[.caseInsensitive]`). - /// - template: A replacement template string. - public mutating func plural(_ pattern: String, options: NSRegularExpression.Options = [.caseInsensitive], _ template: String) { - let reg = try! NSRegularExpression(pattern: pattern, options: options) - pluralizeRules.append((reg, template)) - } - - /// Appends a singularization rule. - /// - /// var inflections = Inflections() - /// inflections.singular("s$", "") - /// inflections.singularize("players") // "player" - /// - /// - parameters: - /// - pattern: A regular expression pattern. - /// - options: Regular expression options (defaults to - /// `[.caseInsensitive]`). - /// - template: A replacement template string. - public mutating func singular(_ pattern: String, options: NSRegularExpression.Options = [.caseInsensitive], _ template: String) { - let reg = try! NSRegularExpression(pattern: pattern, options: options) - singularizeRules.append((reg, template)) - } - - /// Appends uncountable words. - /// - /// var inflections = Inflections() - /// inflections.plural("$", "s") - /// inflections.uncountableWords(["foo"]) - /// inflections.pluralize("foo") // "foo" - /// inflections.pluralize("bar") // "bars" - public mutating func uncountableWords(_ words: [String]) { - for word in words { - uncountableWord(word) - } - } - - /// Appends an irregular singular/plural pair. - /// - /// var inflections = Inflections() - /// inflections.plural("$", "s") - /// inflections.irregularSuffix("man", "men") - /// inflections.pluralize("man") // "men" - /// inflections.singularizes("women") // "woman" - /// - /// - parameters: - /// - singular: The singular form. - /// - plural: The plural form. - public mutating func irregularSuffix(_ singular: String, _ plural: String) { - let s0 = singular.first! - let srest = singular.dropFirst() - - let p0 = plural.first! - let prest = plural.dropFirst() - - if s0.uppercased() == p0.uppercased() { - self.plural("(\(s0))\(srest)$", options: [.caseInsensitive], "$1\(prest)") - self.plural("(\(p0))\(prest)$", options: [.caseInsensitive], "$1\(prest)") - - self.singular("(\(s0))\(srest)$", options: [.caseInsensitive], "$1\(srest)") - self.singular("(\(p0))\(prest)$", options: [.caseInsensitive], "$1\(srest)") - } else { - self.plural("\(s0.uppercased())(?i)\(srest)$", options: [], p0.uppercased() + prest) - self.plural("\(s0.lowercased())(?i)\(srest)$", options: [], p0.lowercased() + prest) - self.plural("\(p0.uppercased())(?i)\(prest)$", options: [], p0.uppercased() + prest) - self.plural("\(p0.lowercased())(?i)\(prest)$", options: [], p0.lowercased() + prest) - - self.singular("\(s0.uppercased())(?i)\(srest)$", options: [], s0.uppercased() + srest) - self.singular("\(s0.lowercased())(?i)\(srest)$", options: [], s0.lowercased() + srest) - self.singular("\(p0.uppercased())(?i)\(prest)$", options: [], s0.uppercased() + srest) - self.singular("\(p0.lowercased())(?i)\(prest)$", options: [], s0.lowercased() + srest) - } - } - - // MARK: - Inflections - - /// Returns a pluralized string. - /// - /// Inflections.default.pluralize("player") // "players" - public func pluralize(_ string: String) -> String { - return inflectString(string, with: pluralizeRules) - } - - /// Returns a singularized string. - public func singularize(_ string: String) -> String { - return inflectString(string, with: singularizeRules) - } - - // MARK: - Utils - - /// Appends an uncountable word. - /// - /// var inflections = Inflections() - /// inflections.plural("$", "s") - /// inflections.uncountableWord("foo") - /// inflections.pluralize("foo") // "foo" - /// inflections.pluralize("bar") // "bars" - private mutating func uncountableWord(_ word: String) { - let escWord = NSRegularExpression.escapedPattern(for: word) - uncountablesRegularExpressions[word] = try! NSRegularExpression(pattern: "\\b\(escWord)\\Z", options: [.caseInsensitive]) - } - - private func isUncountable(_ string: String) -> Bool { - let range = NSRange(location: 0, length: string.utf16.count) - for (_, reg) in uncountablesRegularExpressions { - if reg.firstMatch(in: string, options: [], range: range) != nil { - return true - } - } - return false - } - - private func inflectString(_ string: String, with rules: [(NSRegularExpression, String)]) -> String { - let indexOfLastWord = Inflections.startIndexOfLastWord(string) - let endIndexOfDigitlessRadical = Inflections.endIndexOfDigitlessRadical(string) - let lastWord = String(string[indexOfLastWord.. String { - if string.isEmpty { - return string - } - let range = NSRange(string.startIndex.. 0 { - return String(result) - } - } - return string - } - - /// startIndexOfLastWord("foo") -> "foo" - /// startIndexOfLastWord("foo bar") -> "bar" - /// startIndexOfLastWord("foo_bar") -> "bar" - /// startIndexOfLastWord("fooBar") -> "Bar" - static func startIndexOfLastWord(_ string: String) -> String.Index { - let range = NSRange(string.startIndex.. "bar" - /// "foo12" -> "foo" - static func endIndexOfDigitlessRadical(_ string: String) -> String.Index { - let digits: ClosedRange = "0"..."9" - return string // "foo12" - .reversed() // "21oof" - .prefix(while: { digits.contains($0) }) // "21" - .endIndex // reversed(foo^12) - .base // foo^12 - } - - private static let wordBoundaryReg = try! NSRegularExpression(pattern: "\\b\\w+$", options: []) - private static let underscoreBoundaryReg = try! NSRegularExpression(pattern: "_[^_]+$", options: []) - private static let caseBoundaryReg = try! NSRegularExpression(pattern: "[^A-Z][A-Z]+[a-z1-9]+$", options: []) -} - diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/OrderedDictionary.swift b/Example/Pods/GRDB.swift/GRDB/Utils/OrderedDictionary.swift deleted file mode 100755 index dd51231..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/OrderedDictionary.swift +++ /dev/null @@ -1,152 +0,0 @@ -/// A dictionary with guaranteed keys ordering. -/// -/// var dict = OrderedDictionary() -/// dict.append(1, forKey: "foo") -/// dict.append(2, forKey: "bar") -/// -/// dict["foo"] // 1 -/// dict["bar"] // 2 -/// dict["qux"] // nil -/// dict.map { $0.key } // ["foo", "bar"], in this order. -@usableFromInline -struct OrderedDictionary { - @usableFromInline /* private(set) */ var keys: [Key] - @usableFromInline /* private(set) */ var dictionary: [Key: Value] - - var values: [Value] { - return keys.map { dictionary[$0]! } - } - - /// Creates an empty ordered dictionary. - init() { - keys = [] - dictionary = [:] - } - - /// Creates an empty ordered dictionary. - init(minimumCapacity: Int) { - keys = [] - keys.reserveCapacity(minimumCapacity) - dictionary = Dictionary(minimumCapacity: minimumCapacity) - } - - /// Returns the value associated with key, or nil. - @usableFromInline - subscript(_ key: Key) -> Value? { - get { return dictionary[key] } - set { - if let value = newValue { - updateValue(value, forKey: key) - } else { - removeValue(forKey: key) - } - } - } - - /// Returns the value associated with key, or the default value. - @inlinable - subscript(_ key: Key, default defaultValue: Value) -> Value { - get { return dictionary[key] ?? defaultValue } - set { self[key] = newValue } - } - - /// Appends the given value for the given key. - /// - /// - precondition: There is no value associated with key yet. - mutating func appendValue(_ value: Value, forKey key: Key) { - guard updateValue(value, forKey: key) == nil else { - fatalError("key is already defined") - } - } - - /// Updates the value stored in the dictionary for the given key, or - /// appnds a new key-value pair if the key does not exist. - /// - /// Use this method instead of key-based subscripting when you need to know - /// whether the new value supplants the value of an existing key. If the - /// value of an existing key is updated, updateValue(_:forKey:) returns the - /// original value. If the given key is not present in the dictionary, this - /// method appends the key-value pair and returns nil. - @discardableResult - @usableFromInline - mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { - if let oldValue = dictionary.updateValue(value, forKey: key) { - return oldValue - } - keys.append(key) - return nil - } - - /// Removes the value associated with key. - @discardableResult - @inlinable - mutating func removeValue(forKey key: Key) -> Value? { - guard let value = dictionary.removeValue(forKey: key) else { - return nil - } - let index = keys.firstIndex { $0 == key }! - keys.remove(at: index) - return value - } - - /// Returns a new ordered dictionary containing the keys of this dictionary - /// with the values transformed by the given closure. - func mapValues(_ transform: (Value) throws -> T) rethrows -> OrderedDictionary { - return try reduce(into: .init()) { dict, pair in - let value = try transform(pair.value) - dict.appendValue(value, forKey: pair.key) - } - } - - /// Returns a new ordered dictionary containing only the key-value pairs - /// that have non-nil values as the result of transformation by the - /// given closure. - func compactMapValues(_ transform: (Value) throws -> T?) rethrows -> OrderedDictionary { - return try reduce(into: .init()) { dict, pair in - if let value = try transform(pair.value) { - dict.appendValue(value, forKey: pair.key) - } - } - } -} - -extension OrderedDictionary: Collection { - @usableFromInline typealias Index = Int - - @usableFromInline var startIndex: Int { - return 0 - } - - @usableFromInline var endIndex: Int { - return keys.count - } - - @usableFromInline func index(after i: Int) -> Int { - return i + 1 - } - - @usableFromInline subscript(position: Int) -> (key: Key, value: Value) { - let key = keys[position] - return (key: key, value: dictionary[key]!) - } -} - -extension OrderedDictionary: ExpressibleByDictionaryLiteral { - @usableFromInline init(dictionaryLiteral elements: (Key, Value)...) { - self.keys = elements.map { $0.0 } - self.dictionary = Dictionary(uniqueKeysWithValues: elements) - } -} - -extension OrderedDictionary: Equatable where Value: Equatable { - @usableFromInline - static func == (lhs: OrderedDictionary, rhs: OrderedDictionary) -> Bool { - return (lhs.keys == rhs.keys) && (lhs.dictionary == rhs.dictionary) - } -} - -extension Dictionary { - init(_ orderedDictionary: OrderedDictionary) { - self = orderedDictionary.dictionary - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/Pool.swift b/Example/Pods/GRDB.swift/GRDB/Utils/Pool.swift deleted file mode 100755 index 444d926..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/Pool.swift +++ /dev/null @@ -1,121 +0,0 @@ -import Dispatch - -/// A Pool maintains a set of elements that are built them on demand. A pool has -/// a maximum number of elements. -/// -/// // A pool of 3 integers -/// var number = 0 -/// let pool = Pool(maximumCount: 3, makeElement: { -/// number = number + 1 -/// return number -/// }) -/// -/// The function get() dequeues an available element and gives this element to -/// the block argument. During the block execution, the element is not -/// available. When the block is ended, the element is available again. -/// -/// // got 1 -/// pool.get { n in -/// print("got \(n)") -/// } -/// -/// If there is no available element, the pool builds a new element, unless the -/// maximum number of elements is reached. In this case, the get() method -/// blocks the current thread, until an element eventually turns available again. -/// -/// DispatchQueue.concurrentPerform(iterations: 6) { _ in -/// pool.get { n in -/// print("got \(n)") -/// } -/// } -/// -/// got 1 -/// got 2 -/// got 3 -/// got 2 -/// got 1 -/// got 3 -final class Pool { - private class Item { - let element: T - var available: Bool - - init(element: T, available: Bool) { - self.element = element - self.available = available - } - } - - private let makeElement: () throws -> T - private var items: ReadWriteBox<[Item]> = ReadWriteBox([]) - private let semaphore: DispatchSemaphore // limits the number of elements - - init(maximumCount: Int, makeElement: @escaping () throws -> T) { - GRDBPrecondition(maximumCount > 0, "Pool size must be at least 1") - self.makeElement = makeElement - self.semaphore = DispatchSemaphore(value: maximumCount) - } - - /// Returns a tuple (element, release) - /// Client MUST call release() after the element has been used. - func get() throws -> (T, () -> ()) { - _ = semaphore.wait(timeout: .distantFuture) - do { - let item = try items.write { items -> Item in - if let item = items.first(where: { $0.available }) { - item.available = false - return item - } else { - let element = try makeElement() - let item = Item(element: element, available: false) - items.append(item) - return item - } - } - let release = { - self.items.write { _ in - // This is why Item is a class, not a struct: so that we can - // release it without having to find in it the items array. - item.available = true - } - self.semaphore.signal() - } - return (item.element, release) - } catch { - semaphore.signal() - throw error - } - } - - /// Performs a synchronous block with an element. The element turns - /// available after the block has executed. - func get(block: (T) throws -> U) throws -> U { - let (element, release) = try get() - defer { release() } - return try block(element) - } - - /// Performs a block on each pool element, available or not. - /// The block is run is some arbitrary dispatch queue. - func forEach(_ body: (T) throws -> ()) rethrows { - try items.read { items in - for item in items { - try body(item.element) - } - } - } - - /// Empty the pool. Currently used items won't be reused. - func clear() { - clear {} - } - - /// Empty the pool. Currently used items won't be reused. - /// Eventual block is executed before any other element is dequeued. - func clear(andThen block: () throws -> ()) rethrows { - try items.write { items in - items.removeAll() - try block() - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/ReadWriteBox.swift b/Example/Pods/GRDB.swift/GRDB/Utils/ReadWriteBox.swift deleted file mode 100755 index 4dc9e4f..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/ReadWriteBox.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Dispatch - -/// A ReadWriteBox grants multiple readers and single-writer guarantees on a value. -final class ReadWriteBox { - var value: T { - get { return read { $0 } } - set { write { $0 = newValue } } - } - - init(_ value: T) { - self._value = value - self.queue = DispatchQueue(label: "GRDB.ReadWriteBox", attributes: [.concurrent]) - } - - func read(_ block: (T) throws -> U) rethrows -> U { - return try queue.sync { - try block(_value) - } - } - - func write(_ block: (inout T) throws -> U) rethrows -> U { - return try queue.sync(flags: [.barrier]) { - try block(&_value) - } - } - - private var _value: T - private var queue: DispatchQueue -} - -extension ReadWriteBox where T: Numeric { - func increment() -> T { - return write { n in - n += 1 - return n - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/Result.swift b/Example/Pods/GRDB.swift/GRDB/Utils/Result.swift deleted file mode 100755 index 028259c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/Result.swift +++ /dev/null @@ -1,34 +0,0 @@ -#if compiler(>=5.0) -typealias Result = Swift.Result -#else -enum Result { - case success(Success) - case failure(Error) - - init(catching body: () throws -> Success) { - do { - self = try .success(body()) - } catch { - self = .failure(error) - } - } - - func map(_ transform: (Success) -> T) -> Result { - switch self { - case .success(let success): - return .success(transform(success)) - case .failure(let error): - return .failure(error) - } - } - - func get() throws -> Success { - switch self { - case .success(let success): - return success - case .failure(let error): - throw error - } - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/Utils/Utils.swift b/Example/Pods/GRDB.swift/GRDB/Utils/Utils.swift deleted file mode 100755 index ae666ec..0000000 --- a/Example/Pods/GRDB.swift/GRDB/Utils/Utils.swift +++ /dev/null @@ -1,120 +0,0 @@ -import Foundation - -// MARK: - Public - -extension String { - /// Returns the receiver, quoted for safe insertion as an identifier in an - /// SQL query. - /// - /// db.execute(sql: "SELECT * FROM \(tableName.quotedDatabaseIdentifier)") - @inlinable - public var quotedDatabaseIdentifier: String { - // See https://www.sqlite.org/lang_keywords.html - return "\"\(self)\"" - } -} - -/// Return as many question marks separated with commas as the *count* argument. -/// -/// databaseQuestionMarks(count: 3) // "?,?,?" -@inlinable -public func databaseQuestionMarks(count: Int) -> String { - return repeatElement("?", count: count).joined(separator: ",") -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// This protocol is an implementation detail of GRDB. Don't use it. -/// -/// :nodoc: -public protocol _OptionalProtocol { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - associatedtype _Wrapped -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// This conformance is an implementation detail of GRDB. Don't rely on it. -/// -/// :nodoc: -extension Optional : _OptionalProtocol { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// :nodoc: - public typealias _Wrapped = Wrapped -} - - -// MARK: - Internal - -/// Reserved for GRDB: do not use. -@inlinable -func GRDBPrecondition(_ condition: @autoclosure() -> Bool, _ message: @autoclosure() -> String = "", file: StaticString = #file, line: UInt = #line) { - /// Custom precondition function which aims at solving - /// https://bugs.swift.org/browse/SR-905 and - /// https://github.com/groue/GRDB.swift/issues/37 - if !condition() { - fatalError(message(), file: file, line: line) - } -} - -// Workaround Swift inconvenience around factory methods of non-final classes -func cast(_ value: T) -> U? { - return value as? U -} - -extension RangeReplaceableCollection { - /// Removes the first object that matches *predicate*. - mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows { - if let index = try firstIndex(where: predicate) { - remove(at: index) - } - } -} - -extension Dictionary { - /// Removes the first object that matches *predicate*. - mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows { - if let index = try firstIndex(where: predicate) { - remove(at: index) - } - } -} - -extension DispatchQueue { - private static var mainKey: DispatchSpecificKey<()> = { - let key = DispatchSpecificKey<()>() - DispatchQueue.main.setSpecific(key: key, value: ()) - return key - }() - - static var isMain: Bool { - return DispatchQueue.getSpecific(key: mainKey) != nil - } -} - -// Has SE-0220 been removed in Xcode 10.2 beta 4? -// #if compiler(<5.0) -extension Sequence { - @inlinable - func count(where predicate: (Element) throws -> Bool) rethrows -> Int { - var count = 0 - for e in self where try predicate(e) { - count += 1 - } - return count - } -} -// #endif - -#if !compiler(>=5.0) -extension Character { - func uppercased() -> String { - return String(self).uppercased() - } - - func lowercased() -> String { - return String(self).lowercased() - } -} -#endif diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Combine.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Combine.swift deleted file mode 100755 index da06efc..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Combine.swift +++ /dev/null @@ -1,273 +0,0 @@ -extension ValueObservation where Reducer == Void { - public static func combine( - _ o1: ValueObservation, - _ o2: ValueObservation) - -> ValueObservation> - { - return ValueObservation>( - tracking: { try DatabaseRegion.union( - o1.observedRegion($0), - o2.observedRegion($0)) }, - reducer: { try _combine( - o1.makeReducer($0), - o2.makeReducer($0)) }) - } - - public static func combine( - _ o1: ValueObservation, - _ o2: ValueObservation, - _ o3: ValueObservation) - -> ValueObservation> - { - return ValueObservation>( - tracking: { try DatabaseRegion.union( - o1.observedRegion($0), - o2.observedRegion($0), - o3.observedRegion($0)) }, - reducer: { try _combine( - o1.makeReducer($0), - o2.makeReducer($0), - o3.makeReducer($0)) }) - } - - public static func combine( - _ o1: ValueObservation, - _ o2: ValueObservation, - _ o3: ValueObservation, - _ o4: ValueObservation) - -> ValueObservation> - { - return ValueObservation>( - tracking: { try DatabaseRegion.union( - o1.observedRegion($0), - o2.observedRegion($0), - o3.observedRegion($0), - o4.observedRegion($0)) }, - reducer: { try _combine( - o1.makeReducer($0), - o2.makeReducer($0), - o3.makeReducer($0), - o4.makeReducer($0)) }) - } - - public static func combine( - _ o1: ValueObservation, - _ o2: ValueObservation, - _ o3: ValueObservation, - _ o4: ValueObservation, - _ o5: ValueObservation) - -> ValueObservation> - { - return ValueObservation>( - tracking: { try DatabaseRegion.union( - o1.observedRegion($0), - o2.observedRegion($0), - o3.observedRegion($0), - o4.observedRegion($0), - o5.observedRegion($0)) }, - reducer: { try _combine( - o1.makeReducer($0), - o2.makeReducer($0), - o3.makeReducer($0), - o4.makeReducer($0), - o5.makeReducer($0)) }) - } -} - -private func _combine( - _ r1: R1, - _ r2: R2) - -> AnyValueReducer< - (R1.Fetched, R2.Fetched), - (R1.Value, R2.Value)> -{ - var r1 = r1 - var r2 = r2 - var prev1: R1.Value? - var prev2: R2.Value? - func fetch(db: Database) throws -> (R1.Fetched, R2.Fetched) { - return try ( - r1.fetch(db), - r2.fetch(db)) - } - func value(tuple: (R1.Fetched, R2.Fetched)) -> (R1.Value, R2.Value)? { - let v1 = r1.value(tuple.0) - let v2 = r2.value(tuple.1) - defer { - if let v1 = v1 { prev1 = v1 } - if let v2 = v2 { prev2 = v2 } - } - if v1 != nil || v2 != nil, - let c1 = v1 ?? prev1, - let c2 = v2 ?? prev2 - { - return (c1, c2) - } else { - return nil - } - } - return AnyValueReducer(fetch: fetch, value: value) -} - -private func _combine( - _ r1: R1, - _ r2: R2, - _ r3: R3) - -> AnyValueReducer< - (R1.Fetched, R2.Fetched, R3.Fetched), - (R1.Value, R2.Value, R3.Value)> -{ - var r1 = r1 - var r2 = r2 - var r3 = r3 - var prev1: R1.Value? - var prev2: R2.Value? - var prev3: R3.Value? - func fetch(db: Database) throws -> (R1.Fetched, R2.Fetched, R3.Fetched) { - return try ( - r1.fetch(db), - r2.fetch(db), - r3.fetch(db)) - } - func value(tuple: (R1.Fetched, R2.Fetched, R3.Fetched)) -> (R1.Value, R2.Value, R3.Value)? { - let v1 = r1.value(tuple.0) - let v2 = r2.value(tuple.1) - let v3 = r3.value(tuple.2) - defer { - if let v1 = v1 { prev1 = v1 } - if let v2 = v2 { prev2 = v2 } - if let v3 = v3 { prev3 = v3 } - } - if v1 != nil || v2 != nil || v3 != nil, - let c1 = v1 ?? prev1, - let c2 = v2 ?? prev2, - let c3 = v3 ?? prev3 - { - return (c1, c2, c3) - } else { - return nil - } - } - return AnyValueReducer(fetch: fetch, value: value) -} - -private func _combine( - _ r1: R1, - _ r2: R2, - _ r3: R3, - _ r4: R4) - -> AnyValueReducer< - (R1.Fetched, R2.Fetched, R3.Fetched, R4.Fetched), - (R1.Value, R2.Value, R3.Value, R4.Value)> -{ - var r1 = r1 - var r2 = r2 - var r3 = r3 - var r4 = r4 - var prev1: R1.Value? - var prev2: R2.Value? - var prev3: R3.Value? - var prev4: R4.Value? - func fetch(db: Database) throws -> (R1.Fetched, R2.Fetched, R3.Fetched, R4.Fetched) { - return try ( - r1.fetch(db), - r2.fetch(db), - r3.fetch(db), - r4.fetch(db)) - } - func value(tuple: (R1.Fetched, R2.Fetched, R3.Fetched, R4.Fetched)) -> (R1.Value, R2.Value, R3.Value, R4.Value)? { - let v1 = r1.value(tuple.0) - let v2 = r2.value(tuple.1) - let v3 = r3.value(tuple.2) - let v4 = r4.value(tuple.3) - defer { - if let v1 = v1 { prev1 = v1 } - if let v2 = v2 { prev2 = v2 } - if let v3 = v3 { prev3 = v3 } - if let v4 = v4 { prev4 = v4 } - } - if v1 != nil || v2 != nil || v3 != nil || v4 != nil, - let c1 = v1 ?? prev1, - let c2 = v2 ?? prev2, - let c3 = v3 ?? prev3, - let c4 = v4 ?? prev4 - { - return (c1, c2, c3, c4) - } else { - return nil - } - } - return AnyValueReducer(fetch: fetch, value: value) -} - -private func _combine( - _ r1: R1, - _ r2: R2, - _ r3: R3, - _ r4: R4, - _ r5: R5) - -> AnyValueReducer< - (R1.Fetched, R2.Fetched, R3.Fetched, R4.Fetched, R5.Fetched), - (R1.Value, R2.Value, R3.Value, R4.Value, R5.Value)> -{ - var r1 = r1 - var r2 = r2 - var r3 = r3 - var r4 = r4 - var r5 = r5 - var prev1: R1.Value? - var prev2: R2.Value? - var prev3: R3.Value? - var prev4: R4.Value? - var prev5: R5.Value? - func fetch(db: Database) throws -> (R1.Fetched, R2.Fetched, R3.Fetched, R4.Fetched, R5.Fetched) { - return try ( - r1.fetch(db), - r2.fetch(db), - r3.fetch(db), - r4.fetch(db), - r5.fetch(db)) - } - func value(tuple: (R1.Fetched, R2.Fetched, R3.Fetched, R4.Fetched, R5.Fetched)) -> (R1.Value, R2.Value, R3.Value, R4.Value, R5.Value)? { - let v1 = r1.value(tuple.0) - let v2 = r2.value(tuple.1) - let v3 = r3.value(tuple.2) - let v4 = r4.value(tuple.3) - let v5 = r5.value(tuple.4) - defer { - if let v1 = v1 { prev1 = v1 } - if let v2 = v2 { prev2 = v2 } - if let v3 = v3 { prev3 = v3 } - if let v4 = v4 { prev4 = v4 } - if let v5 = v5 { prev5 = v5 } - } - if v1 != nil || v2 != nil || v3 != nil || v4 != nil || v5 != nil, - let c1 = v1 ?? prev1, - let c2 = v2 ?? prev2, - let c3 = v3 ?? prev3, - let c4 = v4 ?? prev4, - let c5 = v5 ?? prev5 - { - return (c1, c2, c3, c4, c5) - } else { - return nil - } - } - return AnyValueReducer(fetch: fetch, value: value) -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+CompactMap.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+CompactMap.swift deleted file mode 100755 index 5778b5a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+CompactMap.swift +++ /dev/null @@ -1,44 +0,0 @@ -extension ValueObservation where Reducer: ValueReducer { - /// Returns a ValueObservation which notifies the non-nil results of calling - /// the given transformation which each element notified by this - /// value observation. - public func compactMap(_ transform: @escaping (Reducer.Value) -> T?) - -> ValueObservation> - { - return mapReducer { $1.compactMap(transform) } - } -} - -extension ValueReducer { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns a reducer which outputs the non-nil results of calling the given - /// transformation which each element emitted by this reducer. - public func compactMap(_ transform: @escaping (Value) -> T?) -> CompactMapValueReducer { - return CompactMapValueReducer(self, transform) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// See ValueReducer.compactMap(_:) -/// -/// :nodoc: -public struct CompactMapValueReducer: ValueReducer { - private var base: Base - private let transform: (Base.Value) -> T? - - init(_ base: Base, _ transform: @escaping (Base.Value) -> T?) { - self.base = base - self.transform = transform - } - - public func fetch(_ db: Database) throws -> Base.Fetched { - return try base.fetch(db) - } - - public mutating func value(_ fetched: Base.Fetched) -> T? { - guard let value = base.value(fetched) else { return nil } - return transform(value) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Count.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Count.swift deleted file mode 100755 index 78ff705..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Count.swift +++ /dev/null @@ -1,33 +0,0 @@ -extension ValueObservation where Reducer == Void { - - // MARK: - Count Observation - - /// Creates a ValueObservation which observes *request*, and notifies its - /// count whenever it is modified by a database transaction. - /// - /// For example: - /// - /// let request = Player.all() - /// let observation = ValueObservation.trackingCount(request) - /// - /// let observer = try observation.start(in: dbQueue) { count: Int in - /// print("Number of players has changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingCount(_ request: Request) - -> ValueObservation>> - { - return ValueObservation.tracking(request, fetch: request.fetchCount).distinctUntilChanged() - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+DatabaseValueConvertible.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+DatabaseValueConvertible.swift deleted file mode 100755 index c326345..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+DatabaseValueConvertible.swift +++ /dev/null @@ -1,210 +0,0 @@ -extension ValueObservation where Reducer == Void { - - // MARK: - DatabaseValueConvertible Observation - - /// Creates a ValueObservation which observes *request*, and notifies - /// fresh values whenever the request is modified by a - /// database transaction. - /// - /// For example: - /// - /// let request = Player.select(Column("name"), as: String.self) - /// let observation = ValueObservation.trackingAll(request) - /// - /// let observer = try observation.start(in: dbQueue) { names: [String] in - /// print("Player names have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingAll(_ request: Request) - -> ValueObservation> - where Request.RowDecoder: DatabaseValueConvertible - { - return ValueObservation>.tracking(request, reducer: { _ in - DatabaseValuesReducer(request: request) } - ) - } - - /// Creates a ValueObservation which observes *request*, and notifies a - /// fresh value whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = Player.select(max(Column("score")), as: Int.self) - /// let observation = ValueObservation.trackingOne(request) - /// - /// let observer = try observation.start(in: dbQueue) { maxScore: Int? in - /// print("Maximum score has changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingOne(_ request: Request) - -> ValueObservation> - where Request.RowDecoder: DatabaseValueConvertible - { - return ValueObservation>.tracking(request, reducer: { _ in DatabaseValueReducer(request: request) - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies - /// fresh values whenever the request is modified by a - /// database transaction. - /// - /// For example: - /// - /// let request = Player.select(Column("name"), as: Optional.self) - /// let observation = ValueObservation.trackingAll(request) - /// - /// let observer = try observation.start(in: dbQueue) { names: [String?] in - /// print("Player names have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingAll(_ request: Request) - -> ValueObservation> - where Request.RowDecoder: _OptionalProtocol, - Request.RowDecoder._Wrapped: DatabaseValueConvertible - { - return ValueObservation>.tracking(request, reducer: { _ in - OptionalDatabaseValuesReducer(request: request) - }) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs arrays of values, filtering out consecutive -/// identical database values. -/// -/// :nodoc: -public struct DatabaseValuesReducer: ValueReducer - where Request.RowDecoder: DatabaseValueConvertible -{ - public let request: Request - private var previousDbValues: [DatabaseValue]? - - init(request: Request) { - self.request = request - } - - public func fetch(_ db: Database) throws -> [DatabaseValue] { - return try DatabaseValue.fetchAll(db, request) - } - - public mutating func value(_ dbValues: [DatabaseValue]) -> [Request.RowDecoder]? { - if let previousDbValues = previousDbValues, previousDbValues == dbValues { - // Don't notify consecutive identical dbValue arrays - return nil - } - self.previousDbValues = dbValues - return dbValues.map { - Request.RowDecoder.decode(from: $0, conversionContext: nil) - } - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs optional values, filtering out consecutive -/// identical database values. -/// -/// :nodoc: -public struct DatabaseValueReducer: ValueReducer - where Request.RowDecoder: DatabaseValueConvertible -{ - public let request: Request - private var previousDbValue: DatabaseValue?? - private var previousValueWasNil = false - - init(request: Request) { - self.request = request - } - - public func fetch(_ db: Database) throws -> DatabaseValue? { - return try DatabaseValue.fetchOne(db, request) - } - - public mutating func value(_ dbValue: DatabaseValue?) -> Request.RowDecoder?? { - if let previousDbValue = previousDbValue, previousDbValue == dbValue { - // Don't notify consecutive identical dbValue - return nil - } - self.previousDbValue = dbValue - if let dbValue = dbValue, - let value = Request.RowDecoder.decodeIfPresent(from: dbValue, conversionContext: nil) - { - previousValueWasNil = false - return .some(value) - } else if previousValueWasNil { - // Don't notify consecutive nil values - return nil - } else { - previousValueWasNil = true - return .some(nil) - } - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs arrays of optional values, filtering out consecutive -/// identical database values. -/// -/// :nodoc: -public struct OptionalDatabaseValuesReducer: ValueReducer - where - Request.RowDecoder: _OptionalProtocol, - Request.RowDecoder._Wrapped: DatabaseValueConvertible -{ - public let request: Request - private var previousDbValues: [DatabaseValue]? - - init(request: Request) { - self.request = request - } - - public func fetch(_ db: Database) throws -> [DatabaseValue] { - return try DatabaseValue.fetchAll(db, request) - } - - public mutating func value(_ dbValues: [DatabaseValue]) -> [Request.RowDecoder._Wrapped?]? { - if let previousDbValues = previousDbValues, previousDbValues == dbValues { - // Don't notify consecutive identical dbValue arrays - return nil - } - self.previousDbValues = dbValues - return dbValues.map { - Request.RowDecoder._Wrapped.decodeIfPresent(from: $0, conversionContext: nil) - } - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+DistinctUntilChanged.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+DistinctUntilChanged.swift deleted file mode 100755 index a15102a..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+DistinctUntilChanged.swift +++ /dev/null @@ -1,47 +0,0 @@ -extension ValueObservation where Reducer: ValueReducer, Reducer.Value: Equatable { - /// Returns a ValueObservation which filters out consecutive equal values. - public func distinctUntilChanged() - -> ValueObservation> - { - return mapReducer { $1.distinctUntilChanged() } - } -} - -extension ValueReducer where Value: Equatable { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns a ValueReducer which filters out consecutive equal values. - public func distinctUntilChanged() -> DistinctUntilChangedValueReducer { - return DistinctUntilChangedValueReducer(self) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// See ValueReducer.distinctUntilChanged() -/// -/// :nodoc: -public struct DistinctUntilChangedValueReducer: ValueReducer where Base.Value: Equatable { - private var base: Base - private var previousValue: Base.Value? - - init(_ base: Base) { - self.base = base - } - - public func fetch(_ db: Database) throws -> Base.Fetched { - return try base.fetch(db) - } - - public mutating func value(_ fetched: Base.Fetched) -> Base.Value? { - guard let value = base.value(fetched) else { - return nil - } - if let previousValue = previousValue, previousValue == value { - // Don't notify consecutive identical values - return nil - } - self.previousValue = value - return value - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+FetchableRecord.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+FetchableRecord.swift deleted file mode 100755 index df28ced..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+FetchableRecord.swift +++ /dev/null @@ -1,192 +0,0 @@ -extension ValueObservation where Reducer == Void { - - // MARK: - FetchableRecord Observation - - /// Creates a ValueObservation which observes *request*, and notifies - /// fresh records whenever the request is modified by a - /// database transaction. - /// - /// For example: - /// - /// let request = Player.all() - /// let observation = ValueObservation.trackingAll(request) - /// - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingAll(_ request: Request) - -> ValueObservation> - where Request.RowDecoder: FetchableRecord - { - return ValueObservation>.tracking(request, reducer: { _ in - FetchableRecordsReducer { try Row.fetchAll($0, request) } - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies - /// fresh records whenever the request is modified by a - /// database transaction. - /// - /// For example: - /// - /// let request = Player.all() - /// let observation = ValueObservation.trackingAll(request) - /// - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingAll(_ request: QueryInterfaceRequest) - -> ValueObservation> - { - return ValueObservation>.tracking(request, reducer: { _ in - FetchableRecordsReducer { try Row.fetchAll($0, request) } - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies a - /// fresh record whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = Player.filter(key: 1) - /// let observation = ValueObservation.trackingOne(request) - /// - /// let observer = try observation.start(in: dbQueue) { player: Player? in - /// print("Player has changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingOne(_ request: Request) -> - ValueObservation> - where Request.RowDecoder: FetchableRecord - { - return ValueObservation>.tracking(request, reducer: { _ in - FetchableRecordReducer { try Row.fetchOne($0, request) } - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies a - /// fresh record whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = Player.filter(key: 1) - /// let observation = ValueObservation.trackingOne(request) - /// - /// let observer = try observation.start(in: dbQueue) { player: Player? in - /// print("Player has changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingOne(_ request: QueryInterfaceRequest) - -> ValueObservation> - { - return ValueObservation>.tracking(request, reducer: { _ in - FetchableRecordReducer { try Row.fetchOne($0, request) } - }) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs arrays of records, filtering out consecutive -/// identical database rows. -/// -/// :nodoc: -public struct FetchableRecordsReducer: ValueReducer - where RowDecoder: FetchableRecord -{ - private let _fetch: (Database) throws -> [Row] - private var previousRows: [Row]? - - init(fetch: @escaping (Database) throws -> [Row]) { - self._fetch = fetch - } - - public func fetch(_ db: Database) throws -> [Row] { - return try _fetch(db) - } - - public mutating func value(_ rows: [Row]) -> [RowDecoder]? { - if let previousRows = previousRows, previousRows == rows { - // Don't notify consecutive identical row arrays - return nil - } - self.previousRows = rows - return rows.map(RowDecoder.init(row:)) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs optional records, filtering out consecutive -/// identical database rows. -/// -/// :nodoc: -public struct FetchableRecordReducer: ValueReducer - where RowDecoder: FetchableRecord -{ - private let _fetch: (Database) throws -> Row? - private var previousRow: Row?? - - init(fetch: @escaping (Database) throws -> Row?) { - self._fetch = fetch - } - - public func fetch(_ db: Database) throws -> Row? { - return try _fetch(db) - } - - public mutating func value(_ row: Row?) -> RowDecoder?? { - if let previousRow = previousRow, previousRow == row { - // Don't notify consecutive identical rows - return nil - } - self.previousRow = row - return .some(row.map(RowDecoder.init(row:))) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Map.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Map.swift deleted file mode 100755 index cefd648..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Map.swift +++ /dev/null @@ -1,47 +0,0 @@ -extension ValueObservation where Reducer: ValueReducer { - /// Returns a ValueObservation which notifies the results of calling the - /// given transformation which each element notified by this - /// value observation. - public func map(_ transform: @escaping (Reducer.Value) -> T) - -> ValueObservation> - { - return mapReducer { $1.map(transform) } - } -} - -extension ValueReducer { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns a reducer which outputs the results of calling the given - /// transformation which each element emitted by this reducer. - public func map(_ transform: @escaping (Value) -> T) -> MapValueReducer { - return MapValueReducer(self, transform) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A ValueReducer whose values consist of those in a Base ValueReducer passed -/// through a transform function. -/// -/// See ValueReducer.map(_:) -/// -/// :nodoc: -public struct MapValueReducer: ValueReducer { - private var base: Base - private let transform: (Base.Value) -> T - - init(_ base: Base, _ transform: @escaping (Base.Value) -> T) { - self.base = base - self.transform = transform - } - - public func fetch(_ db: Database) throws -> Base.Fetched { - return try base.fetch(db) - } - - public mutating func value(_ fetched: Base.Fetched) -> T? { - guard let value = base.value(fetched) else { return nil } - return transform(value) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+MapReducer.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+MapReducer.swift deleted file mode 100755 index 0f9705c..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+MapReducer.swift +++ /dev/null @@ -1,14 +0,0 @@ -extension ValueObservation { - /// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) - /// - /// Returns a ValueObservation with a transformed reducer. - public func mapReducer(_ transform: @escaping (Database, Reducer) throws -> R) -> ValueObservation { - let makeReducer = self.makeReducer - var observation = ValueObservation( - tracking: observedRegion, - reducer: { db in try transform(db, makeReducer(db)) }) - observation.scheduling = scheduling - observation.requiresWriteAccess = requiresWriteAccess - return observation - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Row.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Row.swift deleted file mode 100755 index 32038ee..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation+Row.swift +++ /dev/null @@ -1,186 +0,0 @@ -extension ValueObservation where Reducer == Void { - - // MARK: - Row Observation - - /// Creates a ValueObservation which observes *request*, and notifies - /// fresh rows whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = SQLRequest(sql: "SELECT * FROM player") - /// let observation = ValueObservation.trackingAll(request) - /// - /// let observer = try observation.start(in: dbQueue) { rows: [Row] in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingAll(_ request: Request) - -> ValueObservation - where Request.RowDecoder == Row - { - return ValueObservation.tracking(request, reducer: { _ in - RowsReducer(fetch: request.fetchAll) - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies - /// fresh rows whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = SQLRequest(sql: "SELECT * FROM player") - /// let observation = ValueObservation.trackingAll(request) - /// - /// let observer = try observation.start(in: dbQueue) { rows: [Row] in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingAll(_ request: QueryInterfaceRequest) - -> ValueObservation - { - return ValueObservation.tracking(request, reducer: { _ in - RowsReducer(fetch: request.fetchAll) - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies a - /// fresh row whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = SQLRequest(sql: "SELECT * FROM player WHERE id = ?", arguments: [1]) - /// let observation = ValueObservation.trackingOne(request) - /// - /// let observer = try observation.start(in: dbQueue) { row: Row? in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingOne(_ request: Request) - -> ValueObservation - where Request.RowDecoder == Row - { - return ValueObservation.tracking(request, reducer: { _ in - RowReducer(fetch: request.fetchOne) - }) - } - - /// Creates a ValueObservation which observes *request*, and notifies a - /// fresh row whenever the request is modified by a database transaction. - /// - /// For example: - /// - /// let request = SQLRequest(sql: "SELECT * FROM player WHERE id = ?", arguments: [1]) - /// let observation = ValueObservation.trackingOne(request) - /// - /// let observer = try observation.start(in: dbQueue) { row: Row? in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter request: the observed request. - /// - returns: a ValueObservation. - public static func trackingOne(_ request: QueryInterfaceRequest) - -> ValueObservation - { - return ValueObservation.tracking(request, reducer: { _ in - RowReducer(fetch: request.fetchOne) - }) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs arrays of database rows, filtering out -/// consecutive identical arrays. -/// -/// :nodoc: -public struct RowsReducer: ValueReducer { - private let _fetch: (Database) throws -> [Row] - private var previousRows: [Row]? - - init(fetch: @escaping (Database) throws -> [Row]) { - self._fetch = fetch - } - - public func fetch(_ db: Database) throws -> [Row] { - return try _fetch(db) - } - - public mutating func value(_ rows: [Row]) -> [Row]? { - if let previousRows = previousRows, previousRows == rows { - // Don't notify consecutive identical row arrays - return nil - } - self.previousRows = rows - return rows - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs optional records, filtering out consecutive -/// identical database rows. -/// -/// :nodoc: -public struct RowReducer: ValueReducer { - private let _fetch: (Database) throws -> Row? - private var previousRow: Row?? - - init(fetch: @escaping (Database) throws -> Row?) { - self._fetch = fetch - } - - public func fetch(_ db: Database) throws -> Row? { - return try _fetch(db) - } - - public mutating func value(_ row: Row?) -> Row?? { - if let previousRow = previousRow, previousRow == row { - // Don't notify consecutive identical rows - return nil - } - self.previousRow = row - return row - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation.swift deleted file mode 100755 index c263e73..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObservation.swift +++ /dev/null @@ -1,361 +0,0 @@ -import Dispatch - -// MARK: - ValueScheduling - -/// ValueScheduling controls how ValueObservation schedules the notifications -/// of fresh values to your application. -public enum ValueScheduling { - /// All values are notified on the main queue. - /// - /// If the observation starts on the main queue, an initial value is - /// notified right upon subscription, synchronously: - /// - /// // On main queue - /// let observation = ValueObservation.trackingAll(Player.all()) - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("fresh players: \(players)") - /// } - /// // <- here "fresh players" is already printed. - /// - /// If the observation does not start on the main queue, an initial value - /// is also notified on the main queue, but asynchronously: - /// - /// // Not on the main queue: "fresh players" is eventually printed - /// // on the main queue. - /// let observation = ValueObservation.trackingAll(Player.all()) - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("fresh players: \(players)") - /// } - /// - /// When the database changes, fresh values are asynchronously notified on - /// the main queue: - /// - /// // Eventually prints "fresh players" on the main queue - /// try dbQueue.write { db in - /// try Player(...).insert(db) - /// } - case mainQueue - - /// All values are asychronously notified on the specified queue. - /// - /// An initial value is fetched and notified if `startImmediately` - /// is true. - case async(onQueue: DispatchQueue, startImmediately: Bool) - - /// Values are not all notified on the same dispatch queue. - /// - /// If `startImmediately` is true, an initial value is notified right upon - /// subscription, synchronously, on the dispatch queue which starts - /// the observation. - /// - /// // On any queue - /// var observation = ValueObservation.trackingAll(Player.all()) - /// observation.scheduling = .unsafe(startImmediately: true) - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("fresh players: \(players)") - /// } - /// // <- here "fresh players" is already printed. - /// - /// When the database changes, other values are notified on - /// unspecified queues. - case unsafe(startImmediately: Bool) -} - -// MARK: - ValueObservation - -/// ValueObservation tracks changes in the results of database requests, and -/// notifies fresh values whenever the database changes. -/// -/// For example: -/// -/// let observation = ValueObservation.trackingAll(Player.all) -/// let observer = try observation.start(in: dbQueue) { players: [Player] in -/// print("Players have changed.") -/// } -public struct ValueObservation { - /// A closure that is evaluated when the observation starts, and returns - /// the observed database region. - var observedRegion: (Database) throws -> DatabaseRegion - - /// The reducer is created when observation starts, and is triggered upon - /// each database change in *observedRegion*. - var makeReducer: (Database) throws -> Reducer - - /// Default is false. Set this property to true when the observation - /// requires write access in order to fetch fresh values. Fetches are then - /// wrapped inside a savepoint. - /// - /// Don't set this flag to true unless you really need it. A read/write - /// observation is less efficient than a read-only observation. - public var requiresWriteAccess: Bool = false - - /// `scheduling` controls how fresh values are notified. Default - /// is `.mainQueue`. - /// - /// - `.mainQueue`: all values are notified on the main queue. - /// - /// If the observation starts on the main queue, an initial value is - /// notified right upon subscription, synchronously:: - /// - /// // On main queue - /// let observation = ValueObservation.trackingAll(Player.all()) - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("fresh players: \(players)") - /// } - /// // <- here "fresh players" is already printed. - /// - /// If the observation does not start on the main queue, an initial - /// value is also notified on the main queue, but asynchronously: - /// - /// // Not on the main queue: "fresh players" is eventually printed - /// // on the main queue. - /// let observation = ValueObservation.trackingAll(Player.all()) - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("fresh players: \(players)") - /// } - /// - /// When the database changes, fresh values are asynchronously notified: - /// - /// // Eventually prints "fresh players" on the main queue - /// try dbQueue.write { db in - /// try Player(...).insert(db) - /// } - /// - /// - `.onQueue(_:startImmediately:)`: all values are asychronously notified - /// on the specified queue. - /// - /// An initial value is fetched and notified if `startImmediately` - /// is true. - /// - /// - `unsafe(startImmediately:)`: values are not all notified on the same - /// dispatch queue. - /// - /// If `startImmediately` is true, an initial value is notified right - /// upon subscription, synchronously, on the dispatch queue which starts - /// the observation. - /// - /// // On any queue - /// var observation = ValueObservation.trackingAll(Player.all()) - /// observation.scheduling = .unsafe(startImmediately: true) - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("fresh players: \(players)") - /// } - /// // <- here "fresh players" is already printed. - /// - /// When the database changes, other values are notified on - /// unspecified queues. - public var scheduling: ValueScheduling = .mainQueue - - /// The dispatch queue where change callbacks are called. - var notificationQueue: DispatchQueue? { - switch scheduling { - case .mainQueue: - return DispatchQueue.main - case let .async(onQueue: queue, startImmediately: _): - return queue - case .unsafe: - return nil - } - } - - // Not public because we foster DatabaseRegionConvertible. - // See ValueObservation.tracking(_:reducer:) - init( - tracking region: @escaping (Database) throws -> DatabaseRegion, - reducer: @escaping (Database) throws -> Reducer) - { - self.observedRegion = { db in - // Remove views from the observed region. - // - // We can do it because we are only interested in modifications in - // actual tables. And we want to do it because we have a fast path - // for simple regions that span a single table. - let views = try db.schema().names(ofType: .view) - return try region(db).ignoring(views) - } - self.makeReducer = reducer - } -} - -extension ValueObservation where Reducer: ValueReducer { - - // MARK: - Starting Observation - - /// Starts the value observation in the provided database reader (such as - /// a database queue or database pool), and returns a transaction observer. - /// - /// - parameter reader: A DatabaseReader. - /// - parameter onError: A closure that is provided eventual errors that - /// happen during observation - /// - parameter onChange: A closure that is provided fresh values - /// - returns: a TransactionObserver - public func start( - in reader: DatabaseReader, - onError: ((Error) -> Void)? = nil, - onChange: @escaping (Reducer.Value) -> Void) throws -> TransactionObserver - { - return try reader.add(observation: self, onError: onError, onChange: onChange) - } -} - -extension ValueObservation { - - // MARK: - Creating ValueObservation from ValueReducer - - /// Returns a ValueObservation which observes *regions*, and notifies the - /// values returned by the *reducer* whenever one of the observed - /// regions is modified by a database transaction. - /// - /// This method is the most fundamental way to create a ValueObservation. - /// - /// For example, this observation counts the number of a times the player - /// table is modified: - /// - /// var count = 0 - /// let reducer = AnyValueReducer( - /// fetch: { _ in /* don't fetch anything */ }, - /// value: { _ -> Int? in - /// defer { count += 1 } - /// return count }) - /// let observation = ValueObservation.tracking(Player.all(), reducer: { db in reducer }) - /// let observer = observation.start(in: dbQueue) { count: Int in - /// print("Players have been modified \(count) times.") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter regions: A list of observed regions. - /// - parameter reducer: A reducer that turns database changes in the - /// modified regions into fresh values. Currently only reducers that adopt - /// the ValueReducer protocol are supported. - public static func tracking( - _ regions: DatabaseRegionConvertible..., - reducer: @escaping (Database) throws -> Reducer) - -> ValueObservation - { - return ValueObservation.tracking(regions, reducer: reducer) - } - - /// Returns a ValueObservation which observes *regions*, and notifies the - /// values returned by the *reducer* whenever one of the observed - /// regions is modified by a database transaction. - /// - /// This method is the most fundamental way to create a ValueObservation. - /// - /// For example, this observation counts the number of a times the player - /// table is modified: - /// - /// var count = 0 - /// let reducer = AnyValueReducer( - /// fetch: { _ in /* don't fetch anything */ }, - /// value: { _ -> Int? in - /// defer { count += 1 } - /// return count }) - /// let observation = ValueObservation.tracking([Player.all()], reducer: { db in reducer }) - /// let observer = observation.start(in: dbQueue) { count: Int in - /// print("Players have been modified \(count) times.") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter regions: A list of observed regions. - /// - parameter reducer: A reducer that turns database changes in the - /// modified regions into fresh values. Currently only reducers that adopt - /// the ValueReducer protocol are supported. - public static func tracking( - _ regions: [DatabaseRegionConvertible], - reducer: @escaping (Database) throws -> Reducer) - -> ValueObservation - { - return ValueObservation( - tracking: DatabaseRegion.union(regions), - reducer: reducer) - } -} - -extension ValueObservation where Reducer == Void { - - // MARK: - Creating ValueObservation from Fetch Closures - - /// Creates a ValueObservation which observes *regions*, and notifies the - /// values returned by the *fetch* closure whenever one of the observed - /// regions is modified by a database transaction. - /// - /// For example: - /// - /// let observation = ValueObservation.tracking( - /// Player.all(), - /// fetch: { db in return try Player.fetchAll(db) }) - /// - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter regions: A list of observed regions. - /// - parameter fetch: A closure that fetches a value. - public static func tracking( - _ regions: DatabaseRegionConvertible..., - fetch: @escaping (Database) throws -> Value) - -> ValueObservation> - { - return ValueObservation.tracking(regions, fetch: fetch) - } - - /// Creates a ValueObservation which observes *regions*, and notifies the - /// values returned by the *fetch* closure whenever one of the observed - /// regions is modified by a database transaction. - /// - /// For example: - /// - /// let observation = ValueObservation.tracking( - /// [Player.all()], - /// fetch: { db in return try Player.fetchAll(db) }) - /// - /// let observer = try observation.start(in: dbQueue) { players: [Player] in - /// print("Players have changed") - /// } - /// - /// The returned observation has the default configuration: - /// - /// - When started with the `start(in:onError:onChange:)` method, a fresh - /// value is immediately notified on the main queue. - /// - Upon subsequent database changes, fresh values are notified on the - /// main queue. - /// - The observation lasts until the observer returned by - /// `start` is deallocated. - /// - /// - parameter regions: A list of observed regions. - /// - parameter fetch: A closure that fetches a value. - public static func tracking( - _ regions: [DatabaseRegionConvertible], - fetch: @escaping (Database) throws -> Value) - -> ValueObservation> - { - return ValueObservation>( - tracking: DatabaseRegion.union(regions), - reducer: { _ in RawValueReducer(fetch) }) - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObserver.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObserver.swift deleted file mode 100755 index a25e778..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueObserver.swift +++ /dev/null @@ -1,87 +0,0 @@ -import Foundation - -/// Support for ValueObservation. -/// See DatabaseWriter.add(observation:onError:onChange:) -class ValueObserver: TransactionObserver { - /* private */ let region: DatabaseRegion // Internal for testability - private var reducer: Reducer - private let fetch: (Database, Reducer) -> DatabaseFuture - private let notificationQueue: DispatchQueue? - private let onError: ((Error) -> Void)? - private let onChange: (Reducer.Value) -> Void - private let reduceQueue: DispatchQueue - private var isChanged = false - - init( - region: DatabaseRegion, - reducer: Reducer, - configuration: Configuration, - fetch: @escaping (Database, Reducer) -> DatabaseFuture, - notificationQueue: DispatchQueue?, - onError: ((Error) -> Void)?, - onChange: @escaping (Reducer.Value) -> Void) - { - self.region = region - self.reducer = reducer - self.fetch = fetch - self.notificationQueue = notificationQueue - self.onChange = onChange - self.onError = onError - self.reduceQueue = configuration.makeDispatchQueue(defaultLabel: "GRDB", purpose: "ValueObservation.reducer") - } - - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - return region.isModified(byEventsOfKind: eventKind) - } - - func databaseDidChange(with event: DatabaseEvent) { - if region.isModified(by: event) { - isChanged = true - stopObservingDatabaseChangesUntilNextTransaction() - } - } - - func databaseDidCommit(_ db: Database) { - guard isChanged else { return } - isChanged = false - - // Grab future fetched values from the database writer queue - let future = fetch(db, reducer) - - // Wait for future fetched values in reduceQueue. This guarantees: - // - that notifications have the same ordering as transactions. - // - that expensive reduce operations are computed without blocking - // any database dispatch queue. - reduceQueue.async { [weak self] in - // Never ever retain self so that notifications stop when self - // is deallocated by the user. - do { - if let value = try self?.reducer.value(future.wait()) { - if let queue = self?.notificationQueue { - queue.async { - self?.onChange(value) - } - } else { - self?.onChange(value) - } - } - } catch { - guard self?.onError != nil else { - // TODO: how can we let the user know about the error? - return - } - if let queue = self?.notificationQueue { - queue.async { - self?.onError?(error) - } - } else { - self?.onError?(error) - } - } - } - } - - func databaseDidRollback(_ db: Database) { - isChanged = false - } -} diff --git a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueReducer.swift b/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueReducer.swift deleted file mode 100755 index 198e3af..0000000 --- a/Example/Pods/GRDB.swift/GRDB/ValueObservation/ValueReducer.swift +++ /dev/null @@ -1,92 +0,0 @@ -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// The ValueReducer protocol supports ValueObservation. -public protocol ValueReducer { - /// The type of fetched database values - associatedtype Fetched - - /// The type of observed values - associatedtype Value - - /// Feches database values upon changes in an observed database region. - func fetch(_ db: Database) throws -> Fetched - - /// Transforms a fetched value into an eventual observed value. Returns nil - /// when observer should not be notified. - /// - /// This method runs inside a private dispatch queue. - mutating func value(_ fetched: Fetched) -> Value? -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A type-erased ValueReducer. -/// -/// An AnyValueReducer forwards its operations to an underlying reducer, -/// hiding its specifics. -public struct AnyValueReducer: ValueReducer { - private var _fetch: (Database) throws -> Fetched - private var _value: (Fetched) -> Value? - - /// Creates a reducer whose `fetch(_:)` and `value(_:)` methods wrap and - /// forward operations the argument closures. - /// - /// For example, this reducer counts the number of a times the player table - /// is modified: - /// - /// var count = 0 - /// let reducer = AnyValueReducer( - /// fetch: { _ in }, - /// value: { _ -> Int? in - /// count += 1 - /// return count - /// }) - /// let observer = ValueObservation - /// .tracking(Player.all(), reducer: reducer) - /// .start(in: dbQueue) { count: Int in - /// print("Players have been modified \(count) times.") - /// } - public init(fetch: @escaping (Database) throws -> Fetched, value: @escaping (Fetched) -> Value?) { - self._fetch = fetch - self._value = value - } - - /// Creates a reducer that wraps and forwards operations to `reducer`. - public init(_ reducer: Base) where Base.Fetched == Fetched, Base.Value == Value { - var reducer = reducer - self._fetch = { try reducer.fetch($0) } - self._value = { reducer.value($0) } - } - - /// :nodoc: - public func fetch(_ db: Database) throws -> Fetched { - return try _fetch(db) - } - - /// :nodoc: - public func value(_ fetched: Fetched) -> Value? { - return _value(fetched) - } -} - -/// [**Experimental**](http://github.com/groue/GRDB.swift#what-are-experimental-features) -/// -/// A reducer which outputs raw values. -/// -/// :nodoc: -public struct RawValueReducer: ValueReducer { - private let _fetch: (Database) throws -> Value - - public init(_ fetch: @escaping (Database) throws -> Value) { - self._fetch = fetch - } - - public func fetch(_ db: Database) throws -> Value { - return try _fetch(db) - } - - public func value(_ fetched: Value) -> Value? { - return fetched - } -} - diff --git a/Example/Pods/GRDB.swift/LICENSE b/Example/Pods/GRDB.swift/LICENSE deleted file mode 100755 index bd480b0..0000000 --- a/Example/Pods/GRDB.swift/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (C) 2019 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Example/Pods/GRDB.swift/README.md b/Example/Pods/GRDB.swift/README.md deleted file mode 100755 index 850fdf0..0000000 --- a/Example/Pods/GRDB.swift/README.md +++ /dev/null @@ -1,8709 +0,0 @@ -GRDB 4 [![Swift 4.2](https://img.shields.io/badge/swift-4.2-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Swift 5](https://img.shields.io/badge/swift-5-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platforms](https://img.shields.io/cocoapods/p/GRDB.swift.svg)](https://developer.apple.com/swift/) [![License](https://img.shields.io/github/license/groue/GRDB.swift.svg?maxAge=2592000)](/LICENSE) [![Build Status](https://travis-ci.org/groue/GRDB.swift.svg?branch=master)](https://travis-ci.org/groue/GRDB.swift) -========== - -### A toolkit for SQLite databases, with a focus on application development - ---- - -**Latest release**: May 25, 2019 • version 4.0.1 • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 3 to GRDB 4](Documentation/GRDB3MigrationGuide.md) - -**Requirements**: iOS 9.0+ / macOS 10.9+ / watchOS 2.0+ • Swift 4.2+ / Xcode 10.0+ - -| Swift version | GRDB version | -| ------------- | ----------------------------------------------------------- | -| **Swift 5** | **v4.0.1** | -| **Swift 4.2** | **v4.0.1** | -| Swift 4.1 | [v3.7.0](https://github.com/groue/GRDB.swift/tree/v3.7.0) | -| Swift 4 | [v2.10.0](https://github.com/groue/GRDB.swift/tree/v2.10.0) | -| Swift 3.2 | [v1.3.0](https://github.com/groue/GRDB.swift/tree/v1.3.0) | -| Swift 3.1 | [v1.3.0](https://github.com/groue/GRDB.swift/tree/v1.3.0) | -| Swift 3 | [v1.0](https://github.com/groue/GRDB.swift/tree/v1.0) | -| Swift 2.3 | [v0.81.2](https://github.com/groue/GRDB.swift/tree/v0.81.2) | -| Swift 2.2 | [v0.80.2](https://github.com/groue/GRDB.swift/tree/v0.80.2) | - -**Contact**: - -- Release announcements and usage tips: follow [@groue](http://twitter.com/groue) on Twitter. -- Report bugs in a [Github issue](https://github.com/groue/GRDB.swift/issues/new). Make sure you check the [existing issues](https://github.com/groue/GRDB.swift/issues?q=is%3Aopen) first. -- A question? Looking for advice? Do you wonder how to contribute? Fancy a chat? Go to the [GRDB forums](https://forums.swift.org/c/related-projects/grdb), or open a [Github issue](https://github.com/groue/GRDB.swift/issues/new). - - -## What is this? - -GRDB provides raw access to SQL and advanced SQLite features, because one sometimes enjoys a sharp tool. It has robust concurrency primitives, so that multi-threaded applications can efficiently use their databases. It grants your application models with persistence and fetching methods, so that you don't have to deal with SQL and raw database rows when you don't want to. - -Compared to [SQLite.swift](http://github.com/stephencelis/SQLite.swift) or [FMDB](http://github.com/ccgus/fmdb), GRDB can spare you a lot of glue code. Compared to [Core Data](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/) or [Realm](http://realm.io), it can simplify your multi-threaded applications. - -It comes with [up-to-date documentation](#documentation), [general guides](#general-guides--good-practices), and it is [fast](https://github.com/groue/GRDB.swift/wiki/Performance). - -See [Why Adopt GRDB?](Documentation/WhyAdoptGRDB.md) if you are looking for your favorite database library. - - ---- - -

- Features • - Usage • - Installation • - Documentation • - Demo Application • - FAQ -

- ---- - - -## Features - -GRDB ships with: - -- [Access to raw SQL and SQLite](#sqlite-api) -- [Records](#records): Fetching and persistence methods for your custom structs and class hierarchies. -- [Query Interface](#the-query-interface): A swift way to avoid the SQL language. -- [Associations](Documentation/AssociationsBasics.md): Relations and joins between record types. -- [WAL Mode Support](#database-pools): Extra performance for multi-threaded applications. -- [Migrations](#migrations): Transform your database as your application evolves. -- [Database Observation](#database-changes-observation): Observe database changes and transactions. -- [Full-Text Search](#full-text-search) -- [Encryption](#encryption) -- [Support for Custom SQLite Builds](Documentation/CustomSQLiteBuilds.md) - -Companion libraries that enhance and extend GRDB: - -- [RxGRDB]: track database changes in a reactive way, with [RxSwift](https://github.com/ReactiveX/RxSwift). -- [GRDBObjc](https://github.com/groue/GRDBObjc): FMDB-compatible bindings to GRDB. - - -## Usage - -
- Connect to an SQLite database - -```swift -import GRDB - -// Simple database connection -let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite") - -// Enhanced multithreading based on SQLite's WAL mode -let dbPool = try DatabasePool(path: "/path/to/database.sqlite") -``` - -See [Database Connections](#database-connections) - -
- -
- Execute SQL statements - -```swift -try dbQueue.write { db in - try db.execute(sql: """ - CREATE TABLE place ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - favorite BOOLEAN NOT NULL DEFAULT 0, - latitude DOUBLE NOT NULL, - longitude DOUBLE NOT NULL) - """) - - try db.execute(sql: """ - INSERT INTO place (title, favorite, latitude, longitude) - VALUES (?, ?, ?, ?) - """, arguments: ["Paris", true, 48.85341, 2.3488]) - - let parisId = db.lastInsertedRowID - - // Swift 5 only - try db.execute(literal: """ - INSERT INTO place (title, favorite, latitude, longitude) - VALUES (\("Madrid"), \(true), \(40.41678), \(-3.70379)) - """) -} -``` - -See [Executing Updates](#executing-updates) - -
- -
- Fetch database rows and values - -```swift -try dbQueue.read { db in - // Fetch database rows - let rows = try Row.fetchCursor(db, sql: "SELECT * FROM place") - while let row = try rows.next() { - let title: String = row["title"] - let isFavorite: Bool = row["favorite"] - let coordinate = CLLocationCoordinate2D( - latitude: row["latitude"], - longitude: row["longitude"]) - } - - // Fetch values - let placeCount = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM place")! // Int - let placeTitles = try String.fetchAll(db, sql: "SELECT title FROM place") // [String] -} - -let placeCount = try dbQueue.read { db in - try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM place")! -} -``` - -See [Fetch Queries](#fetch-queries) - -
- -
- Store custom models aka "records" - -```swift -struct Place { - var id: Int64? - var title: String - var isFavorite: Bool - var coordinate: CLLocationCoordinate2D -} - -// snip: turn Place into a "record" by adopting the protocols that -// provide fetching and persistence methods. - -try dbQueue.write { db in - // Create database table - try db.create(table: "place") { t in - t.autoIncrementedPrimaryKey("id") - t.column("title", .text).notNull() - t.column("favorite", .boolean).notNull().defaults(to: false) - t.column("longitude", .double).notNull() - t.column("latitude", .double).notNull() - } - - var berlin = Place( - id: nil, - title: "Berlin", - isFavorite: false, - coordinate: CLLocationCoordinate2D(latitude: 52.52437, longitude: 13.41053)) - - try berlin.insert(db) - berlin.id // some value - - berlin.isFavorite = true - try berlin.update(db) -} -``` - -See [Records](#records) - -
- -
- Fetch records and values with the Swift query interface - -```swift -try dbQueue.read { db in - // Place? - let paris = try Place.fetchOne(db, key: 1) - - // Place? - let berlin = try Place.filter(Column("title") == "Berlin").fetchOne(db) - - // [Place] - let favoritePlaces = try Place - .filter(Column("favorite") == true) - .order(Column("title")) - .fetchAll(db) - - // Int - let favoriteCount = try Place.filter(Column("favorite")).fetchCount(db) - - // SQL is always welcome - let places = try Place.fetchAll(db, sql: "SELECT * FROM place") -} -``` - -See the [Query Interface](#the-query-interface) - -
- -
- Be notified of database changes - -```swift -let request = Place.order(Column("title")) -try ValueObservation - .trackingAll(request) - .start(in: dbQueue) { (places: [Place]) in - print("Places have changed.") - } -``` - -See [Database Changes Observation](#database-changes-observation) - -
- - -Documentation -============= - -**GRDB runs on top of SQLite**: you should get familiar with the [SQLite FAQ](http://www.sqlite.org/faq.html). For general and detailed information, jump to the [SQLite Documentation](http://www.sqlite.org/docs.html). - -#### Reference - -- [GRDB Reference](http://groue.github.io/GRDB.swift/docs/4.0/index.html) (generated by [Jazzy](https://github.com/realm/jazzy)) - -#### Getting Started - -- [Installation](#installation) -- [Demo Application](#demo-application) -- [Database Connections](#database-connections): Connect to SQLite databases - -#### SQLite and SQL - -- [SQLite API](#sqlite-api): The low-level SQLite API • [executing updates](#executing-updates) • [fetch queries](#fetch-queries) • [SQL Interpolation] - -#### Records and the Query Interface - -- [Records](#records): Fetching and persistence methods for your custom structs and class hierarchies. -- [Query Interface](#the-query-interface): A swift way to generate SQL • [table creation](#database-schema) • [requests](#requests) - -#### Application Tools - -- [Migrations](#migrations): Transform your database as your application evolves. -- [Full-Text Search](#full-text-search): Perform efficient and customizable full-text searches. -- [Joined Queries Support](#joined-queries-support): Consume complex joined queries. -- [Database Changes Observation](#database-changes-observation): Observe database changes and transactions. -- [Encryption](#encryption): Encrypt your database with SQLCipher. -- [Backup](#backup): Dump the content of a database to another. - -#### Good to Know - -- [Avoiding SQL Injection](#avoiding-sql-injection) -- [Error Handling](#error-handling) -- [Unicode](#unicode) -- [Memory Management](#memory-management) -- [Data Protection](#data-protection) -- [Concurrency](#concurrency) -- [Performance](#performance) - -#### General Guides & Good Practices - -- :bulb: [Good Practices for Designing Record Types](Documentation/GoodPracticesForDesigningRecordTypes.md) -- :bulb: [Migrating From GRDB 2 to GRDB 3](Documentation/GRDB2MigrationGuide.md) -- :bulb: [Issues tagged "best practices"](https://github.com/groue/GRDB.swift/issues?q=is%3Aissue+label%3A%22best+practices%22) -- :question: [Issues tagged "question"](https://github.com/groue/GRDB.swift/issues?utf8=✓&q=is%3Aissue%20label%3Aquestion) -- :blue_book: [Why Adopt GRDB?](Documentation/WhyAdoptGRDB.md) -- :blue_book: [How to build an iOS application with SQLite and GRDB.swift](https://medium.com/@gwendal.roue/how-to-build-an-ios-application-with-sqlite-and-grdb-swift-d023a06c29b3) -- :blue_book: [Four different ways to handle SQLite concurrency](https://medium.com/@gwendal.roue/four-different-ways-to-handle-sqlite-concurrency-db3bcc74d00e) -- :blue_book: [Unexpected SQLite with Swift](https://hackernoon.com/unexpected-sqlite-with-swift-ddc6343bcbfc) - - -**[FAQ](#faq)** - -**[Sample Code](#sample-code)** - - -Installation -============ - -**The installation procedures below have GRDB use the version of SQLite that ships with the target operating system.** - -See [Encryption](#encryption) for the installation procedure of GRDB with SQLCipher. - -See [Custom SQLite builds](Documentation/CustomSQLiteBuilds.md) for the installation procedure of GRDB with a customized build of SQLite 3.27.2. - -See [Enabling FTS5 Support](#enabling-fts5-support) for the installation procedure of GRDB with support for the FTS5 full-text engine. - - -## CocoaPods - -[CocoaPods](http://cocoapods.org/) is a dependency manager for Xcode projects. To use GRDB with CocoaPods (version 1.2 or higher), specify in your `Podfile`: - -```ruby -use_frameworks! -pod 'GRDB.swift' -``` - - -## Swift Package Manager - -The [Swift Package Manager](https://swift.org/package-manager/) automates the distribution of Swift code. To use GRDB with SPM, add a dependency to your `Package.swift` file: - -```swift -let package = Package( - dependencies: [ - .package(url: "https://github.com/groue/GRDB.swift.git", from: "4.0.1") - ] -) -``` - -Note that Linux is not currently supported. - - -## Carthage - -[Carthage](https://github.com/Carthage/Carthage) is **unsupported**. For some context about this decision, see [#433](https://github.com/groue/GRDB.swift/issues/433). - - -## Manually - -1. [Download](https://github.com/groue/GRDB.swift/releases/tag/v4.0.1) a copy of GRDB, or clone its repository and make sure you use the latest tagged version with the `git checkout v4.0.1` command. - -2. Embed the `GRDB.xcodeproj` project in your own project. - -3. Add the `GRDBOSX`, `GRDBiOS`, or `GRDBWatchOS` target in the **Target Dependencies** section of the **Build Phases** tab of your application target (extension target for WatchOS). - -4. Add the `GRDB.framework` from the targetted platform to the **Embedded Binaries** section of the **General** tab of your application target (extension target for WatchOS). - -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for an example of such integration. - - -Demo Application -================ - -The repository comes with a [demo application](DemoApps/GRDBDemoiOS/README.md) that shows you: - -- how to setup a database in an iOS app -- how to define a simple [Codable Record](#codable-records) -- how to track database changes with [ValueObservation] and [FetchedRecordsController]. - - -Database Connections -==================== - -GRDB provides two classes for accessing SQLite databases: `DatabaseQueue` and `DatabasePool`: - -```swift -import GRDB - -// Pick one: -let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite") -let dbPool = try DatabasePool(path: "/path/to/database.sqlite") -``` - -The differences are: - -- Database pools allow concurrent database accesses (this can improve the performance of multithreaded applications). -- Database pools open your SQLite database in the [WAL mode](https://www.sqlite.org/wal.html) (unless read-only). -- Database queues support [in-memory databases](https://www.sqlite.org/inmemorydb.html). - -**If you are not sure, choose DatabaseQueue.** You will always be able to switch to DatabasePool later. - -- [Database Queues](#database-queues) -- [Database Pools](#database-pools) - - -## Database Queues - -**Open a database queue** with the path to a database file: - -```swift -import GRDB - -let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite") -let inMemoryDBQueue = DatabaseQueue() -``` - -SQLite creates the database file if it does not already exist. The connection is closed when the database queue gets deallocated. - -**A database queue can be used from any thread.** The `write` and `read` methods are synchronous, and block the current thread until your database statements are executed in a protected dispatch queue: - -```swift -// Modify the database: -try dbQueue.write { db in - try db.create(table: "place") { ... } - try Place(...).insert(db) -} - -// Read values: -try dbQueue.read { db in - let places = try Place.fetchAll(db) - let placeCount = try Place.fetchCount(db) -} -``` - -Database access methods can return values: - -```swift -let placeCount = try dbQueue.read { db in - try Place.fetchCount(db) -} - -let newPlaceCount = try dbQueue.write { db -> Int in - try Place(...).insert(db) - return try Place.fetchCount(db) -} -``` - -**A database queue serializes accesses to the database**, which means that there is never more than one thread that uses the database. - -- When you don't need to modify the database, prefer the `read` method. It prevents any modification to the database. - -- The `write` method wraps your database statements in a transaction that commits if and only if no error occurs. On the first unhandled error, all changes are reverted, the whole transaction is rollbacked, and the error is rethrown. - - When precise transaction handling is required, see [Transactions and Savepoints](#transactions-and-savepoints). - -**A database queue needs your application to follow rules in order to deliver its safety guarantees.** Please refer to the [Concurrency](#concurrency) chapter. - -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample code that sets up a database queue on iOS. - - -### DatabaseQueue Configuration - -```swift -var config = Configuration() -config.readonly = true -config.foreignKeysEnabled = true // Default is already true -config.trace = { print($0) } // Prints all SQL statements -config.label = "MyDatabase" // Useful when your app opens multiple databases - -let dbQueue = try DatabaseQueue( - path: "/path/to/database.sqlite", - configuration: config) -``` - -See [Configuration](http://groue.github.io/GRDB.swift/docs/4.0/Structs/Configuration.html) for more details. - - -## Database Pools - -**A database pool allows concurrent database accesses.** - -```swift -import GRDB -let dbPool = try DatabasePool(path: "/path/to/database.sqlite") -``` - -SQLite creates the database file if it does not already exist. The connection is closed when the database pool gets deallocated. - -> :point_up: **Note**: unless read-only, a database pool opens your database in the SQLite "WAL mode". The WAL mode does not fit all situations. Please have a look at https://www.sqlite.org/wal.html. - -**A database pool can be used from any thread.** The `write` and `read` methods are synchronous, and block the current thread until your database statements are executed in a protected dispatch queue: - -```swift -// Modify the database: -try dbPool.write { db in - try db.create(table: "place") { ... } - try Place(...).insert(db) -} - -// Read values: -try dbPool.read { db in - let places = try Place.fetchAll(db) - let placeCount = try Place.fetchCount(db) -} -``` - -Database access methods can return values: - -```swift -let placeCount = try dbPool.read { db in - try Place.fetchCount(db) -} - -let newPlaceCount = try dbPool.write { db -> Int in - try Place(...).insert(db) - return try Place.fetchCount(db) -} -``` - -**Database pools allow several threads to access the database at the same time:** - -- When you don't need to modify the database, prefer the `read` method, because several threads can perform reads in parallel. - - Reads are generally non-blocking, unless the maximum number of concurrent reads has been reached. In this case, a read has to wait for another read to complete. That maximum number can be [configured](#databasepool-configuration). - -- Reads are guaranteed an immutable view of the last committed state of the database, regardless of concurrent writes. This kind of isolation is called [snapshot isolation](https://sqlite.org/isolation.html). - -- Unlike reads, writes are serialized. There is never more than a single thread that is writing into the database. - -- The `write` method wraps your database statements in a transaction that commits if and only if no error occurs. On the first unhandled error, all changes are reverted, the whole transaction is rollbacked, and the error is rethrown. - - When precise transaction handling is required, see [Transactions and Savepoints](#transactions-and-savepoints). - -- Database pools can take [snapshots](#database-snapshots) of the database. - -**A database pool needs your application to follow rules in order to deliver its safety guarantees.** See the [Concurrency](#concurrency) chapter for more details about database pools, how they differ from database queues, and advanced use cases. - -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample code that sets up a database queue on iOS, and just replace DatabaseQueue with DatabasePool. - - -### DatabasePool Configuration - -```swift -var config = Configuration() -config.readonly = true -config.foreignKeysEnabled = true // Default is already true -config.trace = { print($0) } // Prints all SQL statements -config.label = "MyDatabase" // Useful when your app opens multiple databases -config.maximumReaderCount = 10 // The default is 5 - -let dbPool = try DatabasePool( - path: "/path/to/database.sqlite", - configuration: config) -``` - -See [Configuration](http://groue.github.io/GRDB.swift/docs/4.0/Structs/Configuration.html) for more details. - - -Database pools are more memory-hungry than database queues. See [Memory Management](#memory-management) for more information. - - -SQLite API -========== - -**In this section of the documentation, we will talk SQL.** Jump to the [query interface](#the-query-interface) if SQL is not your cup of tea. - -- [Executing Updates](#executing-updates) -- [Fetch Queries](#fetch-queries) - - [Fetching Methods](#fetching-methods) - - [Row Queries](#row-queries) - - [Value Queries](#value-queries) -- [Values](#values) - - [Data](#data-and-memory-savings) - - [Date and DateComponents](#date-and-datecomponents) - - [NSNumber and NSDecimalNumber](#nsnumber-and-nsdecimalnumber) - - [Swift enums](#swift-enums) - - [Custom Value Types](#custom-value-types) -- [Transactions and Savepoints](#transactions-and-savepoints) -- [SQL Interpolation] - -Advanced topics: - -- [Prepared Statements](#prepared-statements) -- [Custom SQL Functions and Aggregates](#custom-sql-functions-and-aggregates) -- [Database Schema Introspection](#database-schema-introspection) -- [Row Adapters](#row-adapters) -- [Raw SQLite Pointers](#raw-sqlite-pointers) - - -## Executing Updates - -Once granted with a [database connection](#database-connections), the `execute` method executes the SQL statements that do not return any database row, such as `CREATE TABLE`, `INSERT`, `DELETE`, `ALTER`, etc. - -For example: - -```swift -try dbQueue.write { db in - try db.execute(sql: """ - CREATE TABLE player ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - score INT) - """) - - try db.execute( - sql: "INSERT INTO player (name, score) VALUES (?, ?)", - arguments: ["Barbara", 1000]) - - try db.execute( - sql: "UPDATE player SET score = :score WHERE id = :id", - arguments: ["score": 1000, "id": 1]) - } -} -``` - -The `?` and colon-prefixed keys like `:score` in the SQL query are the **statements arguments**. You pass arguments with arrays or dictionaries, as in the example above. See [Values](#values) for more information on supported arguments types (Bool, Int, String, Date, Swift enums, etc.), and [StatementArguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html) for a detailed documentation of SQLite arguments. - -In Swift 5, you can embed query arguments right into your SQL queries, with the `literal` argument label, as in the example below. See [SQL Interpolation] for more details. - -```swift -// Swift 5 -try dbQueue.write { db in - try db.execute(literal: """ - INSERT INTO player (name, score) - VALUES (\("O'Brien"), \(550)) - """) -} -``` - -**Never ever embed values directly in your raw SQL strings**. See [Avoiding SQL Injection](#avoiding-sql-injection) for more information: - -```swift -// WRONG: don't embed values in raw SQL strings -let id = 123 -let name = textField.text -try db.execute( - sql: "UPDATE player SET name = '\(name)' WHERE id = \(id)") - -// CORRECT: use SQL Interpolation (Swift 5) -try db.execute( - literal: "UPDATE player SET name = \(name) WHERE id = \(id)") - -// CORRECT: use arguments dictionary -try db.execute( - sql: "UPDATE player SET name = :name WHERE id = :id", - arguments: ["name": name, "id": id]) - -// CORRECT: use arguments array -try db.execute( - sql: "UPDATE player SET name = ? WHERE id = ?", - arguments: [name, id]) -``` - -**Join multiple statements with a semicolon**: - -```swift -try db.execute(sql: """ - INSERT INTO player (name, score) VALUES (?, ?); - INSERT INTO player (name, score) VALUES (?, ?); - """, arguments: ["Arthur", 750, "Barbara", 1000]) - -// Swift 5 -try db.execute(literal: """ - INSERT INTO player (name, score) VALUES (\("Arthur"), \(750)); - INSERT INTO player (name, score) VALUES (\("Barbara"), \(1000)); - """) -``` - -When you want to make sure that a single statement is executed, use [Prepared Statements](#prepared-statements). - -**After an INSERT statement**, you can get the row ID of the inserted row: - -```swift -try db.execute( - sql: "INSERT INTO player (name, score) VALUES (?, ?)", - arguments: ["Arthur", 1000]) -let playerId = db.lastInsertedRowID -``` - -Don't miss [Records](#records), that provide classic **persistence methods**: - -```swift -var player = Player(name: "Arthur", score: 1000) -try player.insert(db) -let playerId = player.id -``` - - -## Fetch Queries - -[Database connections](#database-connections) let you fetch database rows, plain values, and custom models aka "records". - -**Rows** are the raw results of SQL queries: - -```swift -try dbQueue.read { db in - if let row = try Row.fetchOne(db, sql: "SELECT * FROM wine WHERE id = ?", arguments: [1]) { - let name: String = row["name"] - let color: Color = row["color"] - print(name, color) - } -} -``` - - -**Values** are the Bool, Int, String, Date, Swift enums, etc. stored in row columns: - -```swift -try dbQueue.read { db in - let urls = try URL.fetchCursor(db, sql: "SELECT url FROM wine") - while let url = try urls.next() { - print(url) - } -} -``` - - -**Records** are your application objects that can initialize themselves from rows: - -```swift -let wines = try dbQueue.read { db in - try Wine.fetchAll(db, sql: "SELECT * FROM wine") -} -``` - -- [Fetching Methods](#fetching-methods) and [Cursors](#cursors) -- [Row Queries](#row-queries) -- [Value Queries](#value-queries) -- [Records](#records) - - -### Fetching Methods - -**Throughout GRDB**, you can always fetch *cursors*, *arrays*, or *single values* of any fetchable type (database [row](#row-queries), simple [value](#value-queries), or custom [record](#records)): - -```swift -try Row.fetchCursor(...) // A Cursor of Row -try Row.fetchAll(...) // [Row] -try Row.fetchOne(...) // Row? -``` - -- `fetchCursor` returns a **[cursor](#cursors)** over fetched values: - - ```swift - let rows = try Row.fetchCursor(db, sql: "SELECT ...") // A Cursor of Row - ``` - -- `fetchAll` returns an **array**: - - ```swift - let players = try Player.fetchAll(db, sql: "SELECT ...") // [Player] - ``` - -- `fetchOne` returns a **single optional value**, and consumes a single database row (if any). - - ```swift - let count = try Int.fetchOne(db, sql: "SELECT COUNT(*) ...") // Int? - ``` - - -### Cursors - -**Whenever you consume several rows from the database, you can fetch an Array, or a Cursor**. - -The `fetchAll()` method returns a regular Swift array, that you iterate like all other arrays: - -```swift -try dbQueue.read { db in - // [Player] - let players = try Player.fetchAll(db, sql: "SELECT ...") - for player in players { - // use player - } -} -``` - -Unlike arrays, cursors returned by `fetchCursor()` load their results step after step: - -```swift -try dbQueue.read { db in - // Cursor of Player - let players = try Player.fetchCursor(db, sql: "SELECT ...") - while let player = try players.next() { - // use player - } -} -``` - -Both arrays and cursors can iterate over database results. How do you choose one or the other? Look at the differences: - -- **Cursors can not be used on any thread**: you must consume a cursor on the dispatch queue it was created in. Particularly, don't extract a cursor out of a database access method: - - ```swift - // Wrong - let cursor = try dbQueue.read { db in - try Player.fetchCursor(db, ...) - } - while let player = try cursor.next() { ... } - ``` - - Conversely, arrays may be consumed on any thread: - - ```swift - // OK - let array = try dbQueue.read { db in - try Player.fetchAll(db, ...) - } - for player in array { ... } - ``` - -- **Cursors can be iterated only one time.** Arrays can be iterated many times. - -- **Cursors iterate database results in a lazy fashion**, and don't consume much memory. Arrays contain copies of database values, and may take a lot of memory when there are many fetched results. - -- **Cursors are granted with direct access to SQLite,** unlike arrays that have to take the time to copy database values. If you look after extra performance, you may prefer cursors over arrays. - -- **Cursors adopt the [Cursor](http://groue.github.io/GRDB.swift/docs/4.0/Protocols/Cursor.html) protocol, which looks a lot like standard [lazy sequences](https://developer.apple.com/reference/swift/lazysequenceprotocol) of Swift.** As such, cursors come with many convenience methods: `compactMap`, `contains`, `dropFirst`, `dropLast`, `drop(while:)`, `enumerated`, `filter`, `first`, `flatMap`, `forEach`, `joined`, `joined(separator:)`, `max`, `max(by:)`, `min`, `min(by:)`, `map`, `prefix`, `prefix(while:)`, `reduce`, `reduce(into:)`, `suffix`: - - ```swift - // Prints all Github links - try URL - .fetchCursor(db, sql: "SELECT url FROM link") - .filter { url in url.host == "github.com" } - .forEach { url in print(url) } - - // An efficient cursor of coordinates: - let locations = try Row. - .fetchCursor(db, sql: "SELECT latitude, longitude FROM place") - .map { row in - CLLocationCoordinate2D(latitude: row[0], longitude: row[1]) - } - - // Turn cursors into arrays or sets: - let array = try Array(cursor) - let set = try Set(cursor) - ``` - -- **Cursors are not Swift sequences.** That's because Swift sequences can't handle iteration errors, when reading SQLite results may fail at any time. SQL functions may throw errors. On iOS, [data protection](#data-protection) may block access to the database file in the background. On macOS, your application users may mess with the file system. - -- **Cursors require a little care**: - - - Don't modify the results during a cursor iteration: - - ```swift - // Undefined behavior - while let player = try players.next() { - try db.execute(sql: "DELETE ...") - } - ``` - - - Don't turn a cursor of `Row` into an array. You would not get the distinct rows you expect. To get a array of rows, use `Row.fetchAll(...)`. Generally speaking, make sure you copy a row whenever you extract it from a cursor for later use: `row.copy()`. - -If you don't see, or don't care about the difference, use arrays. If you care about memory and performance, use cursors when appropriate. - - -### Row Queries - -- [Fetching Rows](#fetching-rows) -- [Column Values](#column-values) -- [DatabaseValue](#databasevalue) -- [Rows as Dictionaries](#rows-as-dictionaries) - - -#### Fetching Rows - -Fetch **cursors** of rows, **arrays**, or **single** rows (see [fetching methods](#fetching-methods)): - -```swift -try dbQueue.read { db in - try Row.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Row - try Row.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Row] - try Row.fetchOne(db, sql: "SELECT ...", arguments: ...) // Row? - - let rows = try Row.fetchCursor(db, sql: "SELECT * FROM wine") - while let row = try rows.next() { - let name: String = row["name"] - let color: Color = row["color"] - print(name, color) - } -} - -let rows = try dbQueue.read { db in - try Row.fetchAll(db, sql: "SELECT * FROM player") -} -``` - -Arguments are optional arrays or dictionaries that fill the positional `?` and colon-prefixed keys like `:name` in the query: - -```swift -let rows = try Row.fetchAll(db, - sql: "SELECT * FROM player WHERE name = ?", - arguments: ["Arthur"]) - -let rows = try Row.fetchAll(db, - sql: "SELECT * FROM player WHERE name = :name", - arguments: ["name": "Arthur"]) -``` - -See [Values](#values) for more information on supported arguments types (Bool, Int, String, Date, Swift enums, etc.), and [StatementArguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html) for a detailed documentation of SQLite arguments. - -Unlike row arrays that contain copies of the database rows, row cursors are close to the SQLite metal, and require a little care: - -> :point_up: **Don't turn a cursor of `Row` into an array**. You would not get the distinct rows you expect. To get a array of rows, use `Row.fetchAll(...)`. Generally speaking, make sure you copy a row whenever you extract it from a cursor for later use: `row.copy()`. - - -#### Column Values - -**Read column values** by index or column name: - -```swift -let name: String = row[0] // 0 is the leftmost column -let name: String = row["name"] // Leftmost matching column - lookup is case-insensitive -let name: String = row[Column("name")] // Using query interface's Column -``` - -Make sure to ask for an optional when the value may be NULL: - -```swift -let name: String? = row["name"] -``` - -The `row[]` subscript returns the type you ask for. See [Values](#values) for more information on supported value types: - -```swift -let bookCount: Int = row["bookCount"] -let bookCount64: Int64 = row["bookCount"] -let hasBooks: Bool = row["bookCount"] // false when 0 - -let string: String = row["date"] // "2015-09-11 18:14:15.123" -let date: Date = row["date"] // Date -self.date = row["date"] // Depends on the type of the property. -``` - -You can also use the `as` type casting operator: - -```swift -row[...] as Int -row[...] as Int? -``` - -> :warning: **Warning**: avoid the `as!` and `as?` operators: -> -> ```swift -> if let int = row[...] as? Int { ... } // BAD - doesn't work -> if let int = row[...] as Int? { ... } // GOOD -> ``` - -Generally speaking, you can extract the type you need, provided it can be converted from the underlying SQLite value: - -- **Successful conversions include:** - - - All numeric SQLite values to all numeric Swift types, and Bool (zero is the only false boolean). - - Text SQLite values to Swift String. - - Blob SQLite values to Foundation Data. - - See [Values](#values) for more information on supported types (Bool, Int, String, Date, Swift enums, etc.) - -- **NULL returns nil.** - - ```swift - let row = try Row.fetchOne(db, sql: "SELECT NULL")! - row[0] as Int? // nil - row[0] as Int // fatal error: could not convert NULL to Int. - ``` - - There is one exception, though: the [DatabaseValue](#databasevalue) type: - - ```swift - row[0] as DatabaseValue // DatabaseValue.null - ``` - -- **Missing columns return nil.** - - ```swift - let row = try Row.fetchOne(db, sql: "SELECT 'foo' AS foo")! - row["missing"] as String? // nil - row["missing"] as String // fatal error: no such column: missing - ``` - - You can explicitly check for a column presence with the `hasColumn` method. - -- **Invalid conversions throw a fatal error.** - - ```swift - let row = try Row.fetchOne(db, sql: "SELECT 'Mom’s birthday'")! - row[0] as String // "Mom’s birthday" - row[0] as Date? // fatal error: could not convert "Mom’s birthday" to Date. - row[0] as Date // fatal error: could not convert "Mom’s birthday" to Date. - - let row = try Row.fetchOne(db, sql: "SELECT 256")! - row[0] as Int // 256 - row[0] as UInt8? // fatal error: could not convert 256 to UInt8. - row[0] as UInt8 // fatal error: could not convert 256 to UInt8. - ``` - - Those conversion fatal errors can be avoided with the [DatabaseValue](#databasevalue) type: - - ```swift - let row = try Row.fetchOne(db, sql: "SELECT 'Mom’s birthday'")! - let dbValue: DatabaseValue = row[0] - if dbValue.isNull { - // Handle NULL - } else if let date = Date.fromDatabaseValue(dbValue) { - // Handle valid date - } else { - // Handle invalid date - } - ``` - - This extra verbosity is the consequence of having to deal with an untrusted database: you may consider fixing the content of your database instead. See [Fatal Errors](#fatal-errors) for more information. - -- **SQLite has a weak type system, and provides [convenience conversions](https://www.sqlite.org/c3ref/column_blob.html) that can turn String to Int, Double to Blob, etc.** - - GRDB will sometimes let those conversions go through: - - ```swift - let rows = try Row.fetchCursor(db, sql: "SELECT '20 small cigars'") - while let row = try rows.next() { - row[0] as Int // 20 - } - ``` - - Don't freak out: those conversions did not prevent SQLite from becoming the immensely successful database engine you want to use. And GRDB adds safety checks described just above. You can also prevent those convenience conversions altogether by using the [DatabaseValue](#databasevalue) type. - - -#### DatabaseValue - -**DatabaseValue is an intermediate type between SQLite and your values, which gives information about the raw value stored in the database.** - -You get DatabaseValue just like other value types: - -```swift -let dbValue: DatabaseValue = row[0] -let dbValue: DatabaseValue? = row["name"] // nil if and only if column does not exist - -// Check for NULL: -dbValue.isNull // Bool - -// The stored value: -dbValue.storage.value // Int64, Double, String, Data, or nil - -// All the five storage classes supported by SQLite: -switch dbValue.storage { -case .null: print("NULL") -case .int64(let int64): print("Int64: \(int64)") -case .double(let double): print("Double: \(double)") -case .string(let string): print("String: \(string)") -case .blob(let data): print("Data: \(data)") -} -``` - -You can extract regular [values](#values) (Bool, Int, String, Date, Swift enums, etc.) from DatabaseValue with the [DatabaseValueConvertible.fromDatabaseValue()](#custom-value-types) method: - -```swift -let dbValue: DatabaseValue = row["bookCount"] -let bookCount = Int.fromDatabaseValue(dbValue) // Int? -let bookCount64 = Int64.fromDatabaseValue(dbValue) // Int64? -let hasBooks = Bool.fromDatabaseValue(dbValue) // Bool?, false when 0 - -let dbValue: DatabaseValue = row["date"] -let string = String.fromDatabaseValue(dbValue) // "2015-09-11 18:14:15.123" -let date = Date.fromDatabaseValue(dbValue) // Date? -``` - -`fromDatabaseValue` returns nil for invalid conversions: - -```swift -let row = try Row.fetchOne(db, sql: "SELECT 'Mom’s birthday'")! -let dbValue: DatabaseValue = row[0] -let string = String.fromDatabaseValue(dbValue) // "Mom’s birthday" -let int = Int.fromDatabaseValue(dbValue) // nil -let date = Date.fromDatabaseValue(dbValue) // nil -``` - - -#### Rows as Dictionaries - -Row adopts the standard [RandomAccessCollection](https://developer.apple.com/documentation/swift/randomaccesscollection) protocol, and can be seen as a dictionary of [DatabaseValue](#databasevalue): - -```swift -// All the (columnName, dbValue) tuples, from left to right: -for (columnName, dbValue) in row { - ... -} -``` - -**You can build rows from dictionaries** (standard Swift dictionaries and NSDictionary). See [Values](#values) for more information on supported types: - -```swift -let row: Row = ["name": "foo", "date": nil] -let row = Row(["name": "foo", "date": nil]) -let row = Row(/* [AnyHashable: Any] */) // nil if invalid dictionary -``` - -Yet rows are not real dictionaries: they may contain duplicate columns: - -```swift -let row = try Row.fetchOne(db, sql: "SELECT 1 AS foo, 2 AS foo")! -row.columnNames // ["foo", "foo"] -row.databaseValues // [1, 2] -row["foo"] // 1 (leftmost matching column) -for (columnName, dbValue) in row { ... } // ("foo", 1), ("foo", 2) -``` - -**When you build a dictionary from a row**, you have to disambiguate identical columns, and choose how to present database values. For example: - -- A `[String: DatabaseValue]` dictionary that keeps leftmost value in case of duplicated column name: - - ```swift - let dict = Dictionary(row, uniquingKeysWith: { (left, _) in left }) - ``` - -- A `[String: AnyObject]` dictionary which keeps rightmost value in case of duplicated column name. This dictionary is identical to FMResultSet's resultDictionary from FMDB. It contains NSNull values for null columns, and can be shared with Objective-C: - - ```swift - let dict = Dictionary( - row.map { (column, dbValue) in - (column, dbValue.storage.value as AnyObject) - }, - uniquingKeysWith: { (_, right) in right }) - ``` - -- A `[String: Any]` dictionary that can feed, for example, JSONSerialization: - - ```swift - let dict = Dictionary( - row.map { (column, dbValue) in - (column, dbValue.storage.value) - }, - uniquingKeysWith: { (left, _) in left }) - ``` - -See the documentation of [`Dictionary.init(_:uniquingKeysWith:)`](https://developer.apple.com/documentation/swift/dictionary/2892961-init) for more information. - - -### Value Queries - -Instead of rows, you can directly fetch **[values](#values)**. Like rows, fetch them as **cursors**, **arrays**, or **single** values (see [fetching methods](#fetching-methods)). Values are extracted from the leftmost column of the SQL queries: - -```swift -try dbQueue.read { db in - try Int.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Int - try Int.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Int] - try Int.fetchOne(db, sql: "SELECT ...", arguments: ...) // Int? - - // When database may contain NULL: - try Optional.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Int? - try Optional.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Int?] -} - -let playerCount = try dbQueue.read { db in - try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM player")! -} -``` - -`fetchOne` returns an optional value which is nil in two cases: either the SELECT statement yielded no row, or one row with a NULL value. - -There are many supported value types (Bool, Int, String, Date, Swift enums, etc.). See [Values](#values) for more information: - -```swift -let count = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM player")! // Int -let urls = try URL.fetchAll(db, sql: "SELECT url FROM link") // [URL] -``` - - -## Values - -GRDB ships with built-in support for the following value types: - -- **Swift Standard Library**: Bool, Double, Float, all signed and unsigned integer types, String, [Swift enums](#swift-enums). - -- **Foundation**: [Data](#data-and-memory-savings), [Date](#date-and-datecomponents), [DateComponents](#date-and-datecomponents), NSNull, [NSNumber](#nsnumber-and-nsdecimalnumber), NSString, URL, [UUID](#uuid). - -- **CoreGraphics**: CGFloat. - -- **[DatabaseValue](#databasevalue)**, the type which gives information about the raw value stored in the database. - -- **Full-Text Patterns**: [FTS3Pattern](#fts3pattern) and [FTS5Pattern](#fts5pattern). - -- Generally speaking, all types that adopt the [DatabaseValueConvertible](#custom-value-types) protocol. - -Values can be used as [statement arguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html): - -```swift -let url: URL = ... -let verified: Bool = ... -try db.execute( - sql: "INSERT INTO link (url, verified) VALUES (?, ?)", - arguments: [url, verified]) -``` - -Values can be [extracted from rows](#column-values): - -```swift -let rows = try Row.fetchCursor(db, sql: "SELECT * FROM link") -while let row = try rows.next() { - let url: URL = row["url"] - let verified: Bool = row["verified"] -} -``` - -Values can be [directly fetched](#value-queries): - -```swift -let urls = try URL.fetchAll(db, sql: "SELECT url FROM link") // [URL] -``` - -Use values in [Records](#records): - -```swift -struct Link: FetchableRecord { - var url: URL - var isVerified: Bool - - init(row: Row) { - url = row["url"] - isVerified = row["verified"] - } -} -``` - -Use values in the [query interface](#the-query-interface): - -```swift -let url: URL = ... -let link = try Link.filter(Column("url") == url).fetchOne(db) -``` - - -### Data (and Memory Savings) - -**Data** suits the BLOB SQLite columns. It can be stored and fetched from the database just like other [values](#values): - -```swift -let rows = try Row.fetchCursor(db, sql: "SELECT data, ...") -while let row = try rows.next() { - let data: Data = row["data"] -} -``` - -At each step of the request iteration, the `row[]` subscript creates *two copies* of the database bytes: one fetched by SQLite, and another, stored in the Swift Data value. - -**You have the opportunity to save memory** by not copying the data fetched by SQLite: - -```swift -while let row = try rows.next() { - let data = row.dataNoCopy(named: "data") // Data? -} -``` - -The non-copied data does not live longer than the iteration step: make sure that you do not use it past this point. - - -### Date and DateComponents - -[**Date**](#date) and [**DateComponents**](#datecomponents) can be stored and fetched from the database. - -Here is how GRDB supports the various [date formats](https://www.sqlite.org/lang_datefunc.html) supported by SQLite: - -| SQLite format | Date | DateComponents | -|:---------------------------- |:------------:|:--------------:| -| YYYY-MM-DD | Read ¹ | Read/Write | -| YYYY-MM-DD HH:MM | Read ¹ | Read/Write | -| YYYY-MM-DD HH:MM:SS | Read ¹ | Read/Write | -| YYYY-MM-DD HH:MM:SS.SSS | Read/Write ¹ | Read/Write | -| YYYY-MM-DD**T**HH:MM | Read ¹ | Read | -| YYYY-MM-DD**T**HH:MM:SS | Read ¹ | Read | -| YYYY-MM-DD**T**HH:MM:SS.SSS | Read ¹ | Read | -| HH:MM | | Read/Write | -| HH:MM:SS | | Read/Write | -| HH:MM:SS.SSS | | Read/Write | -| Timestamps since unix epoch | Read ² | | -| `now` | | | - -¹ Dates are stored and read in the UTC time zone. Missing components are assumed to be zero. - -² GRDB 2+ interprets numerical values as timestamps that fuel `Date(timeIntervalSince1970:)`. Previous GRDB versions used to interpret numbers as [julian days](https://en.wikipedia.org/wiki/Julian_day). Julian days are still supported, with the `Date(julianDay:)` initializer. - - -#### Date - -**Date** can be stored and fetched from the database just like other [values](#values): - -```swift -try db.execute( - sql: "INSERT INTO player (creationDate, ...) VALUES (?, ...)", - arguments: [Date(), ...]) - -let row = try Row.fetchOne(db, ...)! -let creationDate: Date = row["creationDate"] -``` - -Dates are stored using the format "YYYY-MM-DD HH:MM:SS.SSS" in the UTC time zone. It is precise to the millisecond. - -> :point_up: **Note**: this format was chosen because it is the only format that is: -> -> - Comparable (`ORDER BY date` works) -> - Comparable with the SQLite keyword CURRENT_TIMESTAMP (`WHERE date > CURRENT_TIMESTAMP` works) -> - Able to feed [SQLite date & time functions](https://www.sqlite.org/lang_datefunc.html) -> - Precise enough - -When the default format does not fit your needs, customize date conversions. For example: - -```swift -try db.execute( - sql: "INSERT INTO player (creationDate, ...) VALUES (?, ...)", - arguments: [Date().timeIntervalSinceReferenceDate, ...]) - -let row = try Row.fetchOne(db, ...)! -let creationDate = Date(timeIntervalSinceReferenceDate: row["creationDate"]) -``` - -See [Codable Records] for more date customization options. - - -#### DateComponents - -DateComponents is indirectly supported, through the **DatabaseDateComponents** helper type. - -DatabaseDateComponents reads date components from all [date formats supported by SQLite](https://www.sqlite.org/lang_datefunc.html), and stores them in the format of your choice, from HH:MM to YYYY-MM-DD HH:MM:SS.SSS. - -DatabaseDateComponents can be stored and fetched from the database just like other [values](#values): - -```swift -let components = DateComponents() -components.year = 1973 -components.month = 9 -components.day = 18 - -// Store "1973-09-18" -let dbComponents = DatabaseDateComponents(components, format: .YMD) -try db.execute( - sql: "INSERT INTO player (birthDate, ...) VALUES (?, ...)", - arguments: [dbComponents, ...]) - -// Read "1973-09-18" -let row = try Row.fetchOne(db, sql: "SELECT birthDate ...")! -let dbComponents: DatabaseDateComponents = row["birthDate"] -dbComponents.format // .YMD (the actual format found in the database) -dbComponents.dateComponents // DateComponents -``` - - -### NSNumber and NSDecimalNumber - -**NSNumber** can be stored and fetched from the database just like other [values](#values). Floating point NSNumbers are stored as Double. Integer and boolean, as Int64. Integers that don't fit Int64 won't be stored: you'll get a fatal error instead. Be cautious when an NSNumber contains an UInt64, for example. - -NSDecimalNumber deserves a longer discussion: - -**SQLite has no support for decimal numbers.** Given the table below, SQLite will actually store integers or doubles: - -```sql -CREATE TABLE transfer ( - amount DECIMAL(10,5) -- will store integer or double, actually -) -``` - -This means that computations will not be exact: - -```swift -try db.execute(sql: "INSERT INTO transfer (amount) VALUES (0.1)") -try db.execute(sql: "INSERT INTO transfer (amount) VALUES (0.2)") -let sum = try NSDecimalNumber.fetchOne(db, sql: "SELECT SUM(amount) FROM transfer")! - -// Yikes! 0.3000000000000000512 -print(sum) -``` - -Don't blame SQLite or GRDB, and instead store your decimal numbers differently. - -A classic technique is to store *integers* instead, since SQLite performs exact computations of integers. For example, don't store Euros, but store cents instead: - -```swift -// Write -let amount = NSDecimalNumber(string: "0.10") -let integerAmount = amount.multiplying(byPowerOf10: 2).int64Value -try db.execute(sql: "INSERT INTO transfer (amount) VALUES (?)", arguments: [integerAmount]) - -// Read -let integerAmount = try Int64.fetchOne(db, sql: "SELECT SUM(amount) FROM transfer")! -let amount = NSDecimalNumber(value: integerAmount).multiplying(byPowerOf10: -2) // 0.10 -``` - - -### UUID - -**UUID** can be stored and fetched from the database just like other [values](#values). - -GRDB stores uuids as 16-bytes data blobs, and decodes them from both 16-bytes data blobs and strings such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F". - - -### Swift Enums - -**Swift enums** and generally all types that adopt the [RawRepresentable](https://developer.apple.com/library/tvos/documentation/Swift/Reference/Swift_RawRepresentable_Protocol/index.html) protocol can be stored and fetched from the database just like their raw [values](#values): - -```swift -enum Color : Int { - case red, white, rose -} - -enum Grape : String { - case chardonnay, merlot, riesling -} - -// Declare empty DatabaseValueConvertible adoption -extension Color : DatabaseValueConvertible { } -extension Grape : DatabaseValueConvertible { } - -// Store -try db.execute( - sql: "INSERT INTO wine (grape, color) VALUES (?, ?)", - arguments: [Grape.merlot, Color.red]) - -// Read -let rows = try Row.fetchCursor(db, sql: "SELECT * FROM wine") -while let row = try rows.next() { - let grape: Grape = row["grape"] - let color: Color = row["color"] -} -``` - -**When a database value does not match any enum case**, you get a fatal error. This fatal error can be avoided with the [DatabaseValue](#databasevalue) type: - -```swift -let row = try Row.fetchOne(db, sql: "SELECT 'syrah'")! - -row[0] as String // "syrah" -row[0] as Grape? // fatal error: could not convert "syrah" to Grape. -row[0] as Grape // fatal error: could not convert "syrah" to Grape. - -let dbValue: DatabaseValue = row[0] -if dbValue.isNull { - // Handle NULL -} else if let grape = Grape.fromDatabaseValue(dbValue) { - // Handle valid grape -} else { - // Handle unknown grape -} -``` - - -### Custom Value Types - -Conversion to and from the database is based on the `DatabaseValueConvertible` protocol: - -```swift -protocol DatabaseValueConvertible { - /// Returns a value that can be stored in the database. - var databaseValue: DatabaseValue { get } - - /// Returns a value initialized from dbValue, if possible. - static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? -} -``` - -All types that adopt this protocol can be used like all other [values](#values) (Bool, Int, String, Date, Swift enums, etc.) - -The `databaseValue` property returns [DatabaseValue](#databasevalue), a type that wraps the five values supported by SQLite: NULL, Int64, Double, String and Data. Since DatabaseValue has no public initializer, use `DatabaseValue.null`, or another type that already adopts the protocol: `1.databaseValue`, `"foo".databaseValue`, etc. Conversion to DatabaseValue *must not* fail. - -The `fromDatabaseValue()` factory method returns an instance of your custom type if the database value contains a suitable value. If the database value does not contain a suitable value, such as "foo" for Date, `fromDatabaseValue` *must* return nil (GRDB will interpret this nil result as a conversion error, and react accordingly). - - -## Transactions and Savepoints - -- [Transactions and Safety](#transactions-and-safety) -- [Explicit Transactions](#explicit-transactions) -- [Savepoints](#savepoints) -- [Transaction Kinds](#transaction-kinds) - - -### Transactions and Safety - -**A transaction** is a fundamental tool of SQLite that guarantees [data consistency](https://www.sqlite.org/transactional.html) as well as [proper isolation](https://sqlite.org/isolation.html) between application threads and database connections. - -GRDB generally opens transactions for you, as a way to enforce its [concurrency guarantees](#concurrency), and provide maximal security for both your application data and application logic: - -```swift -// BEGIN TRANSACTION -// INSERT INTO credit ... -// INSERT INTO debit ... -// COMMIT -try dbQueue.write { db in - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) -} - -// BEGIN TRANSACTION -// INSERT INTO credit ... -// INSERT INTO debit ... -// COMMIT -try dbPool.write { db in - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) -} -``` - -Yet you may need to exactly control when transactions take place: - - -### Explicit Transactions - -`DatabaseQueue.inDatabase()` and `DatabasePool.writeWithoutTransaction()` execute your database statements outside of any transaction: - -```swift -// INSERT INTO credit ... -// INSERT INTO debit ... -try dbQueue.inDatabase { db in - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) -} - -// INSERT INTO credit ... -// INSERT INTO debit ... -try dbPool.writeWithoutTransaction { db in - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) -} -``` - -**Writing outside of any transaction is dangerous,** for two reasons: - -- In our credit/debit example, you may successfully insert a credit, but fail inserting the debit, and end up with unbalanced accounts (oops). - - ```swift - // UNSAFE DATABASE INTEGRITY - try dbQueue.inDatabase { db in // or dbPool.writeWithoutTransaction - try Credit(destinationAccout, amount).insert(db) // may succeed - try Debit(sourceAccount, amount).insert(db) // may fail - } - ``` - - Transactions avoid this kind of bug. - -- [Database pool](#database-pools) concurrent reads can see an inconsistent state of the database: - - ```swift - // UNSAFE CONCURRENCY - try dbPool.writeWithoutTransaction { db in - try Credit(destinationAccout, amount).insert(db) - // <- Concurrent dbPool.read sees a partial db update here - try Debit(sourceAccount, amount).insert(db) - } - ``` - - Transactions avoid this kind of bug, too. - -To open explicit transactions, use one of the `Database.inTransaction`, `DatabaseQueue.inTransaction`, or `DatabasePool.writeInTransaction` methods: - -```swift -// BEGIN TRANSACTION -// INSERT INTO credit ... -// INSERT INTO debit ... -// COMMIT -try dbQueue.inDatabase { db in // or dbPool.writeWithoutTransaction - try db.inTransaction { - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) - return .commit - } -} - -// BEGIN TRANSACTION -// INSERT INTO credit ... -// INSERT INTO debit ... -// COMMIT -try dbQueue.inTransaction { db in // or dbPool.writeInTransaction - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) - return .commit -} -``` - -If an error is thrown from the transaction block, the transaction is rollbacked and the error is rethrown by the `inTransaction` method. If you return `.rollback` instead of `.commit`, the transaction is also rollbacked, but no error is thrown. - -You can also perform manual transaction management: - -```swift -try dbQueue.inDatabase { db in // or dbPool.writeWithoutTransaction - try db.beginTransaction() - ... - try db.commit() - - try db.execute(sql: "BEGIN TRANSACTION") - ... - try db.execute(sql: "ROLLBACK") -} -``` - -Transactions can't be left opened unless you set the [allowsUnsafeTransactions](http://groue.github.io/GRDB.swift/docs/4.0/Structs/Configuration.html) configuration flag: - -```swift -// fatal error: A transaction has been left opened at the end of a database access -try dbQueue.inDatabase { db in - try db.execute(sql: "BEGIN TRANSACTION") - // <- no commit or rollback -} -``` - -You can ask if a transaction is currently opened: - -```swift -func myCriticalMethod(_ db: Database) throws { - precondition(db.isInsideTransaction, "This method requires a transaction") - try ... -} -``` - -Yet, you have a better option than checking for transactions: critical database sections should use savepoints, described below: - -```swift -func myCriticalMethod(_ db: Database) throws { - try db.inSavepoint { - // Here the database is guaranteed to be inside a transaction. - try ... - } -} -``` - - -### Savepoints - -**Statements grouped in a savepoint can be rollbacked without invalidating a whole transaction:** - -```swift -try dbQueue.write { db in - // Makes sure both inserts succeed, or none: - try db.inSavepoint { - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) - return .commit - } - - // Other savepoints, etc... -} -``` - -If an error is thrown from the savepoint block, the savepoint is rollbacked and the error is rethrown by the `inSavepoint` method. If you return `.rollback` instead of `.commit`, the savepoint is also rollbacked, but no error is thrown. - -**Unlike transactions, savepoints can be nested.** They implicitly open a transaction if no one was opened when the savepoint begins. As such, they behave just like nested transactions. Yet the database changes are only written to disk when the outermost transaction is committed: - -```swift -try dbQueue.inDatabase { db in - try db.inSavepoint { - ... - try db.inSavepoint { - ... - return .commit - } - ... - return .commit // writes changes to disk - } -} -``` - -SQLite savepoints are more than nested transactions, though. For advanced uses, use [SQLite savepoint documentation](https://www.sqlite.org/lang_savepoint.html). - - -### Transaction Kinds - -SQLite supports [three kinds of transactions](https://www.sqlite.org/lang_transaction.html): deferred (the default), immediate, and exclusive. - -The transaction kind can be changed in the database configuration, or for each transaction: - -```swift -// 1) Default configuration: -let dbQueue = try DatabaseQueue(path: "...") - -// BEGIN DEFERED TRANSACTION ... -dbQueue.write { db in ... } - -// BEGIN EXCLUSIVE TRANSACTION ... -dbQueue.inTransaction(.exclusive) { db in ... } - -// 2) Customized default transaction kind: -var config = Configuration() -config.defaultTransactionKind = .immediate -let dbQueue = try DatabaseQueue(path: "...", configuration: config) - -// BEGIN IMMEDIATE TRANSACTION ... -dbQueue.write { db in ... } - -// BEGIN EXCLUSIVE TRANSACTION ... -dbQueue.inTransaction(.exclusive) { db in ... } -``` - - -## Prepared Statements - -**Prepared Statements** let you prepare an SQL query and execute it later, several times if you need, with different arguments. - -There are two kinds of prepared statements: **select statements**, and **update statements**: - -```swift -try dbQueue.write { db in - let updateSQL = "INSERT INTO player (name, score) VALUES (:name, :score)" - let updateStatement = try db.makeUpdateStatement(sql: updateSQL) - - let selectSQL = "SELECT * FROM player WHERE name = ?" - let selectStatement = try db.makeSelectStatement(sql: selectSQL) -} -``` - -The `?` and colon-prefixed keys like `:name` in the SQL query are the statement arguments. You set them with arrays or dictionaries (arguments are actually of type [StatementArguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html), which happens to adopt the ExpressibleByArrayLiteral and ExpressibleByDictionaryLiteral protocols). - -```swift -updateStatement.arguments = ["name": "Arthur", "score": 1000] -selectStatement.arguments = ["Arthur"] -``` - -After arguments are set, you can execute the prepared statement: - -```swift -try updateStatement.execute() -``` - -Select statements can be used wherever a raw SQL query string would fit (see [fetch queries](#fetch-queries)): - -```swift -let rows = try Row.fetchCursor(selectStatement) // A Cursor of Row -let players = try Player.fetchAll(selectStatement) // [Player] -let player = try Player.fetchOne(selectStatement) // Player? -``` - -You can set the arguments at the moment of the statement execution: - -```swift -try updateStatement.execute(arguments: ["name": "Arthur", "score": 1000]) -let player = try Player.fetchOne(selectStatement, arguments: ["Arthur"]) -``` - -> :point_up: **Note**: it is a programmer error to reuse a prepared statement that has failed: GRDB may crash if you do so. - -See [row queries](#row-queries), [value queries](#value-queries), and [Records](#records) for more information. - - -### Prepared Statements Cache - -When the same query will be used several times in the lifetime of your application, you may feel a natural desire to cache prepared statements. - -**Don't cache statements yourself.** - -> :point_up: **Note**: This is because you don't have the necessary tools. Statements are tied to specific SQLite connections and dispatch queues which you don't manage yourself, especially when you use [database pools](#database-pools). A change in the database schema [may, or may not](https://www.sqlite.org/compile.html#max_schema_retry) invalidate a statement. On systems earlier than OSX 10.10 that don't have the [sqlite3_close_v2 function](https://www.sqlite.org/c3ref/close.html), SQLite connections won't close properly if statements have been kept alive. - -Instead, use the `cachedUpdateStatement` and `cachedSelectStatement` methods. GRDB does all the hard caching and [memory management](#memory-management) stuff for you: - -```swift -let updateStatement = try db.cachedUpdateStatement(sql: sql) -let selectStatement = try db.cachedSelectStatement(sql: sql) -``` - -Should a cached prepared statement throw an error, don't reuse it (it is a programmer error). Instead, reload it from the cache. - - -## Custom SQL Functions and Aggregates - -**SQLite lets you define SQL functions and aggregates.** - -A custom SQL function or aggregate extends SQLite: - -```sql -SELECT reverse(name) FROM player; -- custom function -SELECT maxLength(name) FROM player; -- custom aggregate -``` - -- [Custom SQL Functions](#custom-sql-functions) -- [Custom Aggregates](#custom-aggregates) - - -### Custom SQL Functions - -```swift -let reverse = DatabaseFunction("reverse", argumentCount: 1, pure: true) { (values: [DatabaseValue]) in - // Extract string value, if any... - guard let string = String.fromDatabaseValue(values[0]) else { - return nil - } - // ... and return reversed string: - return String(string.reversed()) -} -dbQueue.add(function: reverse) // Or dbPool.add(function: ...) - -try dbQueue.read { db in - // "oof" - try String.fetchOne(db, sql: "SELECT reverse('foo')")! -} -``` - -The *function* argument takes an array of [DatabaseValue](#databasevalue), and returns any valid [value](#values) (Bool, Int, String, Date, Swift enums, etc.) The number of database values is guaranteed to be *argumentCount*. - -SQLite has the opportunity to perform additional optimizations when functions are "pure", which means that their result only depends on their arguments. So make sure to set the *pure* argument to true when possible. - - -**Functions can take a variable number of arguments:** - -When you don't provide any explicit *argumentCount*, the function can take any number of arguments: - -```swift -let averageOf = DatabaseFunction("averageOf", pure: true) { (values: [DatabaseValue]) in - let doubles = values.compactMap { Double.fromDatabaseValue($0) } - return doubles.reduce(0, +) / Double(doubles.count) -} -dbQueue.add(function: averageOf) - -try dbQueue.read { db in - // 2.0 - try Double.fetchOne(db, sql: "SELECT averageOf(1, 2, 3)")! -} -``` - - -**Functions can throw:** - -```swift -let sqrt = DatabaseFunction("sqrt", argumentCount: 1, pure: true) { (values: [DatabaseValue]) in - guard let double = Double.fromDatabaseValue(values[0]) else { - return nil - } - guard double >= 0 else { - throw DatabaseError(message: "invalid negative number") - } - return sqrt(double) -} -dbQueue.add(function: sqrt) - -// SQLite error 1 with statement `SELECT sqrt(-1)`: invalid negative number -try dbQueue.read { db in - try Double.fetchOne(db, sql: "SELECT sqrt(-1)")! -} -``` - - -**Use custom functions in the [query interface](#the-query-interface):** - -```swift -// SELECT reverseString("name") FROM player -Player.select(reverseString.apply(nameColumn)) -``` - - -**GRDB ships with built-in SQL functions that perform unicode-aware string transformations.** See [Unicode](#unicode). - - -### Custom Aggregates - -Before registering a custom aggregate, you need to define a type that adopts the `DatabaseAggregate` protocol: - -```swift -protocol DatabaseAggregate { - // Initializes an aggregate - init() - - // Called at each step of the aggregation - mutating func step(_ dbValues: [DatabaseValue]) throws - - // Returns the final result - func finalize() throws -> DatabaseValueConvertible? -} -``` - -For example: - -```swift -struct MaxLength : DatabaseAggregate { - var maxLength: Int = 0 - - mutating func step(_ dbValues: [DatabaseValue]) { - // At each step, extract string value, if any... - guard let string = String.fromDatabaseValue(dbValues[0]) else { - return - } - // ... and update the result - let length = string.count - if length > maxLength { - maxLength = length - } - } - - func finalize() -> DatabaseValueConvertible? { - return maxLength - } -} - -let maxLength = DatabaseFunction( - "maxLength", - argumentCount: 1, - pure: true, - aggregate: MaxLength.self) - -dbQueue.add(function: maxLength) // Or dbPool.add(function: ...) - -try dbQueue.read { db in - // Some Int - try Int.fetchOne(db, sql: "SELECT maxLength(name) FROM player")! -} -``` - -The `step` method of the aggregate takes an array of [DatabaseValue](#databasevalue). This array contains as many values as the *argumentCount* parameter (or any number of values, when *argumentCount* is omitted). - -The `finalize` method of the aggregate returns the final aggregated [value](#values) (Bool, Int, String, Date, Swift enums, etc.). - -SQLite has the opportunity to perform additional optimizations when aggregates are "pure", which means that their result only depends on their inputs. So make sure to set the *pure* argument to true when possible. - - -**Use custom aggregates in the [query interface](#the-query-interface):** - -```swift -// SELECT maxLength("name") FROM player -let request = Player.select(maxLength.apply(nameColumn)) -try Int.fetchOne(db, request) // Int? -``` - - -## Database Schema Introspection - -GRDB comes with a set of schema introspection methods: - -```swift -try dbQueue.read { db in - // Bool, true if the table exists - try db.tableExists("player") - - // [ColumnInfo], the columns in the table - try db.columns(in: "player") - - // PrimaryKeyInfo - try db.primaryKey("player") - - // [ForeignKeyInfo], the foreign keys defined on the table - try db.foreignKeys(on: "player") - - // [IndexInfo], the indexes defined on the table - try db.indexes(on: "player") - - // Bool, true if column(s) is a unique key (primary key or unique index) - try db.table("player", hasUniqueKey: ["email"]) -} - -// Bool, true if argument is the name of an internal SQLite table -Database.isSQLiteInternalTable(...) - -// Bool, true if argument is the name of an internal GRDB table -Database.isGRDBInternalTable(...) -``` - - -## Row Adapters - -**Row adapters let you present database rows in the way expected by the row consumers.** - -They basically help two incompatible row interfaces to work together. For example, a row consumer expects a column named "consumed", but the produced row has a column named "produced". - -In this case, the `ColumnMapping` row adapter comes in handy: - -```swift -// Fetch a 'produced' column, and consume a 'consumed' column: -let adapter = ColumnMapping(["consumed": "produced"]) -let row = try Row.fetchOne(db, sql: "SELECT 'Hello' AS produced", adapter: adapter)! -row["consumed"] // "Hello" -row["produced"] // nil -``` - -Row adapters are values that adopt the [RowAdapter](http://groue.github.io/GRDB.swift/docs/4.0/Protocols/RowAdapter.html) protocol. You can implement your own custom adapters ([**:fire: EXPERIMENTAL**](#what-are-experimental-features)), or use one of the four built-in adapters, described below. - -To see how row adapters can be used, see [Joined Queries Support](#joined-queries-support). - - -### ColumnMapping - -ColumnMapping renames columns. Build one with a dictionary whose keys are adapted column names, and values the column names in the raw row: - -```swift -// [newName:"Hello"] -let adapter = ColumnMapping(["newName": "oldName"]) -let row = try Row.fetchOne(db, sql: "SELECT 'Hello' AS oldName", adapter: adapter)! -``` - -### SuffixRowAdapter - -`SuffixRowAdapter` hides the first columns in a row: - -```swift -// [b:1 c:2] -let adapter = SuffixRowAdapter(fromIndex: 1) -let row = try Row.fetchOne(db, sql: "SELECT 0 AS a, 1 AS b, 2 AS c", adapter: adapter)! -``` - -### RangeRowAdapter - -`RangeRowAdapter` only exposes a range of columns. - -```swift -// [b:1] -let adapter = RangeRowAdapter(1..<2) -let row = try Row.fetchOne(db, sql: "SELECT 0 AS a, 1 AS b, 2 AS c", adapter: adapter)! -``` - -### EmptyRowAdapter - -`EmptyRowAdapter` hides all columns. - -```swift -let adapter = EmptyRowAdapter() -let row = try Row.fetchOne(db, sql: "SELECT 0 AS a, 1 AS b, 2 AS c", adapter: adapter)! -row.isEmpty // true -``` - -This limit adapter may turn out useful in some narrow use cases. You'll be happy to find it when you need it. - - -### ScopeAdapter - -`ScopeAdapter` defines *row scopes*: - -```swift -let adapter = ScopeAdapter([ - "left": RangeRowAdapter(0..<2), - "right": RangeRowAdapter(2..<4)]) -let row = try Row.fetchOne(db, sql: "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d", adapter: adapter)! -``` - -ScopeAdapter does not change the columns and values of the fetched row. Instead, it defines *scopes*, which you access through the `Row.scopes` property: - -```swift -row // [a:0 b:1 c:2 d:3] -row.scopes["left"] // [a:0 b:1] -row.scopes["right"] // [c:2 d:3] -row.scopes["missing"] // nil -``` - -Scopes can be nested: - -```swift -let adapter = ScopeAdapter([ - "left": ScopeAdapter([ - "left": RangeRowAdapter(0..<1), - "right": RangeRowAdapter(1..<2)]), - "right": ScopeAdapter([ - "left": RangeRowAdapter(2..<3), - "right": RangeRowAdapter(3..<4)]) - ]) -let row = try Row.fetchOne(db, sql: "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d", adapter: adapter)! - -let leftRow = row.scopes["left"]! -leftRow.scopes["left"] // [a:0] -leftRow.scopes["right"] // [b:1] - -let rightRow = row.scopes["right"]! -rightRow.scopes["left"] // [c:2] -rightRow.scopes["right"] // [d:3] -``` - -Any adapter can be extended with scopes: - -```swift -let baseAdapter = RangeRowAdapter(0..<2) -let adapter = ScopeAdapter(base: baseAdapter, scopes: [ - "remainder": SuffixRowAdapter(fromIndex: 2)]) -let row = try Row.fetchOne(db, sql: "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d", adapter: adapter)! - -row // [a:0 b:1] -row.scopes["remainder"] // [c:2 d:3] -``` - - -## Raw SQLite Pointers - -**If not all SQLite APIs are exposed in GRDB, you can still use the [SQLite C Interface](https://www.sqlite.org/c3ref/intro.html) and call [SQLite C functions](https://www.sqlite.org/c3ref/funclist.html).** - -Those functions are embedded right into the [GRDBCustom](Documentation/CustomSQLiteBuilds.md) module. Otherwise, you'll need to import `SQLite3`, `SQLCipher`, or `CSQLite`, depending on the GRDB flavor you are using: - -```swift -// Swift Package Manager -import CSQLite - -// SQLCipher -import SQLCipher - -// System SQLite -import SQLite3 - -let sqliteVersion = String(cString: sqlite3_libversion()) -``` - -Raw pointers to database connections and statements are available through the `Database.sqliteConnection` and `Statement.sqliteStatement` properties: - -```swift -try dbQueue.read { db in - // The raw pointer to a database connection: - let sqliteConnection = db.sqliteConnection - - // The raw pointer to a statement: - let statement = try db.makeSelectStatement(sql: "SELECT ...") - let sqliteStatement = statement.sqliteStatement -} -``` - -> :point_up: **Notes** -> -> - Those pointers are owned by GRDB: don't close connections or finalize statements created by GRDB. -> - GRDB opens SQLite connections in the "[multi-thread mode](https://www.sqlite.org/threadsafe.html)", which (oddly) means that **they are not thread-safe**. Make sure you touch raw databases and statements inside their dedicated dispatch queues. -> - Use the raw SQLite C Interface at your own risk. GRDB won't prevent you from shooting yourself in the foot. - -Before jumping in the low-level wagon, here is the list of all SQLite APIs used by GRDB: - -- `sqlite3_aggregate_context`, `sqlite3_create_function_v2`, `sqlite3_result_blob`, `sqlite3_result_double`, `sqlite3_result_error`, `sqlite3_result_error_code`, `sqlite3_result_int64`, `sqlite3_result_null`, `sqlite3_result_text`, `sqlite3_user_data`, `sqlite3_value_blob`, `sqlite3_value_bytes`, `sqlite3_value_double`, `sqlite3_value_int64`, `sqlite3_value_text`, `sqlite3_value_type`: see [Custom SQL Functions and Aggregates](#custom-sql-functions-and-aggregates) -- `sqlite3_backup_finish`, `sqlite3_backup_init`, `sqlite3_backup_step`: see [Backup](#backup) -- `sqlite3_bind_blob`, `sqlite3_bind_double`, `sqlite3_bind_int64`, `sqlite3_bind_null`, `sqlite3_bind_parameter_count`, `sqlite3_bind_parameter_name`, `sqlite3_bind_text`, `sqlite3_clear_bindings`, `sqlite3_column_blob`, `sqlite3_column_bytes`, `sqlite3_column_count`, `sqlite3_column_double`, `sqlite3_column_int64`, `sqlite3_column_name`, `sqlite3_column_text`, `sqlite3_column_type`, `sqlite3_exec`, `sqlite3_finalize`, `sqlite3_prepare_v2`, `sqlite3_reset`, `sqlite3_step`: see [Executing Updates](#executing-updates), [Fetch Queries](#fetch-queries), [Prepared Statements](#prepared-statements), [Values](#values) -- `sqlite3_busy_handler`, `sqlite3_busy_timeout`: see [Configuration.busyMode](http://groue.github.io/GRDB.swift/docs/4.0/Structs/Configuration.html) -- `sqlite3_changes`, `sqlite3_total_changes`: see [Database.changesCount and Database.totalChangesCount](http://groue.github.io/GRDB.swift/docs/4.0/Classes/Database.html) -- `sqlite3_close`, `sqlite3_close_v2`, `sqlite3_next_stmt`, `sqlite3_open_v2`: see [Database Connections](#database-connections) -- `sqlite3_commit_hook`, `sqlite3_rollback_hook`, `sqlite3_update_hook`: see [TransactionObserver Protocol](#transactionobserver-protocol), [DatabaseRegionObservation], [ValueObservation], [FetchedRecordsController] -- `sqlite3_config`: see [Error Log](#error-log) -- `sqlite3_create_collation_v2`: see [String Comparison](#string-comparison) -- `sqlite3_db_release_memory`: see [Memory Management](#memory-management) -- `sqlite3_errcode`, `sqlite3_errmsg`, `sqlite3_errstr`, `sqlite3_extended_result_codes`: see [Error Handling](#error-handling) -- `sqlite3_key`, `sqlite3_rekey`: see [Encryption](#encryption) -- `sqlite3_last_insert_rowid`: see [Executing Updates](#executing-updates) -- `sqlite3_preupdate_count`, `sqlite3_preupdate_depth`, `sqlite3_preupdate_hook`, `sqlite3_preupdate_new`, `sqlite3_preupdate_old`: see [Support for SQLite Pre-Update Hooks](#support-for-sqlite-pre-update-hooks) -- `sqlite3_set_authorizer`: **reserved by GRDB** -- `sqlite3_sql`: see [Statement.sql](http://groue.github.io/GRDB.swift/docs/4.0/Classes/Statement.html) -- `sqlite3_trace`: see [Configuration.trace](http://groue.github.io/GRDB.swift/docs/4.0/Structs/Configuration.html) -- `sqlite3_wal_checkpoint_v2`: see [DatabasePool.checkpoint](http://groue.github.io/GRDB.swift/docs/4.0/Classes/DatabasePool.html) - - -Records -======= - -**On top of the [SQLite API](#sqlite-api), GRDB provides protocols and a class** that help manipulating database rows as regular objects named "records": - -```swift -try dbQueue.write { db in - if var place = try Place.fetchOne(db, key: 1) { - place.isFavorite = true - try place.update(db) - } -} -``` - -Of course, you need to open a [database connection](#database-connections), and [create database tables](#database-schema) first. - -To define your custom records, you subclass the ready-made `Record` class, or you extend your structs and classes with protocols that come with focused sets of features: fetching methods, persistence methods, record comparison... - -Extending structs with record protocols is more "swifty". Subclassing the Record class is more "classic". You can choose either way. See some [examples of record definitions](#examples-of-record-definitions), and the [list of record methods](#list-of-record-methods) for an overview. - -> :point_up: **Note**: if you are familiar with Core Data's NSManagedObject or Realm's Object, you may experience a cultural shock: GRDB records are not uniqued, do not auto-update, and do not lazy-load. This is both a purpose, and a consequence of protocol-oriented programming. You should read [How to build an iOS application with SQLite and GRDB.swift](https://medium.com/@gwendal.roue/how-to-build-an-ios-application-with-sqlite-and-grdb-swift-d023a06c29b3) for a general introduction. -> -> :bulb: **Tip**: after you have read this chapter, check the [Good Practices for Designing Record Types](Documentation/GoodPracticesForDesigningRecordTypes.md) Guide. -> -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample app that uses records. - -**Overview** - -- [Inserting Records](#inserting-records) -- [Fetching Records](#fetching-records) -- [Updating Records](#updating-records) -- [Deleting Records](#deleting-records) -- [Counting Records](#counting-records) - -**Protocols and the Record Class** - -- [Record Protocols Overview](#record-protocols-overview) -- [FetchableRecord Protocol](#fetchablerecord-protocol) -- [TableRecord Protocol](#tablerecord-protocol) -- [PersistableRecord Protocol](#persistablerecord-protocol) - - [Persistence Methods](#persistence-methods) - - [Customizing the Persistence Methods] -- [Codable Records] -- [Record Class](#record-class) -- [Record Comparison] -- [Record Customization Options] - -**Records in a Glance** - -- [Examples of Record Definitions](#examples-of-record-definitions) -- [List of Record Methods](#list-of-record-methods) - - -### Inserting Records - -To insert a record in the database, call the `insert` method: - -```swift -let player = Player(name: "Arthur", email: "arthur@example.com") -try player.insert(db) -``` - -:point_right: `insert` is available for subclasses of the [Record](#record-class) class, and types that adopt the [PersistableRecord] protocol. - - -### Fetching Records - -To fetch records from the database, call a [fetching method](#fetching-methods): - -```swift -let arthur = try Player.fetchOne(db, // Player? - sql: "SELECT * FROM players WHERE name = ?", - arguments: ["Arthur"]) - -let bestPlayers = try Player // [Player] - .order(Column("score").desc) - .limit(10) - .fetchAll(db) - -let spain = try Country.fetchOne(db, key: "ES") // Country? -``` - -:point_right: Fetching from raw SQL is available for subclasses of the [Record](#record-class) class, and types that adopt the [FetchableRecord] protocol. - -:point_right: Fetching without SQL, using the [query interface](#the-query-interface), is available for subclasses of the [Record](#record-class) class, and types that adopt both [FetchableRecord] and [TableRecord] protocol. - - -### Updating Records - -To update a record in the database, call the `update` method: - -```swift -if let player = try Player.fetchOne(db, key: 1) - player.score = 1000 - try player.update(db) -} -``` - -It is possible to [avoid useless updates](#record-comparison): - -```swift -if var player = try Player.fetchOne(db, key: 1) { - // does not hit the database if score has not changed - try player.updateChanges(db) { - $0.score = 1000 - } -} -``` - -For batch updates, execute an [SQL query](#executing-updates): - -```swift -try db.execute(sql: "UPDATE player SET synchronized = 1") -``` - -:point_right: update methods are available for subclasses of the [Record](#record-class) class, and types that adopt the [PersistableRecord] protocol. - - -### Deleting Records - -To delete a record in the database, call the `delete` method: - -```swift -if let player = try Player.fetchOne(db, key: 1) { - try player.delete(db) -} -``` - -You can also delete by primary key, or any unique index: - -```swift -try Player.deleteOne(db, key: 1) -try Player.deleteOne(db, key: ["email": "arthur@example.com"]) -try Country.deleteAll(db, keys: ["FR", "US"]) -``` - -For batch deletes, execute an [SQL query](#executing-updates), or see the [query interface](#the-query-interface): - -```swift -try Player - .filter(Column("email") == nil) - .deleteAll(db) -``` - -:point_right: delete methods are available for subclasses of the [Record](#record-class) class, and types that adopt the [PersistableRecord] protocol. - - -### Counting Records - -To count records, call the `fetchCount` method: - -```swift -let playerCount: Int = try Player.fetchCount(db) - -let playerWithEmailCount: Int = try Player - .filter(Column("email") == nil) - .fetchCount(db) -``` - -:point_right: `fetchCount` is available for subclasses of the [Record](#record-class) class, and types that adopt the [TableRecord] protocol. - - -Details follow: - -- [Record Protocols Overview](#record-protocols-overview) -- [FetchableRecord Protocol](#fetchablerecord-protocol) -- [TableRecord Protocol](#tablerecord-protocol) -- [PersistableRecord Protocol](#persistablerecord-protocol) -- [Codable Records] -- [Record Class](#record-class) -- [Record Comparison] -- [Record Customization Options] -- [Examples of Record Definitions](#examples-of-record-definitions) -- [List of Record Methods](#list-of-record-methods) - - -## Record Protocols Overview - -**GRDB ships with three record protocols**. Your own types will adopt one or several of them, according to the abilities you want to extend your types with. - -- [FetchableRecord] is able to **decode database rows**. - - It is always possible to decode rows without this protocol: - - ```swift - struct Place { ... } - try dbQueue.read { db in - let rows = try Row.fetchAll(db, sql: "SELECT * FROM place") - let places: [Place] = rows.map { row in - return Place( - id: row["id"], - title: row["title"], - coordinate: CLLocationCoordinate2D( - latitude: row["latitude"], - longitude: row["longitude"])) - ) - } - } - ``` - - But FetchableRecord lets you write code that is easier to read, and more efficient as well, both in terms of performance and memory usage: - - ```swift - struct Place: FetchableRecord { ... } - try dbQueue.read { db in - let places = try Place.fetchAll(db, sql: "SELECT * FROM place") - } - ``` - - > :bulb: **Tip**: FetchableRecord can derive its implementation from the standard Decodable protocol. See [Codable Records] for more information. - - FetchableRecord can decode database rows, but it is not able to build SQL requests for you. For that, you also need TableRecord: - -- [TableRecord] is able to **generate SQL queries**: - - ```swift - struct Place: TableRecord { ... } - // SELECT * FROM place ORDER BY title - let request = Place.order(Column("title")) - ``` - - When a type adopts both TableRecord and FetchableRecord, it can load from those requests: - - ```swift - struct Place: TableRecord, FetchableRecord { ... } - try dbQueue.read { db in - let places = try Place.order(Column("title")).fetchAll(db) - let paris = try Place.fetchOne(key: 1) - } - ``` - -- [PersistableRecord] is able to **write**: it can create, update, and delete rows in the database: - - ```swift - struct Place : PersistableRecord { ... } - try dbQueue.write { db in - try Place.delete(db, key: 1) - try Place(...).insert(db) - } - ``` - - A persistable record can also [compare](#record-comparison) itself against other records, and avoid useless database updates. - - > :bulb: **Tip**: PersistableRecord can derive its implementation from the standard Encodable protocol. See [Codable Records] for more information. - - -## FetchableRecord Protocol - -**The FetchableRecord protocol grants fetching methods to any type** that can be built from a database row: - -```swift -protocol FetchableRecord { - /// Row initializer - init(row: Row) -} -``` - -**To use FetchableRecord**, subclass the [Record](#record-class) class, or adopt it explicitly. For example: - -```swift -struct Place { - var id: Int64? - var title: String - var coordinate: CLLocationCoordinate2D -} - -extension Place : FetchableRecord { - init(row: Row) { - id = row["id"] - title = row["title"] - coordinate = CLLocationCoordinate2D( - latitude: row["latitude"], - longitude: row["longitude"]) - } -} -``` - -Rows also accept column enums: - -```swift -extension Place : FetchableRecord { - enum Columns: String, ColumnExpression { - case id, title, latitude, longitude - } - - init(row: Row) { - id = row[Columns.id] - title = row[Columns.title] - coordinate = CLLocationCoordinate2D( - latitude: row[Columns.latitude], - longitude: row[Columns.longitude]) - } -} -``` - -See [column values](#column-values) for more information about the `row[]` subscript. - -When your record type adopts the standard Decodable protocol, you don't have to provide the implementation for `init(row:)`. See [Codable Records] for more information: - -```swift -// That's all -struct Player: Decodable, FetchableRecord { - var id: Int64 - var name: String - var score: Int -} -``` - -FetchableRecord allows adopting types to be fetched from SQL queries: - -```swift -try Place.fetchCursor(db, sql: "SELECT ...", arguments:...) // A Cursor of Place -try Place.fetchAll(db, sql: "SELECT ...", arguments:...) // [Place] -try Place.fetchOne(db, sql: "SELECT ...", arguments:...) // Place? -``` - -See [fetching methods](#fetching-methods) for information about the `fetchCursor`, `fetchAll` and `fetchOne` methods. See [StatementArguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html) for more information about the query arguments. - -> :point_up: **Note**: for performance reasons, the same row argument to `init(row:)` is reused during the iteration of a fetch query. If you want to keep the row for later use, make sure to store a copy: `self.row = row.copy()`. - -> :point_up: **Note**: The `FetchableRecord.init(row:)` initializer fits the needs of most applications. But some application are more demanding than others. When FetchableRecord does not exactly provide the support you need, have a look at the [Beyond FetchableRecord] chapter. - - -## TableRecord Protocol - -**The TableRecord protocol** generates SQL for you. To use TableRecord, subclass the [Record](#record-class) class, or adopt it explicitly: - -```swift -protocol TableRecord { - static var databaseTableName: String { get } - static var databaseSelection: [SQLSelectable] { get } -} -``` - -The `databaseSelection` type property is optional, and documented in the [Columns Selected by a Request] chapter. - -The `databaseTableName` type property is the name of a database table. By default, it is derived from the type name: - -```swift -struct Place: TableRecord { } -print(Place.databaseTableName) // prints "place" -``` - -For example: - -- Place: `place` -- Country: `country` -- PostalAddress: `postalAddress` -- HTTPRequest: `httpRequest` -- TOEFL: `toefl` - -You can still provide a custom table name: - -```swift -struct Place: TableRecord { - static let databaseTableName = "location" -} -print(Place.databaseTableName) // prints "location" -``` - -Subclasses of the [Record](#record-class) class must always override their superclass's `databaseTableName` property: - -```swift -class Place: Record { - override class var databaseTableName: String { - return "place" - } -} -print(Place.databaseTableName) // prints "place" -``` - -When a type adopts both TableRecord and [FetchableRecord](#fetchablerecord-protocol), it can be fetched using the [query interface](#the-query-interface): - -```swift -// SELECT * FROM place WHERE name = 'Paris' -let paris = try Place.filter(nameColumn == "Paris").fetchOne(db) -``` - -TableRecord can also fetch records by primary key: - -```swift -try Player.fetchOne(db, key: 1) // Player? -try Player.fetchAll(db, keys: [1, 2, 3]) // [Player] - -try Country.fetchOne(db, key: "FR") // Country? -try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] -``` - -When the table has no explicit primary key, GRDB uses the [hidden "rowid" column](#the-implicit-rowid-primary-key): - -```swift -// SELECT * FROM document WHERE rowid = 1 -try Document.fetchOne(db, key: 1) // Document? -``` - -For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: - -```swift -// SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' -try Citizenship.fetchOne(db, key: ["citizenId": 1, "countryCode": "FR"]) // Citizenship? -``` - - -## PersistableRecord Protocol - -**GRDB record types can create, update, and delete rows in the database.** - -Those abilities are granted by three protocols: - -```swift -// Defines how a record encodes itself into the database -protocol EncodableRecord { - /// Defines the values persisted in the database - func encode(to container: inout PersistenceContainer) -} - -// Adds persistence methods -protocol MutablePersistableRecord: TableRecord, EncodableRecord { - /// Optional method that lets your adopting type store its rowID upon - /// successful insertion. Don't call it directly: it is called for you. - mutating func didInsert(with rowID: Int64, for column: String?) -} - -// Adds immutability -protocol PersistableRecord: MutablePersistableRecord { - /// Non-mutating version of the optional didInsert(with:for:) - func didInsert(with rowID: Int64, for column: String?) -} -``` - -Yes, three protocols instead of one. Here is how you pick one or the other: - -- **If your type is a class**, choose `PersistableRecord`. On top of that, implement `didInsert(with:for:)` if the database table has an auto-incremented primary key. - -- **If your type is a struct, and the database table has an auto-incremented primary key**, choose `MutablePersistableRecord`, and implement `didInsert(with:for:)`. - -- **Otherwise**, choose `PersistableRecord`, and ignore `didInsert(with:for:)`. - -The `encode(to:)` method defines which [values](#values) (Bool, Int, String, Date, Swift enums, etc.) are assigned to database columns. - -The optional `didInsert` method lets the adopting type store its rowID after successful insertion, and is only useful for tables that have an auto-incremented primary key. It is called from a protected dispatch queue, and serialized with all database updates. - -**To use the persistable protocols**, subclass the [Record](#record-class) class, or adopt one of them explicitly. For example: - -```swift -extension Place : MutablePersistableRecord { - /// The values persisted in the database - func encode(to container: inout PersistenceContainer) { - container["id"] = id - container["title"] = title - container["latitude"] = coordinate.latitude - container["longitude"] = coordinate.longitude - } - - // Update id upon successful insertion: - mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } -} - -var paris = Place( - id: nil, - title: "Paris", - coordinate: CLLocationCoordinate2D(latitude: 48.8534100, longitude: 2.3488000)) - -try paris.insert(db) -paris.id // some value -``` - -Persistence containers also accept column enums: - -```swift -extension Place : MutablePersistableRecord { - enum Columns: String, ColumnExpression { - case id, title, latitude, longitude - } - - func encode(to container: inout PersistenceContainer) { - container[Columns.id] = id - container[Columns.title] = title - container[Columns.latitude] = coordinate.latitude - container[Columns.longitude] = coordinate.longitude - } -} -``` - -When your record type adopts the standard Encodable protocol, you don't have to provide the implementation for `encode(to:)`. See [Codable Records] for more information: - -```swift -// That's all -struct Player: Encodable, MutablePersistableRecord { - var id: Int64? - var name: String - var score: Int - - mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } -} -``` - - -### Persistence Methods - -[Record](#record-class) subclasses and types that adopt [PersistableRecord] are given default implementations for methods that insert, update, and delete: - -```swift -// Instance methods -try place.save(db) // INSERT or UPDATE -try place.insert(db) // INSERT -try place.update(db) // UPDATE -try place.update(db, columns: ...) // UPDATE -try place.updateChanges(db, from: ...) // Maybe UPDATE -try place.updateChanges(db) { ... } // Maybe UPDATE -try place.updateChanges(db) // Maybe UPDATE (Record class only) -try place.delete(db) // DELETE -try place.exists(db) - -// Type methods -try Place.deleteAll(db) // DELETE -try Place.deleteAll(db, keys:...) // DELETE -try Place.deleteOne(db, key:...) // DELETE -``` - -- `insert`, `update`, `save` and `delete` can throw a [DatabaseError](#error-handling). - -- `update` and `updateChanges` can also throw a [PersistenceError](#persistenceerror), should the update fail because there is no matching row in the database. - - When saving an object that may or may not already exist in the database, prefer the `save` method: - -- `save` makes sure your values are stored in the database. - - It performs an UPDATE if the record has a non-null primary key, and then, if no row was modified, an INSERT. It directly perfoms an INSERT if the record has no primary key, or a null primary key. - - Despite the fact that it may execute two SQL statements, `save` behaves as an atomic operation: GRDB won't allow any concurrent thread to sneak in (see [concurrency](#concurrency)). - -- `delete` returns whether a database row was deleted or not. - -**All primary keys are supported**, including composite primary keys that span several columns, and the [implicit rowid primary key](#the-implicit-rowid-primary-key). - - -### Customizing the Persistence Methods - -Your custom type may want to perform extra work when the persistence methods are invoked. - -For example, it may want to have its UUID automatically set before inserting. Or it may want to validate its values before saving. - -When you subclass [Record](#record-class), you simply have to override the customized method, and call `super`: - -```swift -class Player : Record { - var uuid: UUID? - - override func insert(_ db: Database) throws { - if uuid == nil { - uuid = UUID() - } - try super.insert(db) - } -} -``` - -If you use the raw [PersistableRecord] protocol, use one of the *special methods* `performInsert`, `performUpdate`, `performSave`, `performDelete`, or `performExists`: - -```swift -struct Link : PersistableRecord { - var url: URL - - func insert(_ db: Database) throws { - try validate() - try performInsert(db) - } - - func update(_ db: Database, columns: Set) throws { - try validate() - try performUpdate(db, columns: columns) - } - - func validate() throws { - if url.host == nil { - throw ValidationError("url must be absolute.") - } - } -} -``` - -> :point_up: **Note**: the special methods `performInsert`, `performUpdate`, etc. are reserved for your custom implementations. Do not use them elsewhere. Do not provide another implementation for those methods. -> -> :point_up: **Note**: it is recommended that you do not implement your own version of the `save` method. Its default implementation forwards the job to `update` or `insert`: these are the methods that may need customization, not `save`. - - -## Codable Records - -Record types that adopt an archival protocol ([Codable, Encodable or Decodable](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)) get free database support just by declaring conformance to the desired [record protocols](#record-protocols-overview): - -```swift -// Declare a record... -struct Player: Codable, FetchableRecord, PersistableRecord { - var name: String - var score: Int -} - -// ...and there you go: -try dbQueue.write { db in - try Player(name: "Arthur", score: 100).insert(db) - let players = try Player.fetchAll(db) -} -``` - -Codable records encode and decode their properties according to their own implementation of the Encodable and Decodable protocols. Yet databases have specific requirements: - -- Properties are always coded according to their preferred database representation, when they have one (all [values](#values) that adopt the [DatabaseValueConvertible](#custom-value-types) protocol). -- You can customize the encoding and decoding of dates and uuids. -- Complex properties (arrays, dictionaries, nested structs, etc.) are stored as JSON. - -For more information about Codable records, see: - -- [JSON Columns] -- [Date and UUID Coding Strategies] -- [The userInfo Dictionary] -- [Tip: Derive Columns from Coding Keys](#tip-derive-columns-from-coding-keys) - -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample app that uses Codable records. - - -### JSON Columns - -When a [Codable record](#codable-records) contains a property that is not a simple [value](#values) (Bool, Int, String, Date, Swift enums, etc.), that value is encoded and decoded as a **JSON string**. For example: - -```swift -enum AchievementColor: String, Codable { - case bronze, silver, gold -} - -struct Achievement: Codable { - var name: String - var color: AchievementColor -} - -struct Player: Codable, FetchableRecord, PersistableRecord { - var name: String - var score: Int - var achievements: [Achievement] // stored in a JSON column -} - -try! dbQueue.write { db in - // INSERT INTO player (name, score, achievements) - // VALUES ( - // 'Arthur', - // 100, - // '[{"color":"gold","name":"Use Codable Records"}]') - let achievement = Achievement(name: "Use Codable Records", color: .gold) - let player = Player(name: "Arthur", score: 100, achievements: [achievement]) - try player.insert(db) -} -``` - -GRDB uses the standard [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder) and [JSONEncoder](https://developer.apple.com/documentation/foundation/jsonencoder) from Foundation. By default, Data values are handled with the `.base64` strategy, Date with the `.millisecondsSince1970` strategy, and non conforming floats with the `.throw` strategy. - -You can customize the JSON format by implementing those methods: - -```swift -protocol FetchableRecord { - static func databaseJSONDecoder(for column: String) -> JSONDecoder -} - -protocol MutablePersistableRecord { - static func databaseJSONEncoder(for column: String) -> JSONEncoder -} -``` - -> :bulb: **Tip**: Make sure you set the JSONEncoder `sortedKeys` option, available from iOS 11.0+, macOS 10.13+, and watchOS 4.0+. This option makes sure that the JSON output is stable. This stability is required for [Record Comparison] to work as expected, and database observation tools such as [ValueObservation] to accurately recognize changed records. - - -### Date and UUID Coding Strategies - -By default, [Codable Records] encode and decode their Date and UUID properties as described in the general [Date and DateComponents](#date-and-datecomponents) and [UUID](#uuid) chapters. - -To sum up: dates encode themselves in the "YYYY-MM-DD HH:MM:SS.SSS" format, in the UTC time zone, and decode a variety of date formats and timestamps. UUIDs encode themselves as 16-bytes data blobs, and decode both 16-bytes data blobs and strings such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F". - -Those behaviors can be overridden: - -```swift -protocol FetchableRecord { - static var databaseDateDecodingStrategy: DatabaseDateDecodingStrategy { get } -} - -protocol MutablePersistableRecord { - static var databaseDateEncodingStrategy: DatabaseDateEncodingStrategy { get } - static var databaseUUIDEncodingStrategy: DatabaseUUIDEncodingStrategy { get } -} -``` - -See [DatabaseDateDecodingStrategy](https://groue.github.io/GRDB.swift/docs/4.0/Enums/DatabaseDateDecodingStrategy.html), [DatabaseDateEncodingStrategy](https://groue.github.io/GRDB.swift/docs/4.0/Enums/DatabaseDateEncodingStrategy.html), and [DatabaseUUIDEncodingStrategy](https://groue.github.io/GRDB.swift/docs/4.0/Enums/DatabaseUUIDEncodingStrategy.html) to learn about all available strategies. - -> :point_up: **Note**: there is no customization of uuid decoding, because UUID can already decode all its encoded variants (16-bytes blobs, and uuid strings). - - -### The userInfo Dictionary - -Your [Codable Records] can be stored in the database, but they may also have other purposes. In this case, you may need to customize their implementations of `Decodable.init(from:)` and `Encodable.encode(to:)`, depending on the context. - -The standard way to provide such context is the `userInfo` dictionary. Implement those properties: - -```swift -protocol FetchableRecord { - static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] { get } -} - -protocol MutablePersistableRecord { - static var databaseEncodingUserInfo: [CodingUserInfoKey: Any] { get } -} -``` - -For example, here is a Player type that customizes its decoding: - -```swift -// A key that holds a decoder's name -let decoderName = CodingUserInfoKey(rawValue: "decoderName")! - -struct Player: FetchableRecord, Decodable { - init(from decoder: Decoder) throws { - // Print the decoder name - let decoderName = decoder.userInfo[decoderName] as? String - print("Decoded from \(decoderName ?? "unknown decoder")") - ... - } -} -``` - -You can have a specific decoding from JSON... - -```swift -// prints "Decoded from JSON" -let decoder = JSONDecoder() -decoder.userInfo = [decoderName: "JSON"] -let player = try decoder.decode(Player.self, from: jsonData) -``` - -... and another one from database rows: - -```swift -extension Player: FetchableRecord { - static let databaseDecodingUserInfo: [CodingUserInfoKey: Any] = [decoderName: "database row"] -} - -// prints "Decoded from database row" -let player = try Player.fetchOne(db, ...) -``` - -> :point_up: **Note**: make sure the `databaseDecodingUserInfo` and `databaseEncodingUserInfo` properties are explicitly declared as `[CodingUserInfoKey: Any]`. If they are not, the Swift compiler may silently miss the protocol requirement, resulting in sticky empty userInfo. - - -### Tip: Derive Columns from Coding Keys - -Codable types are granted with a [CodingKeys](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types) enum. You can use them to safely define database columns: - -```swift -struct Player: Codable { - var id: Int64 - var name: String - var score: Int -} - -extension Player: FetchableRecord, PersistableRecord { - enum Columns { - static let id = Column(CodingKeys.id) - static let name = Column(CodingKeys.name) - static let score = Column(CodingKeys.score) - } -} -``` - -Those columns let you build requests with the [query interface](#the-query-interface): - -```swift -extension Player { - static func filter(name: String) -> QueryInterfaceRequest { - return filter(Columns.name == name) - } - - static var maximumScore: QueryInterfaceRequest { - return select(max(Columns.score), as: Int.self) - } -} -``` - -Those requests can both fetch... - -```swift -// Fetch values -try dbQueue.read { db in - // SELECT * FROM player WHERE name = 'Arthur' - let arthur = try Player.filter(name: "Arthur").fetchOne(db) // Player? - - // SELECT MAX(score) FROM player - let maxScore = try Player.maximumScore.fetchOne(db) // Int? -} -``` - -... and feed database observation tools such as [ValueObservation]: - -```swift -// Observe changes -try ValueObservation - .trackingOne(Player.maximumScore) - .start(in: dbQueue) { (maxScore: Int?) in - print("The maximum score has changed") - } -``` - - -## Record Class - -**Record** is a class that is designed to be subclassed. It inherits its features from the [FetchableRecord, TableRecord, and PersistableRecord](#record-protocols-overview) protocols. On top of that, Record instances can compare against previous versions of themselves in order to [avoid useless updates](#record-comparison). - -Record subclasses define their custom database relationship by overriding database methods. For example: - -```swift -class Place: Record { - var id: Int64? - var title: String - var isFavorite: Bool - var coordinate: CLLocationCoordinate2D - - init(id: Int64?, title: String, isFavorite: Bool, coordinate: CLLocationCoordinate2D) { - self.id = id - self.title = title - self.isFavorite = isFavorite - self.coordinate = coordinate - super.init() - } - - /// The table name - override class var databaseTableName: String { - return "place" - } - - /// The table columns - enum Columns: String, ColumnExpression { - case id, title, favorite, latitude, longitude - } - - /// Creates a record from a database row - required init(row: Row) { - id = row[Columns.id] - title = row[Columns.title] - isFavorite = row[Columns.favorite] - coordinate = CLLocationCoordinate2D( - latitude: row[Columns.latitude], - longitude: row[Columns.longitude]) - super.init(row: row) - } - - /// The values persisted in the database - override func encode(to container: inout PersistenceContainer) { - container[Columns.id] = id - container[Columns.title] = title - container[Columns.favorite] = isFavorite - container[Columns.latitude] = coordinate.latitude - container[Columns.longitude] = coordinate.longitude - } - - /// Update record ID after a successful insertion - override func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } -} -``` - - -## Record Comparison - -**Records that adopt the [EncodableRecord] protocol can compare against other records, or against previous versions of themselves.** - -This helps avoiding costly UPDATE statements when a record has not been edited. - -- [The `updateChanges` Methods](#the-updatechanges-methods) -- [The `databaseEquals` Method](#the-databaseequals-method) -- [The `databaseChanges` and `hasDatabaseChanges` Methods](#the-databasechanges-and-hasdatabasechanges-methods) - - -### The `updateChanges` Methods - -The `updateChanges` methods perform a database update of the changed columns only (and does nothing if record has no change). - -- `updateChanges(_:from:)` - - This method lets you compare two records: - - ```swift - if let oldPlayer = try Player.fetchOne(db, key: 42) { - var newPlayer = oldPlayer - newPlayer.score = 100 - if try newPlayer.updateChanges(db, from: oldPlayer) { - print("player was modified, and updated in the database") - } else { - print("player was not modified, and database was not hit") - } - } - ``` - -- `updateChanges(_:with:)` - - This method lets you update a record in place: - - ```swift - if var player = try Player.fetchOne(db, key: 42) { - let modified = try player.updateChanges(db) { - $0.score = 100 - } - if modified { - print("player was modified, and updated in the database") - } else { - print("player was not modified, and database was not hit") - } - } - ``` - -- `updateChanges(_:)` (Record class only) - - Instances of the [Record](#record-class) class are able to compare against themselves, and know if they have changes that have not been saved since the last fetch or saving: - - ```swift - // Record class only - if let player = try Player.fetchOne(db, key: 42) { - player.score = 100 - if try player.updateChanges(db) { - print("player was modified, and updated in the database") - } else { - print("player was not modified, and database was not hit") - } - } - ``` - - -### The `databaseEquals` Method - -This method returns whether two records have the same database representation: - -```swift -let oldPlayer: Player = ... -var newPlayer: Player = ... -if newPlayer.databaseEquals(oldPlayer) == false { - try newPlayer.save(db) -} -``` - -> :point_up: **Note**: The comparison is performed on the database representation of records. As long as your record type adopts the EncodableRecord protocol, you don't need to care about Equatable. - - -### The `databaseChanges` and `hasDatabaseChanges` Methods - -`databaseChanges(from:)` returns a dictionary of differences between two records: - -```swift -let oldPlayer = Player(id: 1, name: "Arthur", score: 100) -let newPlayer = Player(id: 1, name: "Arthur", score: 1000) -for (column, oldValue) in newPlayer.databaseChanges(from: oldPlayer) { - print("\(column) was \(oldValue)") -} -// prints "score was 100" -``` - -The [Record](#record-class) class is able to compare against itself: - -```swift -// Record class only -let player = Player(id: 1, name: "Arthur", score: 100) -try player.insert(db) -player.score = 1000 -for (column, oldValue) in player.databaseChanges { - print("\(column) was \(oldValue)") -} -// prints "score was 100" -``` - -[Record](#record-class) instances also have a `hasDatabaseChanges` property: - -```swift -// Record class only -player.score = 1000 -if player.hasDatabaseChanges { - try player.save(db) -} -``` - -`Record.hasDatabaseChanges` is false after a Record instance has been fetched or saved into the database. Subsequent modifications may set it, or not: `hasDatabaseChanges` is based on value comparison. **Setting a property to the same value does not set the changed flag**: - -```swift -let player = Player(name: "Barbara", score: 750) -player.hasDatabaseChanges // true - -try player.insert(db) -player.hasDatabaseChanges // false - -player.name = "Barbara" -player.hasDatabaseChanges // false - -player.score = 1000 -player.hasDatabaseChanges // true -player.databaseChanges // ["score": 750] -``` - -For an efficient algorithm which synchronizes the content of a database table with a JSON payload, check [JSONSynchronization.playground](Playgrounds/JSONSynchronization.playground/Contents.swift). - - -## Record Customization Options - -GRDB records come with many default behaviors, that are designed to fit most situations. Many of those defaults can be customized for your specific needs: - -- [Customizing the Persistence Methods]: define what happens when you call a persistance method such as `player.insert(db)` -- [Conflict Resolution]: Run `INSERT OR REPLACE` queries, and generally define what happens when a persistence method violates a unique index. -- [The Implicit RowID Primary Key]: all about the special `rowid` column. -- [Columns Selected by a Request]: define which columns are selected by requests such as `Player.fetchAll(db)`. -- [Beyond FetchableRecord]: the FetchableRecord protocol is not the end of the story. - -[Codable Records] have a few extra options: - -- [JSON Columns]: control the format of JSON columns. -- [Date and UUID Coding Strategies]: control the format of Date and UUID properties in your Codable records. -- [The userInfo Dictionary]: adapt your Codable implementation for the database. - - -### Conflict Resolution - -**Insertions and updates can create conflicts**: for example, a query may attempt to insert a duplicate row that violates a unique index. - -Those conflicts normally end with an error. Yet SQLite let you alter the default behavior, and handle conflicts with specific policies. For example, the `INSERT OR REPLACE` statement handles conflicts with the "replace" policy which replaces the conflicting row instead of throwing an error. - -The [five different policies](https://www.sqlite.org/lang_conflict.html) are: abort (the default), replace, rollback, fail, and ignore. - -**SQLite let you specify conflict policies at two different places:** - -- In the definition of the database table: - - ```swift - // CREATE TABLE player ( - // id INTEGER PRIMARY KEY AUTOINCREMENT, - // email TEXT UNIQUE ON CONFLICT REPLACE - // ) - try db.create(table: "player") { t in - t.autoIncrementedPrimaryKey("id") - t.column("email", .text).unique(onConflict: .replace) // <-- - } - - // Despite the unique index on email, both inserts succeed. - // The second insert replaces the first row: - try db.execute(sql: "INSERT INTO player (email) VALUES (?)", arguments: ["arthur@example.com"]) - try db.execute(sql: "INSERT INTO player (email) VALUES (?)", arguments: ["arthur@example.com"]) - ``` - -- In each modification query: - - ```swift - // CREATE TABLE player ( - // id INTEGER PRIMARY KEY AUTOINCREMENT, - // email TEXT UNIQUE - // ) - try db.create(table: "player") { t in - t.autoIncrementedPrimaryKey("id") - t.column("email", .text).unique() - } - - // Again, despite the unique index on email, both inserts succeed. - try db.execute(sql: "INSERT OR REPLACE INTO player (email) VALUES (?)", arguments: ["arthur@example.com"]) - try db.execute(sql: "INSERT OR REPLACE INTO player (email) VALUES (?)", arguments: ["arthur@example.com"]) - ``` - -When you want to handle conflicts at the query level, specify a custom `persistenceConflictPolicy` in your type that adopts the PersistableRecord protocol. It will alter the INSERT and UPDATE queries run by the `insert`, `update` and `save` [persistence methods](#persistence-methods): - -```swift -protocol MutablePersistableRecord { - /// The policy that handles SQLite conflicts when records are - /// inserted or updated. - /// - /// This property is optional: its default value uses the ABORT - /// policy for both insertions and updates, so that GRDB generate - /// regular INSERT and UPDATE queries. - static var persistenceConflictPolicy: PersistenceConflictPolicy { get } -} - -struct Player : MutablePersistableRecord { - static let persistenceConflictPolicy = PersistenceConflictPolicy( - insert: .replace, - update: .replace) -} - -// INSERT OR REPLACE INTO player (...) VALUES (...) -try player.insert(db) -``` - -> :point_up: **Note**: the `ignore` policy does not play well at all with the `didInsert` method which notifies the rowID of inserted records. Choose your poison: -> -> - if you specify the `ignore` policy in the database table definition, don't implement the `didInsert` method: it will be called with some random id in case of failed insert. -> - if you specify the `ignore` policy at the query level, the `didInsert` method is never called. -> -> :point_up: **Note**: The `replace` policy may have to delete rows so that inserts and updates can succeed. Those deletions are not reported to [transaction observers](#transactionobserver-protocol) (this might change in a future release of SQLite). - - -### The Implicit RowID Primary Key - -**All SQLite tables have a primary key.** Even when the primary key is not explicit: - -```swift -// No explicit primary key -try db.create(table: "event") { t in - t.column("message", .text) - t.column("date", .datetime) -} - -// No way to define an explicit primary key -try db.create(virtualTable: "book", using: FTS4()) { t in - t.column("title") - t.column("author") - t.column("body") -} -``` - -The implicit primary key is stored in the hidden column `rowid`. Hidden means that `SELECT *` does not select it, and yet it can be selected and queried: `SELECT *, rowid ... WHERE rowid = 1`. - -Some GRDB methods will automatically use this hidden column when a table has no explicit primary key: - -```swift -// SELECT * FROM event WHERE rowid = 1 -let event = try Event.fetchOne(db, key: 1) - -// DELETE FROM book WHERE rowid = 1 -try Book.deleteOne(db, key: 1) -``` - - -#### Exposing the RowID Column - -**By default, a record type that wraps a table without any explicit primary key doesn't know about the hidden rowid column.** - -Without primary key, records don't have any identity, and the [persistence method](#persistence-methods) can behave in undesired fashion: `update()` throws errors, `save()` always performs insertions and may break constraints, `exists()` is always false. - -When SQLite won't let you provide an explicit primary key (as in [full-text](#full-text-search) tables, for example), you may want to make your record type fully aware of the hidden rowid column: - -1. Have the `databaseSelection` static property (from the [TableRecord] protocol) return the hidden rowid column: - - ```swift - struct Event : TableRecord { - static let databaseSelection: [SQLSelectable] = [AllColumns(), Column.rowID] - } - - // When you subclass Record, you need an override: - class Book : Record { - override class var databaseSelection: [SQLSelectable] { - return [AllColums(), Column.rowID] - } - } - ``` - - GRDB will then select the `rowid` column by default: - - ```swift - // SELECT *, rowid FROM event - let events = try Event.fetchAll(db) - ``` - -2. Have `init(row:)` from the [FetchableRecord] protocol consume the "rowid" column: - - ```swift - struct Event : FetchableRecord { - var id: Int64? - - init(row: Row) { - id = row[Column.rowID] - } - } - ``` - - Your fetched records will then know their ids: - - ```swift - let event = try Event.fetchOne(db)! - event.id // some value - ``` - -3. Encode the rowid in `encode(to:)`, and keep it in the `didInsert(with:for:)` method (both from the [PersistableRecord and MutablePersistableRecord](#persistablerecord-protocol) protocols): - - ```swift - struct Event : MutablePersistableRecord { - var id: Int64? - - func encode(to container: inout PersistenceContainer) { - container[Column.rowID] = id - container["message"] = message - container["date"] = date - } - - mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - } - ``` - - You will then be able to track your record ids, update them, or check for their existence: - - ```swift - let event = Event(message: "foo", date: Date()) - - // Insertion sets the record id: - try event.insert(db) - event.id // some value - - // Record can be updated: - event.message = "bar" - try event.update(db) - - // Record knows if it exists: - event.exists(db) // true - ``` - - -### Beyond FetchableRecord - -**Some GRDB users eventually discover that the [FetchableRecord] protocol does not fit all situations.** Use cases that are not well handled by FetchableRecord include: - -- Your application needs polymorphic row decoding: it decodes some type or another, depending on the values contained in a database row. - -- Your application needs to decode rows with a context: each decoded value should be initialized with some extra value that does not come from the database. - -- Your application needs a record type that supports untrusted databases, and may fail at decoding database rows (throw an error when a row contains invalid values). - -Since those use cases are not well handled by FetchableRecord, don't try to implement them on top of this protocol: you'll just fight the framework. - -Instead, please have a look at the [CustomizedDecodingOfDatabaseRows](Playgrounds/CustomizedDecodingOfDatabaseRows.playground/Contents.swift) playground. You'll run some sample code, and learn how to escape FetchableRecord when you need. And remember that leaving FetchableRecord will not deprive you of [query interface requests](#requests) and generally all SQL generation features of the [TableRecord] and [PersistableRecord] protocols. - - -## Examples of Record Definitions - -We will show below how to declare a record type for the following database table: - -```swift -try dbQueue.write { db in - try db.create(table: "place") { t in - t.autoIncrementedPrimaryKey("id") - t.column("title", .text).notNull() - t.column("favorite", .boolean).notNull().defaults(to: false) - t.column("longitude", .double).notNull() - t.column("latitude", .double).notNull() - } -} -``` - -Each one of the three examples below is correct. You will pick one or the other depending on your personal preferences and the requirements of your application: - -
- Define a Codable struct, and adopt the record protocols you need - -This is the shortest way to define a record type. - -See the [Record Protocols Overview](#record-protocols-overview), and [Codable Records] for more information. - -```swift -struct Place: Codable { - var id: Int64? - var title: String - var favorite: Bool - var latitude: CLLocationDegrees - var longitude: CLLocationDegrees - - var coordinate: CLLocationCoordinate2D { - get { - return CLLocationCoordinate2D( - latitude: latitude, - longitude: longitude) - } - set { - latitude = newValue.latitude - longitude = newValue.longitude - } - } -} - -// SQL generation -extension Place: TableRecord { } - -// Fetching methods -extension Place: FetchableRecord { } - -// Persistence methods -extension Place: MutablePersistableRecord { - /// Update record ID after a successful insertion - mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } -} -``` - -
- -
- Define a plain struct, and adopt the record protocols you need - -See the [Record Protocols Overview](#record-protocols-overview) for more information. - -```swift -struct Place { - var id: Int64? - var title: String - var isFavorite: Bool - var coordinate: CLLocationCoordinate2D -} - -// SQL generation -extension Place: TableRecord { - /// The table columns - enum Columns: String, ColumnExpression { - case id, title, favorite, latitude, longitude - } -} - -// Fetching methods -extension Place: FetchableRecord { - /// Creates a record from a database row - init(row: Row) { - id = row[Columns.id] - title = row[Columns.title] - isFavorite = row[Columns.favorite] - coordinate = CLLocationCoordinate2D( - latitude: row[Columns.latitude], - longitude: row[Columns.longitude]) - } -} - -// Persistence methods -extension Place: MutablePersistableRecord { - /// The values persisted in the database - func encode(to container: inout PersistenceContainer) { - container[Columns.id] = id - container[Columns.title] = title - container[Columns.favorite] = isFavorite - container[Columns.latitude] = coordinate.latitude - container[Columns.longitude] = coordinate.longitude - } - - /// Update record ID after a successful insertion - mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } -} -``` - -
- -
- Subclass the Record class - -See the [Record class](#record-class) for more information. - -```swift -class Place: Record { - var id: Int64? - var title: String - var isFavorite: Bool - var coordinate: CLLocationCoordinate2D - - init(id: Int64?, title: String, isFavorite: Bool, coordinate: CLLocationCoordinate2D) { - self.id = id - self.title = title - self.isFavorite = isFavorite - self.coordinate = coordinate - super.init() - } - - /// The table name - override class var databaseTableName: String { - return "place" - } - - /// The table columns - enum Columns: String, ColumnExpression { - case id, title, favorite, latitude, longitude - } - - /// Creates a record from a database row - required init(row: Row) { - id = row[Columns.id] - title = row[Columns.title] - isFavorite = row[Columns.favorite] - coordinate = CLLocationCoordinate2D( - latitude: row[Columns.latitude], - longitude: row[Columns.longitude]) - super.init(row: row) - } - - /// The values persisted in the database - override func encode(to container: inout PersistenceContainer) { - container[Columns.id] = id - container[Columns.title] = title - container[Columns.favorite] = isFavorite - container[Columns.latitude] = coordinate.latitude - container[Columns.longitude] = coordinate.longitude - } - - /// Update record ID after a successful insertion - override func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } -} -``` - -
- - -## List of Record Methods - -This is the list of record methods, along with their required protocols. The [Record](#record-class) class adopts all these protocols, and adds a few extra methods. - -| Method | Protocols | Notes | -| ------ | --------- | :---: | -| **Core Methods** | | | -| `init(row:)` | [FetchableRecord] | | -| `Type.databaseTableName` | [TableRecord] | | -| `Type.databaseSelection` | [TableRecord] | [*](#columns-selected-by-a-request) | -| `Type.persistenceConflictPolicy` | [PersistableRecord] | [*](#conflict-resolution) | -| `record.encode(to:)` | [PersistableRecord] | | -| `record.didInsert(with:for:)` | [PersistableRecord] | | -| **Insert and Update Records** | | | -| `record.insert(db)` | [PersistableRecord] | | -| `record.save(db)` | [PersistableRecord] | | -| `record.update(db)` | [PersistableRecord] | | -| `record.update(db, columns:...)` | [PersistableRecord] | | -| `record.updateChanges(db, from:...)` | [PersistableRecord] | [*](#record-comparison) | -| `record.updateChanges(db) { ... }` | [PersistableRecord] | [*](#record-comparison) | -| `record.updateChanges(db)` | [Record](#record-class) | [*](#record-comparison) | -| **Delete Records** | | | -| `record.delete(db)` | [PersistableRecord] | | -| `Type.deleteOne(db, key:...)` | [PersistableRecord] | ¹ | -| `Type.deleteAll(db)` | [PersistableRecord] | | -| `Type.deleteAll(db, keys:...)` | [PersistableRecord] | ¹ | -| `Type.filter(...).deleteAll(db)` | [PersistableRecord] | ² | -| **Check Record Existence** | | | -| `record.exists(db)` | [PersistableRecord] | | -| **Convert Record to Dictionary** | | | -| `record.databaseDictionary` | [PersistableRecord] | | -| **Count Records** | | | -| `Type.fetchCount(db)` | [TableRecord] | | -| `Type.filter(...).fetchCount(db)` | [TableRecord] | ² | -| **Fetch Record [Cursors](#cursors)** | | | -| `Type.fetchCursor(db)` | [FetchableRecord] & [TableRecord] | | -| `Type.fetchCursor(db, keys:...)` | [FetchableRecord] & [TableRecord] | ¹ | -| `Type.fetchCursor(db, sql: sql)` | [FetchableRecord] | ³ | -| `Type.fetchCursor(statement)` | [FetchableRecord] | | -| `Type.filter(...).fetchCursor(db)` | [FetchableRecord] & [TableRecord] | ² | -| **Fetch Record Arrays** | | | -| `Type.fetchAll(db)` | [FetchableRecord] & [TableRecord] | | -| `Type.fetchAll(db, keys:...)` | [FetchableRecord] & [TableRecord] | ¹ | -| `Type.fetchAll(db, sql: sql)` | [FetchableRecord] | ³ | -| `Type.fetchAll(statement)` | [FetchableRecord] | | -| `Type.filter(...).fetchAll(db)` | [FetchableRecord] & [TableRecord] | ² | -| **Fetch Individual Records** | | | -| `Type.fetchOne(db)` | [FetchableRecord] & [TableRecord] | | -| `Type.fetchOne(db, key:...)` | [FetchableRecord] & [TableRecord] | ¹ | -| `Type.fetchOne(db, sql: sql)` | [FetchableRecord] | ³ | -| `Type.fetchOne(statement)` | [FetchableRecord] | | -| `Type.filter(...).fetchOne(db)` | [FetchableRecord] & [TableRecord] | ² | -| **[Record Comparison]** | | | -| `record.databaseEquals(...)` | [PersistableRecord] | | -| `record.databaseChanges(from:...)` | [PersistableRecord] | | -| `record.updateChanges(db, from:...)` | [PersistableRecord] | | -| `record.updateChanges(db) { ... }` | [PersistableRecord] | | -| `record.hasDatabaseChanges` | [Record](#record-class) | | -| `record.databaseChanges` | [Record](#record-class) | | -| `record.updateChanges(db)` | [Record](#record-class) | | - -¹ All unique keys are supported: primary keys (single-column, composite, [implicit RowID](#the-implicit-rowid-primary-key)) and unique indexes: - -```swift -try Player.fetchOne(db, key: 1) // Player? -try Player.fetchOne(db, key: ["email": "arthur@example.com"]) // Player? -try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] -``` - -² See [Fetch Requests](#requests): - -```swift -let request = Player.filter(emailColumn != nil).order(nameColumn) -let players = try request.fetchAll(db) // [Player] -let count = try request.fetchCount(db) // Int -``` - -³ See [SQL queries](#fetch-queries): - -```swift -let player = try Player.fetchOne(db, sql: "SELECT * FROM player WHERE id = ?", arguments: [1]) // Player? -``` - - See [Prepared Statements](#prepared-statements): - -```swift -let statement = try db.makeSelectStatement(sql: "SELECT * FROM player WHERE id = ?") -let player = try Player.fetchOne(statement, arguments: [1]) // Player? -``` - - -The Query Interface -=================== - -**The query interface lets you write pure Swift instead of SQL:** - -```swift -try dbQueue.write { db in - // Update database schema - try db.create(table: "wine") { t in ... } - - // Fetch records - let wines = try Wine.filter(origin == "Burgundy").order(price).fetchAll(db) - - // Count - let count = try Wine.filter(color == Color.red).fetchCount(db) - - // Delete - try Wine.filter(corked == true).deleteAll(db) -} -``` - -You need to open a [database connection](#database-connections) before you can query the database. - -Please bear in mind that the query interface can not generate all possible SQL queries. You may also *prefer* writing SQL, and this is just OK. From little snippets to full queries, your SQL skills are welcome: - -```swift -try dbQueue.write { db in - // Update database schema (with SQL) - try db.execute(sql: "CREATE TABLE wine (...)") - - // Fetch records (with SQL) - let wines = try Wine.fetchAll(db, - sql: "SELECT * FROM wine WHERE origin = ? ORDER BY price", - arguments: ["Burgundy"]) - - // Count (with an SQL snippet) - let count = try Wine - .filter(sql: "color = ?", arguments: [Color.red]) - .fetchCount(db) - - // Delete (with SQL) - try db.execute(sql: "DELETE FROM wine WHERE corked") -} -``` - -So don't miss the [SQL API](#sqlite-api). - -- [Database Schema](#database-schema) -- [Requests](#requests) -- [Expressions](#expressions) - - [SQL Operators](#sql-operators) - - [SQL Functions](#sql-functions) -- [Fetching from Requests] -- [Fetching by Key](#fetching-by-key) -- [Fetching Aggregated Values](#fetching-aggregated-values) -- [Delete Requests](#delete-requests) -- [Custom Requests](#custom-requests) -- [Associations and Joins](Documentation/AssociationsBasics.md) - - -## Database Schema - -Once granted with a [database connection](#database-connections), you can setup your database schema without writing SQL: - -- [Create Tables](#create-tables) -- [Modify Tables](#modify-tables) -- [Drop Tables](#drop-tables) -- [Create Indexes](#create-indexes) - - -### Create Tables - -```swift -// CREATE TABLE place ( -// id INTEGER PRIMARY KEY AUTOINCREMENT, -// title TEXT, -// favorite BOOLEAN NOT NULL DEFAULT 0, -// latitude DOUBLE NOT NULL, -// longitude DOUBLE NOT NULL -// ) -try db.create(table: "place") { t in - t.autoIncrementedPrimaryKey("id") - t.column("title", .text) - t.column("favorite", .boolean).notNull().defaults(to: false) - t.column("longitude", .double).notNull() - t.column("latitude", .double).notNull() -} -``` - -The `create(table:)` method covers nearly all SQLite table creation features. For virtual tables, see [Full-Text Search](#full-text-search), or use raw SQL. - -SQLite itself has many reference documents about table creation: [CREATE TABLE](https://www.sqlite.org/lang_createtable.html), [Datatypes In SQLite Version 3](https://www.sqlite.org/datatype3.html), [SQLite Foreign Key Support](https://www.sqlite.org/foreignkeys.html), [ON CONFLICT](https://www.sqlite.org/lang_conflict.html), [The WITHOUT ROWID Optimization](https://www.sqlite.org/withoutrowid.html). - -**Configure table creation**: - -```swift -// CREATE TABLE example ( ... ) -try db.create(table: "example") { t in ... } - -// CREATE TEMPORARY TABLE example IF NOT EXISTS ( -try db.create(table: "example", temporary: true, ifNotExists: true) { t in -``` - -> :bulb: **Tip**: database table names should be singular, and camel-cased. Make them look like Swift identifiers: `place`, `country`, `postalAddress`, 'httpRequest'. -> -> This will help you using [Associations] when you need them. Database table names that follow another naming convention are totally OK, but you will need to perform extra configuration. - -**Add regular columns** with their name and eventual type (text, integer, double, numeric, boolean, blob, date and datetime) - see [SQLite data types](https://www.sqlite.org/datatype3.html): - -```swift -// CREATE TABLE example ( -// a, -// name TEXT, -// creationDate DATETIME, -try db.create(table: "example") { t in - t.column("a") - t.column("name", .text) - t.column("creationDate", .datetime) -``` - -Define **not null** columns, and set **default** values: - -```swift - // email TEXT NOT NULL, - t.column("email", .text).notNull() - - // name TEXT NOT NULL DEFAULT 'Anonymous', - t.column("name", .text).notNull().defaults(to: "Anonymous") -``` - -Use an individual column as **primary**, **unique**, or **foreign key**. When defining a foreign key, the referenced column is the primary key of the referenced table (unless you specify otherwise): - -```swift - // id INTEGER PRIMARY KEY AUTOINCREMENT, - t.autoIncrementedPrimaryKey("id") - - // uuid TEXT PRIMARY KEY, - t.column("uuid", .text).primaryKey() - - // email TEXT UNIQUE, - t.column("email", .text).unique() - - // countryCode TEXT REFERENCES country(code) ON DELETE CASCADE, - t.column("countryCode", .text).references("country", onDelete: .cascade) -``` - -> :bulb: **Tip**: when you need an integer primary key that automatically generates unique values, it is highly recommended that you use the `autoIncrementedPrimaryKey` method: -> -> ```swift -> try db.create(table: "example") { t in -> t.autoIncrementedPrimaryKey("id") -> ... -> } -> ``` -> -> The reason for this recommendation is that auto-incremented primary keys prevent the reuse of ids. This prevents your app or [database observation tools](#database-changes-observation) to think that a row was updated, when it was actually deleted, then replaced. Depending on your application needs, this may be acceptable. But usually it is not. - -**Create an index** on the column: - -```swift - t.column("score", .integer).indexed() -``` - -For extra index options, see [Create Indexes](#create-indexes) below. - -**Perform integrity checks** on individual columns, and SQLite will only let conforming rows in. In the example below, the `$0` closure variable is a column which lets you build any SQL [expression](#expressions). - -```swift - // name TEXT CHECK (LENGTH(name) > 0) - // score INTEGER CHECK (score > 0) - t.column("name", .text).check { length($0) > 0 } - t.column("score", .integer).check(sql: "score > 0") -``` - -Other **table constraints** can involve several columns: - -```swift - // PRIMARY KEY (a, b), - t.primaryKey(["a", "b"]) - - // UNIQUE (a, b) ON CONFLICT REPLACE, - t.uniqueKey(["a", "b"], onConfict: .replace) - - // FOREIGN KEY (a, b) REFERENCES parents(c, d), - t.foreignKey(["a", "b"], references: "parents") - - // CHECK (a + b < 10), - t.check(Column("a") + Column("b") < 10) - - // CHECK (a + b < 10) - t.check(sql: "a + b < 10") -} -``` - -### Modify Tables - -SQLite lets you rename tables, and add columns to existing tables: - -```swift -// ALTER TABLE referer RENAME TO referrer -try db.rename(table: "referer", to: "referrer") - -// ALTER TABLE player ADD COLUMN url TEXT -try db.alter(table: "player") { t in - t.add(column: "url", .text) -} -``` - -> :point_up: **Note**: SQLite restricts the possible table alterations, and may require you to recreate dependent triggers or views. See the documentation of the [ALTER TABLE](https://www.sqlite.org/lang_altertable.html) for details. See [Advanced Database Schema Changes](#advanced-database-schema-changes) for a way to lift restrictions. - - -### Drop Tables - -Drop tables with the `drop(table:)` method: - -```swift -try db.drop(table: "obsolete") -``` - -### Create Indexes - -Create indexes with the `create(index:)` method: - -```swift -// CREATE UNIQUE INDEX byEmail ON users(email) -try db.create(index: "byEmail", on: "users", columns: ["email"], unique: true) -``` - -Relevant SQLite documentation: - -- [CREATE INDEX](https://www.sqlite.org/lang_createindex.html) -- [Indexes On Expressions](https://www.sqlite.org/expridx.html) -- [Partial Indexes](https://www.sqlite.org/partialindex.html) - - -## Requests - -**The query interface requests** let you fetch values from the database: - -```swift -let request = Player.filter(emailColumn != nil).order(nameColumn) -let players = try request.fetchAll(db) // [Player] -let count = try request.fetchCount(db) // Int -``` - -All requests start from **a type** that adopts the `TableRecord` protocol, such as a `Record` subclass (see [Records](#records)): - -```swift -class Player : Record { ... } -``` - -Declare the table **columns** that you want to use for filtering, or sorting: - -```swift -let idColumn = Column("id") -let nameColumn = Column("name") -``` - -You can also declare column enums, if you prefer: - -```swift -// Columns.id and Columns.name can be used just as -// idColumn and nameColumn declared above. -enum Columns: String, ColumnExpression { - case id - case name -} -``` - -You can now build requests with the following methods: `all`, `none`, `select`, `distinct`, `filter`, `matching`, `group`, `having`, `order`, `reversed`, `limit`, `joining`, `including`. All those methods return another request, which you can further refine by applying another method: `Player.select(...).filter(...).order(...)`. - -- `all()`, `none()`: the requests for all rows, or no row. - - ```swift - // SELECT * FROM player - Player.all() - ``` - - The hidden `rowid` column can be selected as well [when you need it](#the-implicit-rowid-primary-key). - -- `select(...)` and `select(..., as:)` define the selected columns. See [Columns Selected by a Request]. - - ```swift - // SELECT name FROM player - Player.select(nameColumn, as: String.self) - ``` - -- `annotated(with: ...)` extends the selection with [association aggregates](Documentation/AssociationsBasics.md#association-aggregates). - - ```swift - // SELECT team.*, COUNT(DISTINCT player.rowid) AS playerCount - // FROM team - // LEFT JOIN player ON player.teamId = team.id - // GROUP BY team.id - Team.annotated(with: Team.players.count) - ``` - -- `distinct()` performs uniquing. - - ```swift - // SELECT DISTINCT name FROM player - Player.select(nameColumn, as: String.self).distinct() - ``` - -- `filter(expression)` applies conditions. - - ```swift - // SELECT * FROM player WHERE id IN (1, 2, 3) - Player.filter([1,2,3].contains(idColumn)) - - // SELECT * FROM player WHERE (name IS NOT NULL) AND (height > 1.75) - Player.filter(nameColumn != nil && heightColumn > 1.75) - ``` - -- `filter(key:)` and `filter(keys:)` apply conditions on primary keys and unique keys: - - ```swift - // SELECT * FROM player WHERE id = 1 - Player.filter(key: 1) - - // SELECT * FROM country WHERE isoCode IN ('FR', 'US') - Country.filter(keys: ["FR", "US"]) - - // SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' - Citizenship.filter(key: ["citizenId": 1, "countryCode": "FR"]) - - // SELECT * FROM player WHERE email = 'arthur@example.com' - Player.filter(key: ["email": "arthur@example.com"]) - ``` - -- `matching(pattern)` performs [full-text search](#full-text-search). - - ```swift - // SELECT * FROM document WHERE document MATCH 'sqlite database' - let pattern = FTS3Pattern(matchingAllTokensIn: "SQLite database") - Document.matching(pattern) - ``` - - When the pattern is nil, no row will match. - -- `group(expression, ...)` groups rows. - - ```swift - // SELECT name, MAX(score) FROM player GROUP BY name - Player - .select(nameColumn, max(scoreColumn)) - .group(nameColumn) - ``` - -- `having(expression)` applies conditions on grouped rows. - - ```swift - // SELECT team, MAX(score) FROM player GROUP BY team HAVING MIN(score) >= 1000 - Player - .select(teamColumn, max(scoreColumn)) - .group(teamColumn) - .having(min(scoreColumn) >= 1000) - ``` - -- `having(aggregate)` applies conditions on grouped rows, according to an [association aggregate](Documentation/AssociationsBasics.md#association-aggregates). - - ```swift - // SELECT team.* - // FROM team - // LEFT JOIN player ON player.teamId = team.id - // GROUP BY team.id - // HAVING COUNT(DISTINCT player.rowid) >= 5 - Team.having(Team.players.count >= 5) - ``` - -- `order(ordering, ...)` sorts. - - ```swift - // SELECT * FROM player ORDER BY name - Player.order(nameColumn) - - // SELECT * FROM player ORDER BY score DESC, name - Player.order(scoreColumn.desc, nameColumn) - ``` - - Each `order` call clears any previous ordering: - - ```swift - // SELECT * FROM player ORDER BY name - Player.order(scoreColumn).order(nameColumn) - ``` - -- `orderByPrimaryKey()` sorts by primary key: - - ```swift - // SELECT * FROM player ORDER BY id - Player.orderByPrimaryKey() - - // SELECT * FROM country ORDER BY code - Country.orderByPrimaryKey() - - // SELECT * FROM citizenship ORDER BY citizenId, countryCode - Citizenship.orderByPrimaryKey() - ``` - -- `reversed()` reverses the eventual orderings. - - ```swift - // SELECT * FROM player ORDER BY score ASC, name DESC - Player.order(scoreColumn.desc, nameColumn).reversed() - ``` - - If no ordering was already specified, this method has no effect: - - ```swift - // SELECT * FROM player - Player.all().reversed() - ``` - -- `limit(limit, offset: offset)` limits and pages results. - - ```swift - // SELECT * FROM player LIMIT 5 - Player.limit(5) - - // SELECT * FROM player LIMIT 5 OFFSET 10 - Player.limit(5, offset: 10) - ``` - -- `joining(...)` and `including(...)` fetch and join records through [Associations]. - - ```swift - // SELECT player.*, team.* - // FROM player - // JOIN team ON team.id = player.teamId - Player.including(required: Player.team) - ``` - -You can refine requests by chaining those methods: - -```swift -// SELECT * FROM player WHERE (email IS NOT NULL) ORDER BY name -Player.order(nameColumn).filter(emailColumn != nil) -``` - -The `select`, `order`, `group`, and `limit` methods ignore and replace previously applied selection, orderings, grouping, and limits. On the opposite, `filter`, `matching`, and `having` methods extend the query: - -```swift -Player // SELECT * FROM player - .filter(nameColumn != nil) // WHERE (name IS NOT NULL) - .filter(emailColumn != nil) // AND (email IS NOT NULL) - .order(nameColumn) // - ignored - - .reversed() // - ignored - - .order(scoreColumn) // ORDER BY score - .limit(20, offset: 40) // - ignored - - .limit(10) // LIMIT 10 -``` - - -Raw SQL snippets are also accepted, with eventual [arguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html): - -```swift -// SELECT DATE(creationDate), COUNT(*) FROM player WHERE name = 'Arthur' GROUP BY date(creationDate) -Player - .select(sql: "DATE(creationDate), COUNT(*)") - .filter(sql: "name = ?", arguments: ["Arthur"]) - .group(sql: "DATE(creationDate)") -``` - - -### Columns Selected by a Request - -By default, query interface requests select all columns: - -```swift -// SELECT * FROM player -let request = Player.all() -``` - -**The selection can be changed for each individual requests, or for all requests built from a given type.** - -The `select(...)` and `select(..., as:)` methods change the selection of a single request (see [Fetching from Requests] for detailed information): - -```swift -let request = Player.select(max(Column("score"))) -let maxScore: Int? = try Int.fetchOne(db, request) -``` - -The default selection for a record type is controlled by the `databaseSelection` property: - -```swift -struct RestrictedPlayer : TableRecord { - static let databaseTableName = "player" - static let databaseSelection: [SQLSelectable] = [Column("id"), Column("name")] -} - -struct ExtendedPlayer : TableRecord { - static let databaseTableName = "player" - static let databaseSelection: [SQLSelectable] = [AllColumns(), Column.rowID] -} - -// SELECT id, name FROM player -let request = RestrictedPlayer.all() - -// SELECT *, rowid FROM player -let request = ExtendedPlayer.all() -``` - -> :point_up: **Note**: make sure the `databaseSelection` property is explicitly declared as `[SQLSelectable]`. If it is not, the Swift compiler may silently miss the protocol requirement, resulting in sticky `SELECT *` requests. To verify your setup, see the [How do I print a request as SQL?](#how-do-i-print-a-request-as-sql) FAQ. - - -## Expressions - -Feed [requests](#requests) with SQL expressions built from your Swift code: - - -### SQL Operators - -- `=`, `<>`, `<`, `<=`, `>`, `>=`, `IS`, `IS NOT` - - Comparison operators are based on the Swift operators `==`, `!=`, `===`, `!==`, `<`, `<=`, `>`, `>=`: - - ```swift - // SELECT * FROM player WHERE (name = 'Arthur') - Player.filter(nameColumn == "Arthur") - - // SELECT * FROM player WHERE (name IS NULL) - Player.filter(nameColumn == nil) - - // SELECT * FROM player WHERE (score IS 1000) - Player.filter(scoreColumn === 1000) - - // SELECT * FROM rectangle WHERE width < height - Rectangle.filter(widthColumn < heightColumn) - ``` - - > :point_up: **Note**: SQLite string comparison, by default, is case-sensitive and not Unicode-aware. See [string comparison](#string-comparison) if you need more control. - -- `*`, `/`, `+`, `-` - - SQLite arithmetic operators are derived from their Swift equivalent: - - ```swift - // SELECT ((temperature * 1.8) + 32) AS farenheit FROM planet - Planet.select((temperatureColumn * 1.8 + 32).aliased("farenheit")) - ``` - - > :point_up: **Note**: an expression like `nameColumn + "rrr"` will be interpreted by SQLite as a numerical addition (with funny results), not as a string concatenation. - -- `AND`, `OR`, `NOT` - - The SQL logical operators are derived from the Swift `&&`, `||` and `!`: - - ```swift - // SELECT * FROM player WHERE ((NOT verified) OR (score < 1000)) - Player.filter(!verifiedColumn || scoreColumn < 1000) - ``` - - When you want to join a sequence of expressions with `AND` or `OR` operators, use `joined(operator:)`: - - ```swift - // SELECT * FROM player WHERE (verified AND (score >= 1000) AND (name IS NOT NULL)) - let conditions = [ - verifiedColumn, - scoreColumn >= 1000, - nameColumn != nil] - Player.filter(conditions.joined(operator: .and)) - ``` - - When the sequence is empty, `joined(operator: .and)` returns true, and `joined(operator: .or)` returns false: - - ```swift - // SELECT * FROM player WHERE 1 - Player.filter([].joined(operator: .and)) - - // SELECT * FROM player WHERE 0 - Player.filter([].joined(operator: .or)) - ``` - -- `BETWEEN`, `IN`, `NOT IN` - - To check inclusion in a Swift sequence (array, set, range…), call the `contains` method: - - ```swift - // SELECT * FROM player WHERE id IN (1, 2, 3) - Player.filter([1, 2, 3].contains(idColumn)) - - // SELECT * FROM player WHERE id NOT IN (1, 2, 3) - Player.filter(![1, 2, 3].contains(idColumn)) - - // SELECT * FROM player WHERE score BETWEEN 0 AND 1000 - Player.filter((0...1000).contains(scoreColumn)) - - // SELECT * FROM player WHERE (score >= 0) AND (score < 1000) - Player.filter((0..<1000).contains(scoreColumn)) - - // SELECT * FROM player WHERE initial BETWEEN 'A' AND 'N' - Player.filter(("A"..."N").contains(initialColumn)) - - // SELECT * FROM player WHERE (initial >= 'A') AND (initial < 'N') - Player.filter(("A"..<"N").contains(initialColumn)) - ``` - - > :point_up: **Note**: SQLite string comparison, by default, is case-sensitive and not Unicode-aware. See [string comparison](#string-comparison) if you need more control. - -- `LIKE` - - The SQLite LIKE operator is available as the `like` method: - - ```swift - // SELECT * FROM player WHERE (email LIKE '%@example.com') - Player.filter(emailColumn.like("%@example.com")) - ``` - - > :point_up: **Note**: the SQLite LIKE operator is case-insensitive but not Unicode-aware. For example, the expression `'a' LIKE 'A'` is true but `'æ' LIKE 'Æ'` is false. - -- `MATCH` - - The full-text MATCH operator is available through [FTS3Pattern](#fts3pattern) (for FTS3 and FTS4 tables) and [FTS5Pattern](#fts5pattern) (for FTS5): - - FTS3 and FTS4: - - ```swift - let pattern = FTS3Pattern(matchingAllTokensIn: "SQLite database") - - // SELECT * FROM document WHERE document MATCH 'sqlite database' - Document.matching(pattern) - - // SELECT * FROM document WHERE content MATCH 'sqlite database' - Document.filter(contentColumn.match(pattern)) - ``` - - FTS5: - - ```swift - let pattern = FTS5Pattern(matchingAllTokensIn: "SQLite database") - - // SELECT * FROM document WHERE document MATCH 'sqlite database' - Document.matching(pattern) - ``` - - -### SQL Functions - -- `ABS`, `AVG`, `COUNT`, `LENGTH`, `MAX`, `MIN`, `SUM`: - - Those are based on the `abs`, `average`, `count`, `length`, `max`, `min` and `sum` Swift functions: - - ```swift - // SELECT MIN(score), MAX(score) FROM player - Player.select(min(scoreColumn), max(scoreColumn)) - - // SELECT COUNT(name) FROM player - Player.select(count(nameColumn)) - - // SELECT COUNT(DISTINCT name) FROM player - Player.select(count(distinct: nameColumn)) - ``` - -- `IFNULL` - - Use the Swift `??` operator: - - ```swift - // SELECT IFNULL(name, 'Anonymous') FROM player - Player.select(nameColumn ?? "Anonymous") - - // SELECT IFNULL(name, email) FROM player - Player.select(nameColumn ?? emailColumn) - ``` - -- `LOWER`, `UPPER` - - The query interface does not give access to those SQLite functions. Nothing against them, but they are not unicode aware. - - Instead, GRDB extends SQLite with SQL functions that call the Swift built-in string functions `capitalized`, `lowercased`, `uppercased`, `localizedCapitalized`, `localizedLowercased` and `localizedUppercased`: - - ```swift - Player.select(nameColumn.uppercased()) - ``` - - > :point_up: **Note**: When *comparing* strings, you'd rather use a [collation](#string-comparison): - > - > ```swift - > let name: String = ... - > - > // Not recommended - > nameColumn.uppercased() == name.uppercased() - > - > // Better - > nameColumn.collating(.caseInsensitiveCompare) == name - > ``` - -- Custom SQL functions and aggregates - - You can apply your own [custom SQL functions and aggregates](#custom-functions-): - - ```swift - let f = DatabaseFunction("f", ...) - - // SELECT f(name) FROM player - Player.select(f.apply(nameColumn)) - ``` - - -## Fetching from Requests - -Once you have a request, you can fetch the records at the origin of the request: - -```swift -// Some request based on `Player` -let request = Player.filter(...)... // QueryInterfaceRequest - -// Fetch players: -try request.fetchCursor(db) // A Cursor of Player -try request.fetchAll(db) // [Player] -try request.fetchOne(db) // Player? -``` - -For example: - -```swift -let allPlayers = try Player.fetchAll(db) // [Player] -let arthur = try Player.filter(nameColumn == "Arthur").fetchOne(db) // Player? -``` - -See [fetching methods](#fetching-methods) for information about the `fetchCursor`, `fetchAll` and `fetchOne` methods. - -**You sometimes want to fetch other values**. - -The simplest way is to use the request as an argument to a fetching method of the desired type: - -```swift -// Fetch an Int -let request = Player.select(max(scoreColumn)) -let maxScore = try Int.fetchOne(db, request) // Int? - -// Fetch a Row -let request = Player.select(min(scoreColumn), max(scoreColumn)) -let row = try Row.fetchOne(db, request)! // Row -let minScore = row[0] as Int? -let maxScore = row[1] as Int? -``` - -When you also want to use database observation tools such as [ValueObservation], you have to go one step further, and change the type of the request: - -- When you change the selection, prefer the `select(..., as:)` method: - - ```swift - // A request of Int - let request = Player.select(max(scoreColumn), as: Int.self) - - // Simple fetch - let maxScore = try dbQueue.read { db in - try request.fetchOne(db) // Int? - } - - // Observe with ValueObservation - try ValueObservation - .trackingOne(request) - .start(in: dbQueue) { (maxScore: Int?) in - print("The maximum score has changed") - } - ``` - -- Otherwise, use `asRequest(of:)`. Here is an example that uses [Associations]: - - ```swift - struct BookInfo: FetchableRecord, Decodable { - var book: Book - var author: Author - } - - // A request of BookInfo - let request = Book - .including(required: Book.author) - .asRequest(of: BookInfo.self) - - // Simple fetch - let bookInfos = try dbQueue.read { db in - try request.fetchAll(db) // [BookInfo] - } - - // Observe with ValueObservation - try ValueObservation - .trackingAll(request) - .start(in: dbQueue) { (bookInfos: [BookInfo]) in - print("Books have changed") - } - ``` - - -## Fetching By Key - -**Fetching records according to their primary key** is a very common task. It has a shortcut which accepts any single-column primary key: - -```swift -// SELECT * FROM player WHERE id = 1 -try Player.fetchOne(db, key: 1) // Player? - -// SELECT * FROM player WHERE id IN (1, 2, 3) -try Player.fetchAll(db, keys: [1, 2, 3]) // [Player] - -// SELECT * FROM country WHERE isoCode = 'FR' -try Country.fetchOne(db, key: "FR") // Country? - -// SELECT * FROM country WHERE isoCode IN ('FR', 'US') -try Country.fetchAll(db, keys: ["FR", "US"]) // [Country] -``` - -When the table has no explicit primary key, GRDB uses the [hidden "rowid" column](#the-implicit-rowid-primary-key): - -```swift -// SELECT * FROM document WHERE rowid = 1 -try Document.fetchOne(db, key: 1) // Document? -``` - -For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: - -```swift -// SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' -try Citizenship.fetchOne(db, key: ["citizenId": 1, "countryCode": "FR"]) // Citizenship? - -// SELECT * FROM player WHERE email = 'arthur@example.com' -try Player.fetchOne(db, key: ["email": "arthur@example.com"]) // Player? -``` - -**When you want to build a request and plan to fetch from it later**, use the `filter(key:)` and `filter(keys:)` methods: - -```swift -// SELECT * FROM player WHERE id = 1 -let request = Player.filter(key: 1) -let player = try request.fetchOne(db) // Player? - -// SELECT * FROM player WHERE id IN (1, 2, 3) -let request = Player.filter(keys: [1, 2, 3]) -let players = try request.fetchAll(db) // [Player] - -// SELECT * FROM country WHERE isoCode = 'FR' -let request = Country.filter(key: "FR") -let country = try request.fetchOne(db) // Country? - -// SELECT * FROM country WHERE isoCode IN ('FR', 'US') -let request = Country.filter(keys: ["FR", "US"]) -let countries = try request.fetchAll(db) // [Country] - -// SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' -let request = Citizenship.filter(key: ["citizenId": 1, "countryCode": "FR"]) -let citizenship = request.fetchOne(db) // Citizenship? - -// SELECT * FROM player WHERE email = 'arthur@example.com' -let request = Player.filter(key: ["email": "arthur@example.com"]) -let player = try request.fetchOne(db) // Player? -``` - -Those requests can feed [ValueObservation]: - -```swift -try ValueObservation. - .trackingOne(Player.filter(key: 1)) - .start(in: dbQueue) { (player: Player?) in - print("Player 1 has changed") - } -``` - - -## Fetching Aggregated Values - -**Requests can count.** The `fetchCount()` method returns the number of rows that would be returned by a fetch request: - -```swift -// SELECT COUNT(*) FROM player -let count = try Player.fetchCount(db) // Int - -// SELECT COUNT(*) FROM player WHERE email IS NOT NULL -let count = try Player.filter(emailColumn != nil).fetchCount(db) - -// SELECT COUNT(DISTINCT name) FROM player -let count = try Player.select(nameColumn).distinct().fetchCount(db) - -// SELECT COUNT(*) FROM (SELECT DISTINCT name, score FROM player) -let count = try Player.select(nameColumn, scoreColumn).distinct().fetchCount(db) -``` - - -**Other aggregated values** can also be selected and fetched (see [SQL Functions](#sql-functions)): - -```swift -let request = Player.select(max(scoreColumn)) -let maxScore = try Int.fetchOne(db, request) // Int? - -let request = Player.select(min(scoreColumn), max(scoreColumn)) -let row = try Row.fetchOne(db, request)! // Row -let minScore = row[0] as Int? -let maxScore = row[1] as Int? -``` - - -## Delete Requests - -**Requests can delete records**, with the `deleteAll()` method: - -```swift -// DELETE FROM player WHERE email IS NULL -let request = Player.filter(emailColumn == nil) -try request.deleteAll(db) -``` - -> :point_up: **Note** Deletion methods are only available for records that adopts the [PersistableRecord] protocol. - -**Deleting records according to their primary key** is also quite common. It has a shortcut which accepts any single-column primary key: - -```swift -// DELETE FROM player WHERE id = 1 -try Player.deleteOne(db, key: 1) - -// DELETE FROM player WHERE id IN (1, 2, 3) -try Player.deleteAll(db, keys: [1, 2, 3]) - -// DELETE FROM country WHERE isoCode = 'FR' -try Country.deleteOne(db, key: "FR") - -// DELETE FROM country WHERE isoCode IN ('FR', 'US') -try Country.deleteAll(db, keys: ["FR", "US"]) -``` - -When the table has no explicit primary key, GRDB uses the [hidden "rowid" column](#the-implicit-rowid-primary-key): - -```swift -// DELETE FROM document WHERE rowid = 1 -try Document.deleteOne(db, key: 1) -``` - -For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: - -```swift -// DELETE FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' -try Citizenship.deleteOne(db, key: ["citizenId": 1, "countryCode": "FR"]) - -// DELETE FROM player WHERE email = 'arthur@example.com' -Player.deleteOne(db, key: ["email": "arthur@example.com"]) -``` - - -## Custom Requests - -Until now, we have seen [requests](#requests) created from any type that adopts the [TableRecord] protocol: - -```swift -let request = Player.all() // QueryInterfaceRequest -``` - -Those requests of type `QueryInterfaceRequest` can fetch and count: - -```swift -try request.fetchCursor(db) // A Cursor of Player -try request.fetchAll(db) // [Player] -try request.fetchOne(db) // Player? -try request.fetchCount(db) // Int -``` - -**When the query interface can not generate the SQL you need**, you can still fallback to [raw SQL](#fetch-queries): - -```swift -// Custom SQL is always welcome -try Player.fetchAll(db, sql: "SELECT ...") // [Player] -``` - -But you may prefer to bring some elegance back in, and build custom requests: - -```swift -// No custom SQL in sight -try Player.customRequest().fetchAll(db) // [Player] -``` - -Custom requests can also feed [ValueObservation]: - -```swift -try ValueObservation. - .trackingAll(Player.customRequest(...)) - .start(in: dbQueue) { (players: [Player]) in - print("Players have changed") - } -``` - -- [FetchRequest Protocol](#fetchrequest-protocol) -- [Building Custom Requests](#building-custom-requests) -- [Fetching From Custom Requests](#fetching-from-custom-requests) - - -### FetchRequest Protocol - -**FetchRequest** is the protocol for all requests that run from a single select statement, and know how fetched rows should be interpreted: - -```swift -protocol FetchRequest: DatabaseRegionConvertible { - /// The type that tells how fetched rows should be decoded - associatedtype RowDecoder - - /// A tuple that contains a prepared statement, and an eventual row adapter. - func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?) - - /// The number of rows fetched by the request. - func fetchCount(_ db: Database) throws -> Int -} -``` - -When the `RowDecoder` associated type is [Row](#fetching-rows), or a [value](#value-queries), or a type that conforms to [FetchableRecord], the request can fetch: see [Fetching From Custom Requests](#fetching-from-custom-requests) below. - -The `prepare(_:forSingleResult:)` method accepts a database connection, a `singleResult` hint, and returns a prepared statement and an optional row adapter. Conforming types can use the `singleResult` hint as an optimization opportunity, and return a [prepared statement](#prepared-statements) that fetches at most one row, with a `LIMIT` SQL clause, when possible. The optional row adapter helps presenting the fetched rows in the way expected by the row decoders (see [row adapters](#row-adapters)). - -The `fetchCount` method has a default implementation that builds a correct but naive SQL query from the statement returned by `prepare`: `SELECT COUNT(*) FROM (...)`. Adopting types can refine the counting SQL by customizing their `fetchCount` implementation. - -The base `DatabaseRegionConvertible` protocol is involved in [database observation](#database-changes-observation). For more information, see [DatabaseRegion], [DatabaseRegionObservation], and [ValueObservation]. - -The FetchRequest protocol is adopted, for example, by [query interface requests](#requests): - -```swift -// A FetchRequest whose RowDecoder associated type is Player: -let request = Player.all() -``` - - -### Building Custom Requests - -**To build custom requests**, you can use one of the built-in requests, derive requests from other requests, or create your own request type that adopts the [FetchRequest](#fetchrequest-protocol) protocol. - -- [SQLRequest](http://groue.github.io/GRDB.swift/docs/4.0/Structs/SQLRequest.html) is a fetch request built from raw SQL. For example: - - ```swift - extension Player { - static func filter(color: Color) -> SQLRequest { - return SQLRequest( - sql: "SELECT * FROM player WHERE color = ?" - arguments: [color]) - } - } - - // [Player] - try Player.filter(color: .red).fetchAll(db) - ``` - - In Swift 5, you can build SQLRequest with [SQL Interpolation]: - - ```swift - // Swift 5 - extension Player { - static func filter(color: Color) -> SQLRequest { - return "SELECT * FROM player WHERE color = \(color)" - } - } - ``` - -- The `asRequest(of:)` method changes the type fetched by the request. It is useful, for example, when you use [Associations]: - - ```swift - struct BookInfo: FetchableRecord, Decodable { - var book: Book - var author: Author - } - - let request = Book - .including(required: Book.author) - .asRequest(of: BookInfo.self) - - // [BookInfo] - try request.fetchAll(db) - ``` - -- The `adapted(_:)` method eases the consumption of complex rows with [row adapters](#row-adapters). See [Joined Queries Support](#joined-queries-support) for some sample code that uses this method. - -- [AnyFetchRequest](http://groue.github.io/GRDB.swift/docs/4.0/Structs/AnyFetchRequest.html): a [type-erased](http://chris.eidhof.nl/post/type-erasers-in-swift/) request. - - -### Fetching From Custom Requests - -A type adopting [FetchRequest](#fetchrequest-protocol) knows exactly what it has to do when its RowDecoder associated type can decode database rows ([Row](#fetching-rows) itself, [values](#value-queries), or [FetchableRecord]): - -```swift -let rowRequest = ... // Some FetchRequest that fetches Row -try request.fetchCursor(db) // A cursor of rows - -let playerRequest = ... // Some FetchRequest that fetches Player -try request.fetchAll(db) // [Player] - -let intRequest = ... // Some FetchRequest that fetches Int -try request.fetchOne(db) // Int? -``` - -For example: - -```swift -let playerRequest = SQLRequest( - sql: "SELECT * FROM player WHERE color = ?" - arguments: [color]) -try request.fetchAll(db) // [Player] -``` - -See [fetching methods](#fetching-methods) for information about the `fetchCursor`, `fetchAll` and `fetchOne` methods. - -The RowDecoder type associated with the FetchRequest does not have to be Row, DatabaseValueConvertible, or FetchableRecord. See the [Beyond FetchableRecord] chapter for more information. - - -## Migrations - -**Migrations** are a convenient way to alter your database schema over time in a consistent and easy way. - -Migrations run in order, once and only once. When a user upgrades your application, only non-applied migrations are run. - -Inside each migration, you typically [define and update your database tables](#database-schema) according to your evolving application needs: - -```swift -var migrator = DatabaseMigrator() - -// 1st migration -migrator.registerMigration("v1") { db in - try db.create(table: "author") { t in ... } - try db.create(table: "book") { t in ... } - try db.create(index: ...) -} - -// 2nd migration -migrator.registerMigration("v2") { db in - try db.alter(table: "author") { t in ... } -} - -// Migrations for future versions will be inserted here: -// -// // 3rd migration -// migrator.registerMigration("...") { db in -// ... -// } -``` - -**Each migration runs in a separate transaction.** Should one throw an error, its transaction is rollbacked, subsequent migrations do not run, and the error is eventually thrown by `migrator.migrate(dbQueue)`. - -**The memory of applied migrations is stored in the database itself** (in a reserved table). - -You migrate the database up to the latest version with the `migrate(_:)` method: - -```swift -try migrator.migrate(dbQueue) // or migrator.migrate(dbPool) -``` - -To migrate a database up to a specific version, use `migrate(_:upTo:)`: - -```swift -try migrator.migrate(dbQueue, upTo: "v2") -``` - -Migrations can only run forward: - -```swift -try migrator.migrate(dbQueue, upTo: "v2") -try migrator.migrate(dbQueue, upTo: "v1") -// fatal error: database is already migrated beyond migration "v1" -``` - -Check if a migration has been applied: - -```swift -let appliedMigrations = try migrator.appliedMigrations(in: dbQueue) -if appliedMigrations.contains("v2") { - // "v2" migration has been applied -} -``` - -### The `eraseDatabaseOnSchemaChange` Option - -A DatabaseMigrator can automatically wipe out the full database content, and recreate the whole database from scratch, if it detects that a migration has changed its definition: - -```swift -var migrator = DatabaseMigrator() -migrator.eraseDatabaseOnSchemaChange = true -``` - -Beware! This flag can destroy your precious users' data! - -Yet it may be useful in those two situations: - -1. During application development, as you are still designing migrations, and the schema changes often. - - In this case, it is recommended that this flag does not ship in the distributed application: - - ```swift - var migrator = DatabaseMigrator() - #if DEBUG - // Speed up development by nuking the database when migrations change - migrator.eraseDatabaseOnSchemaChange = true - #endif - ``` - -2. When the database content can easily be recreated, such as a cache for some downloaded data. - -The `eraseDatabaseOnSchemaChange` option triggers a recreation of the database if the migrator detects a *schema change*. A schema change is any difference in the `sqlite_master` table, which contains the SQL used to create database tables, indexes, triggers, and views. - - -### Advanced Database Schema Changes - -SQLite does not support many schema changes, and won't let you drop a table column with "ALTER TABLE ... DROP COLUMN ...", for example. - -Yet any kind of schema change is still possible. The SQLite documentation explains in detail how to do so: https://www.sqlite.org/lang_altertable.html#otheralter. This technique requires the temporary disabling of foreign key checks, and is supported by the `registerMigrationWithDeferredForeignKeyCheck` function: - -```swift -// Add a NOT NULL constraint on player.name: -migrator.registerMigrationWithDeferredForeignKeyCheck("AddNotNullCheckOnName") { db in - try db.create(table: "new_player") { t in - t.autoIncrementedPrimaryKey("id") - t.column("name", .text).notNull() - } - try db.execute(sql: "INSERT INTO new_player SELECT * FROM player") - try db.drop(table: "player") - try db.rename(table: "new_player", to: "player") -} -``` - -While your migration code runs with disabled foreign key checks, those are re-enabled and checked at the end of the migration, regardless of eventual errors. - - -## Full-Text Search - -**Full-Text Search is an efficient way to search a corpus of textual documents.** - -```swift -// Create full-text tables -try db.create(virtualTable: "book", using: FTS4()) { t in // or FTS3(), or FTS5() - t.column("author") - t.column("title") - t.column("body") -} - -// Populate full-text table with records or SQL -try Book(...).insert(db) -try db.execute( - sql: "INSERT INTO book (author, title, body) VALUES (?, ?, ?)", - arguments: [...]) - -// Build search patterns -let pattern = FTS3Pattern(matchingPhrase: "Moby-Dick") - -// Search with the query interface or SQL -let books = try Book.matching(pattern).fetchAll(db) -let books = try Book.fetchAll(db, - sql: "SELECT * FROM book WHERE book MATCH ?", - arguments: [pattern]) -``` - -- **[Choosing the Full-Text Engine](#choosing-the-full-text-engine)** -- **[Enabling FTS5 Support](#enabling-fts5-support)** -- **Create Full-Text Virtual Tables**: [FTS3/4](#create-fts3-and-fts4-virtual-tables), [FTS5](#create-fts5-virtual-tables) -- **Choosing a Tokenizer**: [FTS3/4](#fts3-and-fts4-tokenizers), [FTS5](#fts5-tokenizers) -- **Search Patterns**: [FTS3/4](#fts3pattern), [FTS5](#fts5pattern) -- **Sorting by Relevance**: [FTS5](#fts5-sorting-by-relevance) -- **External Content Full-Text Tables**: [FTS4/5](#external-content-full-text-tables) -- **Full-Text Record**s: [FTS3/4/5](#full-text-records) -- **Unicode Full-Text Gotchas**: [FTS3/4/5](#unicode-full-text-gotchas). Unicorns don't exist. -- **Custom Tokenizers**: [FTS5](Documentation/FTS5Tokenizers.md). Leverage extra full-text features such as synonyms or stop words. Avoid [unicode gotchas](#unicode-full-text-gotchas). -- **Sample Code**: [WWDC Companion](https://github.com/groue/WWDCCompanion), an iOS app that stores, displays, and lets the user search the WWDC 2016 sessions. - - - -### Choosing the Full-Text Engine - -**SQLite supports three full-text engines: [FTS3, FTS4](https://www.sqlite.org/fts3.html) and [FTS5](https://www.sqlite.org/fts5.html).** - -Generally speaking, FTS5 is better than FTS4 which improves on FTS3. But this does not really tell which engine to choose for your application. Instead, make your choice depend on: - -- **The full-text features needed by the application**: - - | Full-Text Needs | FTS3 | FTS4 | FTS5 | - | -------------------------------------------------------------------------- | :--: | :--: | :--: | - | :question: Queries | | | | - | **Words searches** (documents that contain "database") | X | X | X | - | **Prefix searches** (documents that contain a word starting with "data") | X | X | X | - | **Phrases searches** (documents that contain the phrase "SQLite database") | X | X | X | - | **Boolean searches** (documents that contain "SQLite" or "database") | X | X | X | - | **Proximity search** (documents that contain "SQLite" near "database") | X | X | X | - | :scissors: Tokenization | | | | - | **Ascii case insensitivity** (have "DATABASE" match "database") | X | X | X | - | **Unicode case insensitivity** (have "ÉLÉGANCE" match "élégance") | X | X | X | - | **Latin diacritics insensitivity** (have "elegance" match "élégance") | X | X | X | - | **English Stemming** (have "frustration" match "frustrated") | X | X | X | - | **English Stemming and Ascii case insensitivity** | X | X | X | - | **English Stemming and Unicode case insensitivity** | | | X | - | **English Stemming and Latin diacritics insensitivity** | | | X | - | **Synonyms** (have "1st" match "first") | ¹ | ¹ | X ² | - | **Pinyin and Romaji** (have "romaji" match "ローマ字") | ¹ | ¹ | X ² | - | **Stop words** (don't index, and don't match words like "and" and "the") | ¹ | ¹ | X ² | - | **Spell checking** (have "alamaba" match "alabama") | ¹ | ¹ | ¹ | - | :bowtie: Other Features | | | | - | **Ranking** (sort results by relevance) | ¹ | ¹ | X | - | **Snippets** (display a few words around a match) | X | X | X | - - ¹ Requires extra setup, possibly hard to implement. - - ² Requires a [custom tokenizer](Documentation/FTS5Tokenizers.md). - - For a full feature list, read the SQLite documentation. Some missing features can be achieved with extra application code. - -- **The speed versus disk space constraints.** Roughly speaking, FTS4 and FTS5 are faster than FTS3, but use more space. FTS4 only supports content compression. - -- **The location of the indexed text in your database schema.** Only FTS4 and FTS5 support "contentless" and "external content" tables. - -- **The SQLite library integrated in your application.** The version of SQLite that ships with iOS, macOS and watchOS supports FTS3 and FTS4 out of the box, but not always FTS5. To use FTS5, see [Enabling FTS5 Support](#enabling-fts5-support). - -- See [FST3 vs. FTS4](https://www.sqlite.org/fts3.html#differences_between_fts3_and_fts4) and [FTS5 vs. FTS3/4](https://www.sqlite.org/fts5.html#appendix_a) for more differences. - -> :point_up: **Note**: In case you were still wondering, it is recommended to read the SQLite documentation: [FTS3 & FTS4](https://www.sqlite.org/fts3.html) and [FTS5](https://www.sqlite.org/fts5.html). - - -### Create FTS3 and FTS4 Virtual Tables - -**FTS3 and FTS4 full-text tables store and index textual content.** - -Create tables with the `create(virtualTable:using:)` method: - -```swift -// CREATE VIRTUAL TABLE document USING fts3(content) -try db.create(virtualTable: "document", using: FTS3()) { t in - t.column("content") -} - -// CREATE VIRTUAL TABLE document USING fts4(content) -try db.create(virtualTable: "document", using: FTS4()) { t in - t.column("content") -} -``` - -**All columns in a full-text table contain text.** If you need to index a table that contains other kinds of values, you need an ["external content" full-text table](#external-content-full-text-tables). - -You can specify a [tokenizer](#fts3-and-fts4-tokenizers): - -```swift -// CREATE VIRTUAL TABLE book USING fts4( -// tokenize=porter, -// author, -// title, -// body -// ) -try db.create(virtualTable: "book", using: FTS4()) { t in - t.tokenizer = .porter - t.column("author") - t.column("title") - t.column("body") -} -``` - -FTS4 supports [options](https://www.sqlite.org/fts3.html#fts4_options): - -```swift -// CREATE VIRTUAL TABLE book USING fts4( -// content, -// uuid, -// content="", -// compress=zip, -// uncompress=unzip, -// prefix="2,4", -// notindexed=uuid, -// languageid=lid -// ) -try db.create(virtualTable: "document", using: FTS4()) { t in - t.content = "" - t.compress = "zip" - t.uncompress = "unzip" - t.prefixes = [2, 4] - t.column("content") - t.column("uuid").notIndexed() - t.column("lid").asLanguageId() -} -``` - -The `content` option is involved in "contentless" and "external content" full-text tables. GRDB can help you defining full-text tables that automatically synchronize with their content table. See [External Content Full-Text Tables](#external-content-full-text-tables). - - -See [SQLite documentation](https://www.sqlite.org/fts3.html) for more information. - - -### FTS3 and FTS4 Tokenizers - -**A tokenizer defines what "matching" means.** Depending on the tokenizer you choose, full-text searches won't return the same results. - -SQLite ships with three built-in FTS3/4 tokenizers: `simple`, `porter` and `unicode61` that use different algorithms to match queries with indexed content: - -```swift -try db.create(virtualTable: "book", using: FTS4()) { t in - // Pick one: - t.tokenizer = .simple // default - t.tokenizer = .porter - t.tokenizer = .unicode61(...) -} -``` - -See below some examples of matches: - -| content | query | simple | porter | unicode61 | -| ----------- | ---------- | :----: | :----: | :-------: | -| Foo | Foo | X | X | X | -| Foo | FOO | X | X | X | -| Jérôme | Jérôme | X ¹ | X ¹ | X ¹ | -| Jérôme | JÉRÔME | | | X ¹ | -| Jérôme | Jerome | | | X ¹ | -| Database | Databases | | X | | -| Frustration | Frustrated | | X | | - -¹ Don't miss [Unicode Full-Text Gotchas](#unicode-full-text-gotchas) - -- **simple** - - ```swift - try db.create(virtualTable: "book", using: FTS4()) { t in - t.tokenizer = .simple // default - } - ``` - - The default "simple" tokenizer is case-insensitive for ASCII characters. It matches "foo" with "FOO", but not "Jérôme" with "JÉRÔME". - - It does not provide stemming, and won't match "databases" with "database". - - It does not strip diacritics from latin script characters, and won't match "jérôme" with "jerome". - -- **porter** - - ```swift - try db.create(virtualTable: "book", using: FTS4()) { t in - t.tokenizer = .porter - } - ``` - - The "porter" tokenizer compares English words according to their roots: it matches "database" with "databases", and "frustration" with "frustrated". - - It does not strip diacritics from latin script characters, and won't match "jérôme" with "jerome". - -- **unicode61** - - ```swift - try db.create(virtualTable: "book", using: FTS4()) { t in - t.tokenizer = .unicode61() - t.tokenizer = .unicode61(diacritics: .keep) - } - ``` - - The "unicode61" tokenizer is case-insensitive for unicode characters. It matches "Jérôme" with "JÉRÔME". - - It strips diacritics from latin script characters by default, and matches "jérôme" with "jerome". This behavior can be disabled, as in the example above. - - It does not provide stemming, and won't match "databases" with "database". - -See [SQLite tokenizers](https://www.sqlite.org/fts3.html#tokenizer) for more information. - - -### FTS3Pattern - -**Full-text search in FTS3 and FTS4 tables is performed with search patterns:** - -- `database` matches all documents that contain "database" -- `data*` matches all documents that contain a word starting with "data" -- `SQLite database` matches all documents that contain both "SQLite" and "database" -- `SQLite OR database` matches all documents that contain "SQLite" or "database" -- `"SQLite database"` matches all documents that contain the "SQLite database" phrase - -**Not all search patterns are valid**: they must follow the [Full-Text Index Queries Grammar](https://www.sqlite.org/fts3.html#full_text_index_queries). - -The FTS3Pattern type helps you validating patterns, and building valid patterns from untrusted strings (such as strings typed by users): - -```swift -struct FTS3Pattern { - init(rawPattern: String) throws - init?(matchingAnyTokenIn string: String) - init?(matchingAllTokensIn string: String) - init?(matchingPhrase string: String) -} -``` - -The first initializer validates your raw patterns against the query grammar, and may throw a [DatabaseError](#databaseerror): - -```swift -// OK: FTS3Pattern -let pattern = try FTS3Pattern(rawPattern: "sqlite AND database") -// DatabaseError: malformed MATCH expression: [AND] -let pattern = try FTS3Pattern(rawPattern: "AND") -``` - -The three other initializers don't throw. They build a valid pattern from any string, **including strings provided by users of your application**. They let you find documents that match all given words, any given word, or a full phrase, depending on the needs of your application: - -```swift -let query = "SQLite database" -// Matches documents that contain "SQLite" or "database" -let pattern = FTS3Pattern(matchingAnyTokenIn: query) -// Matches documents that contain both "SQLite" and "database" -let pattern = FTS3Pattern(matchingAllTokensIn: query) -// Matches documents that contain "SQLite database" -let pattern = FTS3Pattern(matchingPhrase: query) -``` - -They return nil when no pattern could be built from the input string: - -```swift -let pattern = FTS3Pattern(matchingAnyTokenIn: "") // nil -let pattern = FTS3Pattern(matchingAnyTokenIn: "*") // nil -``` - -FTS3Pattern are regular [values](#values). You can use them as query [arguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html): - -```swift -let documents = try Document.fetchAll(db, - sql: "SELECT * FROM document WHERE content MATCH ?", - arguments: [pattern]) -``` - -Use them in the [query interface](#the-query-interface): - -```swift -// Search in all columns -let documents = try Document.matching(pattern).fetchAll(db) - -// Search in a specific column: -let documents = try Document.filter(Column("content").match(pattern)).fetchAll(db) -``` - - -### Enabling FTS5 Support - -When the FTS3 and FTS4 full-text engines don't suit your needs, you may want to use FTS5. See [Choosing the Full-Text Engine](#choosing-the-full-text-engine) to help you make a decision. - -The version of SQLite that ships with iOS, macOS and watchOS does not always support the FTS5 engine. To enable FTS5 support, you'll need to install GRDB with one of those installation techniques: - -1. Use the GRDB.swift CocoaPod with a custom compilation option, as below. It uses the system SQLite, which is compiled with FTS5 support, but only on iOS 11.4+ / macOS 10.13+ / watchOS 4.3+: - - ```ruby - pod 'GRDB.swift' - platform :ios, '11.4' # or above - - post_install do |installer| - installer.pods_project.targets.select { |target| target.name == "GRDB.swift" }.each do |target| - target.build_configurations.each do |config| - config.build_settings['OTHER_SWIFT_FLAGS'] = "$(inherited) -D SQLITE_ENABLE_FTS5" - end - end - end - ``` - - > :warning: **Warning**: make sure you use the right platform version! You will get runtime errors on devices with a lower version. - - > :point_up: **Note**: there used to be a GRDBPlus CocoaPod with pre-enabled FTS5 support. This CocoaPod is deprecated: please switch to the above technique. - -2. Use the GRDB.swift/SQLCipher CocoaPod subspec (see [encryption](#encryption)): - - ```ruby - pod 'GRDB.swift/SQLCipher' - ``` - -3. Use a [custom SQLite build] and activate the `SQLITE_ENABLE_FTS5` compilation option. - - -### Create FTS5 Virtual Tables - -**FTS5 full-text tables store and index textual content.** - -To use FTS5, you'll need a [custom SQLite build] that activates the `SQLITE_ENABLE_FTS5` compilation option. - -Create FTS5 tables with the `create(virtualTable:using:)` method: - -```swift -// CREATE VIRTUAL TABLE document USING fts5(content) -try db.create(virtualTable: "document", using: FTS5()) { t in - t.column("content") -} -``` - -**All columns in a full-text table contain text.** If you need to index a table that contains other kinds of values, you need an ["external content" full-text table](#external-content-full-text-tables). - -You can specify a [tokenizer](#fts5-tokenizers): - -```swift -// CREATE VIRTUAL TABLE book USING fts5( -// tokenize='porter', -// author, -// title, -// body -// ) -try db.create(virtualTable: "book", using: FTS5()) { t in - t.tokenizer = .porter() - t.column("author") - t.column("title") - t.column("body") -} -``` - -FTS5 supports [options](https://www.sqlite.org/fts5.html#fts5_table_creation_and_initialization): - -```swift -// CREATE VIRTUAL TABLE book USING fts5( -// content, -// uuid UNINDEXED, -// content='table', -// content_rowid='id', -// prefix='2 4', -// columnsize=0, -// detail=column -// ) -try db.create(virtualTable: "document", using: FTS5()) { t in - t.column("content") - t.column("uuid").notIndexed() - t.content = "table" - t.contentRowID = "id" - t.prefixes = [2, 4] - t.columnSize = 0 - t.detail = "column" -} -``` - -The `content` and `contentRowID` options are involved in "contentless" and "external content" full-text tables. GRDB can help you defining full-text tables that automatically synchronize with their content table. See [External Content Full-Text Tables](#external-content-full-text-tables). - -See [SQLite documentation](https://www.sqlite.org/fts5.html) for more information. - - -### FTS5 Tokenizers - -**A tokenizer defines what "matching" means.** Depending on the tokenizer you choose, full-text searches won't return the same results. - -SQLite ships with three built-in FTS5 tokenizers: `ascii`, `porter` and `unicode61` that use different algorithms to match queries with indexed content. - -```swift -try db.create(virtualTable: "book", using: FTS5()) { t in - // Pick one: - t.tokenizer = .unicode61() // default - t.tokenizer = .unicode61(...) - t.tokenizer = .ascii - t.tokenizer = .porter(...) -} -``` - -See below some examples of matches: - -| content | query | ascii | unicode61 | porter on ascii | porter on unicode61 | -| ----------- | ---------- | :----: | :-------: | :-------------: | :-----------------: | -| Foo | Foo | X | X | X | X | -| Foo | FOO | X | X | X | X | -| Jérôme | Jérôme | X ¹ | X ¹ | X ¹ | X ¹ | -| Jérôme | JÉRÔME | | X ¹ | | X ¹ | -| Jérôme | Jerome | | X ¹ | | X ¹ | -| Database | Databases | | | X | X | -| Frustration | Frustrated | | | X | X | - -¹ Don't miss [Unicode Full-Text Gotchas](#unicode-full-text-gotchas) - -- **unicode61** - - ```swift - try db.create(virtualTable: "book", using: FTS5()) { t in - t.tokenizer = .unicode61() - t.tokenizer = .unicode61(diacritics: .keep) - } - ``` - - The default "unicode61" tokenizer is case-insensitive for unicode characters. It matches "Jérôme" with "JÉRÔME". - - It strips diacritics from latin script characters by default, and matches "jérôme" with "jerome". This behavior can be disabled, as in the example above. - - It does not provide stemming, and won't match "databases" with "database". - -- **ascii** - - ```swift - try db.create(virtualTable: "book", using: FTS5()) { t in - t.tokenizer = .ascii - } - ``` - - The "ascii" tokenizer is case-insensitive for ASCII characters. It matches "foo" with "FOO", but not "Jérôme" with "JÉRÔME". - - It does not provide stemming, and won't match "databases" with "database". - - It does not strip diacritics from latin script characters, and won't match "jérôme" with "jerome". - -- **porter** - - ```swift - try db.create(virtualTable: "book", using: FTS5()) { t in - t.tokenizer = .porter() // porter wrapping unicode61 (the default) - t.tokenizer = .porter(.ascii) // porter wrapping ascii - t.tokenizer = .porter(.unicode61(diacritics: .keep)) // porter wrapping unicode61 without diacritics stripping - } - ``` - - The porter tokenizer is a wrapper tokenizer which compares English words according to their roots: it matches "database" with "databases", and "frustration" with "frustrated". - - It strips diacritics from latin script characters if it wraps unicode61, and does not if it wraps ascii (see the example above). - -See [SQLite tokenizers](https://www.sqlite.org/fts5.html#tokenizers) for more information, and [custom FTS5 tokenizers](Documentation/FTS5Tokenizers.md) in order to add your own tokenizers. - - -### FTS5Pattern - -**Full-text search in FTS5 tables is performed with search patterns:** - -- `database` matches all documents that contain "database" -- `data*` matches all documents that contain a word starting with "data" -- `SQLite database` matches all documents that contain both "SQLite" and "database" -- `SQLite OR database` matches all documents that contain "SQLite" or "database" -- `"SQLite database"` matches all documents that contain the "SQLite database" phrase - -**Not all search patterns are valid**: they must follow the [Full-Text Query Syntax](https://www.sqlite.org/fts5.html#full_text_query_syntax). - -The FTS5Pattern type helps you validating patterns, and building valid patterns from untrusted strings (such as strings typed by users): - -```swift -extension Database { - func makeFTS5Pattern(rawPattern: String, forTable table: String) throws -> FTS5Pattern -} - -struct FTS5Pattern { - init?(matchingAnyTokenIn string: String) - init?(matchingAllTokensIn string: String) - init?(matchingPhrase string: String) -} -``` - -The `Database.makeFTS5Pattern(rawPattern:forTable:)` method validates your raw patterns against the query grammar and the columns of the targeted table, and may throw a [DatabaseError](#databaseerror): - -```swift -// OK: FTS5Pattern -try db.makeFTS5Pattern(rawPattern: "sqlite", forTable: "book") -// DatabaseError: syntax error near \"AND\" -try db.makeFTS5Pattern(rawPattern: "AND", forTable: "book") -// DatabaseError: no such column: missing -try db.makeFTS5Pattern(rawPattern: "missing: sqlite", forTable: "book") -``` - -The FTS5Pattern initializers don't throw. They build a valid pattern from any string, **including strings provided by users of your application**. They let you find documents that match all given words, any given word, or a full phrase, depending on the needs of your application: - -```swift -let query = "SQLite database" -// Matches documents that contain "SQLite" or "database" -let pattern = FTS5Pattern(matchingAnyTokenIn: query) -// Matches documents that contain both "SQLite" and "database" -let pattern = FTS5Pattern(matchingAllTokensIn: query) -// Matches documents that contain "SQLite database" -let pattern = FTS5Pattern(matchingPhrase: query) -// Matches documents that start with "SQLite database" -let pattern = FTS5Pattern(matchingPrefixPhrase: query) -``` - -They return nil when no pattern could be built from the input string: - -```swift -let pattern = FTS5Pattern(matchingAnyTokenIn: "") // nil -let pattern = FTS5Pattern(matchingAnyTokenIn: "*") // nil -``` - -FTS5Pattern are regular [values](#values). You can use them as query [arguments](http://groue.github.io/GRDB.swift/docs/4.0/Structs/StatementArguments.html): - -```swift -let documents = try Document.fetchAll(db, - sql: "SELECT * FROM document WHERE document MATCH ?", - arguments: [pattern]) -``` - -Use them in the [query interface](#the-query-interface): - -```swift -let documents = try Document.matching(pattern).fetchAll(db) -``` - - -### FTS5: Sorting by Relevance - -**FTS5 can sort results by relevance** (most to least relevant): - -```swift -// SQL -let documents = try Document.fetchAll(db, - sql: "SELECT * FROM document WHERE document MATCH ? ORDER BY rank", - arguments: [pattern]) - -// Query Interface -let documents = try Document.matching(pattern).order(Column.rank).fetchAll(db) -``` - -For more information about the ranking algorithm, as well as extra options, read [Sorting by Auxiliary Function Results](https://www.sqlite.org/fts5.html#sorting_by_auxiliary_function_results) - -GRDB does not provide any ranking for FTS3 and FTS4. See SQLite's [Search Application Tips](https://www.sqlite.org/fts3.html#appendix_a) if you really need it. - - -### External Content Full-Text Tables - -**An external content table does not store the indexed text.** Instead, it indexes the text stored in another table. - -This is very handy when you want to index a table that can not be declared as a full-text table (because it contains non-textual values, for example). You just have to define an external content full-text table that refers to the regular table. - -The two tables must be kept up-to-date, so that the full-text index matches the content of the regular table. This synchronization happens automatically if you use the `synchronize(withTable:)` method in your full-text table definition: - -```swift -// A regular table -try db.create(table: "book") { t in - t.column("author", .text) - t.column("title", .text) - t.column("content", .text) - ... -} - -// A full-text table synchronized with the regular table -try db.create(virtualTable: "book_ft", using: FTS4()) { t in // or FTS5() - t.synchronize(withTable: "book") - t.column("author") - t.column("title") - t.column("content") -} -``` - -The eventual content already present in the regular table is indexed, and every insert, update or delete that happens in the regular table is automatically applied to the full-text index. - -For more information, see the SQLite documentation about external content tables: [FTS4](https://www.sqlite.org/fts3.html#_external_content_fts4_tables_), [FTS5](https://sqlite.org/fts5.html#external_content_tables). - -See also [WWDC Companion](https://github.com/groue/WWDCCompanion), a sample app that uses external content tables to store, display, and let the user search the WWDC sessions. - - -#### Deleting Synchronized Full-Text Tables - -Synchronization of full-text tables with their content table happens by the mean of SQL triggers. - -SQLite automatically deletes those triggers when the content (not full-text) table is dropped. - -However, those triggers remain after the full-text table has been dropped. Unless they are dropped too, they will prevent future insertion, updates, and deletions in the content table, and the creation of a new full-text table. - -To drop those triggers, use the `dropFTS4SynchronizationTriggers` or `dropFTS5SynchronizationTriggers` methods: - -```swift -// Create tables -try db.create(table: "book") { t in - ... -} -try db.create(virtualTable: "book_ft", using: FTS4()) { t in - t.synchronize(withTable: "book") - ... -} - -// Drop full-text table -try db.drop(table: "book_ft") -try db.dropFTS4SynchronizationTriggers(forTable: "book_ft") -``` - -> :warning: **Warning**: there was a bug in GRDB up to version 2.3.1 included, which created triggers with a wrong name. If it is possible that the full-text table was created by an old version of GRDB, then delete the synchronization triggers **twice**: once with the name of the deleted full-text table, and once with the name of the content table: -> -> ```swift -> // Drop full-text table -> try db.drop(table: "book_ft") -> try db.dropFTS4SynchronizationTriggers(forTable: "book_ft") -> try db.dropFTS4SynchronizationTriggers(forTable: "book") // Support for GRDB <= 2.3.1 -> ``` - - - -#### Querying External Content Full-Text Tables - -When you need to perform a full-text search, and the external content table contains all the data you need, you can simply query the full-text table. - -But if you need to load columns from the regular table, and in the same time perform a full-text search, then you will need to query both tables at the same time. - -That is because SQLite will throw an error when you try to perform a full-text search on a regular table: - -```swift -// SQLite error 1: unable to use function MATCH in the requested context -// SELECT * FROM book WHERE book MATCH '...' -let books = Book.matching(pattern).fetchAll(db) -``` - -The solution is to perform a joined request, using raw SQL: - -```swift -let sql = """ - SELECT book.* - FROM book - JOIN book_ft - ON book_ft.rowid = book.rowid - AND book_ft MATCH ? - """ -let books = Book.fetchAll(db, sql: sql, arguments: [pattern]) -``` - - -### Full-Text Records - -**You can define [record](#records) types around the full-text virtual tables.** - -However these tables don't have any explicit primary key. Instead, they use the [implicit rowid primary key](#the-implicit-rowid-primary-key): a special hidden column named `rowid`. - -You will have to [expose this hidden column](#exposing-the-rowid-column) in order to fetch, delete, and update full-text records by primary key. - - -### Unicode Full-Text Gotchas - -The SQLite built-in tokenizers for [FTS3, FTS4](#fts3-and-fts4-tokenizers) and [FTS5](#fts5-tokenizers) are generally unicode-aware, with a few caveats, and limitations. - -Generally speaking, matches may fail when content and query don't use the same [unicode normalization](http://unicode.org/reports/tr15/). SQLite actually exhibits inconsistent behavior in this regard. - -For example, for "aimé" to match "aimé", they better have the same normalization: the NFC "aim\u{00E9}" form may not match its NFD "aime\u{0301}" equivalent. Most strings that you get from Swift, UIKit and Cocoa use NFC, so be careful with NFD inputs (such as strings from the HFS+ file system, or strings that you can't trust like network inputs). Use [String.precomposedStringWithCanonicalMapping](https://developer.apple.com/reference/swift/string/1407210-precomposedstringwithcanonicalma) to turn a string into NFC. - -Besides, if you want "fi" to match the ligature "fi" (U+FB01), then you need to normalize your indexed contents and inputs to NFKC or NFKD. Use [String.precomposedStringWithCompatibilityMapping](https://developer.apple.com/reference/swift/string/1407834-precomposedstringwithcompatibili) to turn a string into NFKC. - -Unicode normalization is not the end of the story, because it won't help "Encyclopaedia" match "Encyclopædia", "Mueller", "Müller", "Grossmann", "Großmann", or "Diyarbakır", "DIYARBAKIR". The [String.applyingTransform](https://developer.apple.com/reference/swift/string/1643133-applyingtransform) method can help. - -GRDB lets you write [custom FTS5 tokenizers](Documentation/FTS5Tokenizers.md) that can transparently deal with all these issues. For FTS3 and FTS4, you'll need to pre-process your strings before injecting them in the full-text engine. - -Happy indexing! - - -## Joined Queries Support - -**GRDB helps consuming joined queries with complex selection.** - -In this chapter, we will focus on the extraction of information from complex rows, such as the ones fetched by the query below: - -```sql --- How to consume the left, middle, and right parts of those rows? -SELECT player.*, team.*, MAX(round.score) AS maxScore -FROM player -LEFT JOIN team ON ... -LEFT JOIN round ON ... -GROUP BY ... -``` - -We will not talk about the *generation* of joined queries, which is covered in [Associations]. - -**So what are we talking about?** - -It is difficult to consume rows fetched from complex joined queries, because they often contain several columns with the same name: `id` from table `player`, `id` from table `team`, etc. - -When such ambiguity happens, GRDB row accessors always favor the leftmost matching column. This means that `row["id"]` would give a player id, whithout any obvious way to access the team id. - -A classical technique to avoid this ambiguity is to give each column a unique name. For example: - -```sql --- A classical technique -SELECT player.id AS player_id, player.name AS player_name, team.id AS team_id, team.name AS team_name, team.color AS team_color, MAX(round.score) AS maxScore -FROM player -LEFT JOIN team ON ... -LEFT JOIN round ON ... -GROUP BY ... -``` - -This technique works pretty well, but it has three drawbacks: - -1. The selection becomes hard to read and understand. -2. Such queries are difficult to write by hand. -3. The mangled names are a *very* bad fit for [FetchableRecord] types that expect specific column names. After all, if the `Team` record type can read `SELECT * FROM team ...`, it should be able to read `SELECT ..., team.*, ...` as well. - -We thus need another technique. **Below we'll see how to split rows into slices, and preserve column names.** - -`SELECT player.*, team.*, MAX(round.score) AS maxScore FROM ...` will be splitted into three slices: one that contains player's columns, one that contains team's columns, and a remaining slice that contains remaining column(s). The Player record type will be able to read the first slice, which contains the colums expected by the `Player.init(row:)` initializer. In the same way, the Team record type could read the second slice. - -Unlike the name-mangling technique, splitting rows keeps SQL legible, accepts your hand-crafted SQL queries, and plays as nicely as possible with your existing [record types](#records). - -- [Splitting Rows, an Introduction](#splitting-rows-an-introduction) -- [Splitting Rows, the Record Way](#splitting-rows-the-record-way) -- [Splitting Rows, the Request Way](#splitting-rows-the-request-way) -- [Splitting Rows, the Codable Way](#splitting-rows-the-codable-way) - - -### Splitting Rows, an Introduction - -Let's first write some introductory code, hoping that this chapter will make you understand how pieces fall together. We'll see [later](#splitting-rows-the-record-way) how records will help us streamline the initial approach, how to track changes in joined requests, and how we can use the standard Decodable protocol. - -To split rows, we will use [row adapters](#row-adapters). Row adapters adapt rows so that row consumers see exactly the columns they want. Among other things, row adapters can define several *row scopes* that give access to as many *row slices*. Sounds like a perfect match. - -At the very beginning, there is an SQL query: - -```swift -try dbQueue.read { db in - let sql = """ - SELECT player.*, team.*, MAX(round.score) AS maxScore - FROM player - LEFT JOIN team ON ... - LEFT JOIN round ON ... - GROUP BY ... - """ -``` - -We need an adapter that extracts player columns, in a slice that has as many columns as there are columns in the player table. That's [RangeRowAdapter](#rangerowadapter): - -```swift - // SELECT player.*, team.*, ... - // <------> - let playerWidth = try db.columns(in: "player").count - let playerAdapter = RangeRowAdapter(0 ..< playerWidth) -``` - -We also need an adapter that extracts team columns: - -```swift - // SELECT player.*, team.*, ... - // <----> - let teamWidth = try db.columns(in: "team").count - let teamAdapter = RangeRowAdapter(playerWidth ..< (playerWidth + teamWidth)) -``` - -We merge those two adapters in a single [ScopeAdapter](#scopeadapter) that will allow us to access both sliced rows: - -```swift - let playerScope = "player" - let teamScope = "team" - let adapter = ScopeAdapter([ - playerScope: playerAdapter, - teamScope: teamAdapter]) -``` - -And now we can fetch, and start consuming our rows. You already know [row cursors](#fetching-rows): - -```swift - let rows = try Row.fetchCursor(db, sql: sql, adapter: adapter) - while let row = try rows.next() { -``` - -From a fetched row, we can build a player: - -```swift - let player: Player = row[playerScope] -``` - -In the SQL query, the team is joined with the `LEFT JOIN` operator. This means that the team may be missing: its slice may contain team values, or it may only contain NULLs. When this happens, we don't want to build a Team record, and we thus load an *optional* Team: - -```swift - let team: Team? = row[teamScope] -``` - -And finally, we can load the maximum score, assuming that the "maxScore" column is not ambiguous: - -```swift - let maxScore: Int = row["maxScore"] - - print("player: \(player)") - print("team: \(team)") - print("maxScore: \(maxScore)") - } -} -``` - -> :bulb: In this chapter, we have learned: -> -> - how to use `RangeRowAdapter` to extract a specific table's columns into a *row slice*. -> - how to use `ScopeAdapter` to gives access to several row slices through named scopes. -> - how to use Row subscripting to extract records from rows, or optional records in order to deal with left joins. - - -### Splitting Rows, the Record Way - -Our introduction above has introduced important techniques. It uses [row adapters](#row-adapters) in order to split rows. It uses Row subscripting in order to extract records from row slices. - -But we may want to make it more usable and robust: - -1. It's generally easier to consume records than raw rows. -2. Joined records not always need all columns from a table (see `TableRecord.databaseSelection` in [Columns Selected by a Request]). -3. Building row adapters is long and error prone. - -To address the first bullet, let's define a record that holds our player, optional team, and maximum score. Since it can decode database rows, it adopts the [FetchableRecord] protocol: - -```swift -struct PlayerInfo { - var player: Player - var team: Team? - var maxScore: Int -} - -/// PlayerInfo can decode rows: -extension PlayerInfo: FetchableRecord { - private enum Scopes { - static let player = "player" - static let team = "team" - } - - init(row: Row) { - player = row[Scopes.player] - team = row[Scopes.team] - maxScore = row["maxScore"] - } -} -``` - -Let's now write the method that fetches PlayerInfo records: - -```swift -extension PlayerInfo { - static func fetchAll(_ db: Database) throws -> [PlayerInfo] { -``` - -To acknowledge that both Player and Team records may customize their selection of the "player" and "team" columns, we'll write our SQL in a slightly different way: - -```swift - // Let Player and Team customize their selection: - let sql = """ - SELECT - \(Player.selectionSQL()), -- instead of player.* - \(Team.selectionSQL()), -- instead of team.* - MAX(round.score) AS maxScore - FROM player - LEFT JOIN team ON ... - LEFT JOIN round ON ... - GROUP BY ... - """ -``` - -`Player.selectionSQL()` will output `player.*`, unless Player defines a [customized selection](#columns-selected-by-a-request). - -> :point_up: **Note**: you may also use SQL table aliases: -> -> ```swift -> let sql = """ -> SELECT -> \(Player.selectionSQL(alias: "p")), -> \(Team.selectionSQL(alias: "t")), -> MAX(r.score) AS maxScore -> FROM player p -> LEFT JOIN team t ON ... -> LEFT JOIN round r ON ... -> GROUP BY ... -> """ -> ``` - -Now is the time to build adapters (taking in account the customized selection of both player and team). We use the `splittingRowAdapters` global function, which builds row adapters of desired widths: - -```swift - let adapters = try splittingRowAdapters(columnCounts: [ - Player.numberOfSelectedColumns(db), - Team.numberOfSelectedColumns(db)]) - - let adapter = ScopeAdapter([ - Scopes.player: adapters[0], - Scopes.team: adapters[1]]) -``` - -> :point_up: **Note**: `splittingRowAdapters` returns as many adapters as necessary to fully split a row. In the example above, it returns *three* adapters: one for player, one for team, and one for the remaining columns. - -And finally, we can fetch player infos: - -```swift - return try PlayerInfo.fetchAll(db, sql: sql, adapter: adapter) - } -} -``` - -And when your app needs to fetch player infos, it now reads: - -```swift -// Fetch player infos -let playerInfos = try dbQueue.read { db in - try PlayerInfo.fetchAll(db) -} -``` - - -> :bulb: In this chapter, we have learned: -> -> - how to define a `FetchableRecord` record that consumes rows fetched from a joined query. -> - how to use `selectionSQL` and `numberOfSelectedColumns` in order to deal with nested record types that define custom selection. -> - how to use `splittingRowAdapters` in order to streamline the definition of row slices. -> - how to gather all relevant methods and constants in a record type, fully responsible of its relationship with the database. - - -### Splitting Rows, the Request Way - -The `PlayerInfo.fetchAll` method [above](#splitting-rows-the-record-way) directly fetches records. It's all good, but in order to profit from [database observation](#database-changes-observation), you'll need a [custom request](#custom-requests) that defines a database query. - -It is recommended that you read the previous paragraphs before you dive in this sample code. We start with the same PlayerInfo record as above: - -```swift -struct PlayerInfo { - var player: Player - var team: Team? - var maxScore: Int -} - -/// PlayerInfo can decode rows: -extension PlayerInfo: FetchableRecord { - private enum Scopes { - static let player = "player" - static let team = "team" - } - - init(row: Row) { - player = row[Scopes.player] - team = row[Scopes.team] - maxScore = row["maxScore"] - } -} -``` - -Now we write a method that returns a request, and build the fetching method on top of that request: - -```swift -extension PlayerInfo { - /// The request for all player infos - static func all() -> AdaptedFetchRequest> { - let sql = """ - SELECT - \(Player.selectionSQL()), - \(Team.selectionSQL()), - MAX(round.score) AS maxScore - FROM player - LEFT JOIN team ON ... - LEFT JOIN round ON ... - GROUP BY ... - """ - return SQLRequest(sql: sql).adapted { db in - let adapters = try splittingRowAdapters(columnCounts: [ - Player.numberOfSelectedColumns(db), - Team.numberOfSelectedColumns(db)]) - return ScopeAdapter([ - Scopes.player: adapters[0], - Scopes.team: adapters[1]]) - } - } - - /// Fetches all player infos - static func fetchAll(_ db: Database) throws -> [PlayerInfo] { - return try all().fetchAll(db) - } -} -``` - -It is now time to use our request: - -```swift -// Fetch player infos -let playerInfos = try dbQueue.read { db in - try PlayerInfo.fetchAll(db) -} - -// Track player infos with RxRGDB: -PlayerInfo.all() - .rx.fetchAll(in: dbQueue) - .subscribe(onNext: { (playerInfos: [PlayerInfo]) in - print("Player infos have changed") - }) -``` - -> :bulb: In this chapter, we have learned how to define a custom request that can both fetch records from joined queries, and feed database observation tools. - - -### Splitting Rows, the Codable Way - -[Codable Records] build on top of the standard Decodable protocol in order to decode database rows. - -You can consume complex joined queries with Codable records as well. As a demonstration, we'll rewrite the [above](#splitting-rows-the-request-way) sample code: - -```swift -struct Player: Decodable, FetchableRecord, TableRecord { - var id: Int64 - var name: String -} -struct Team: Decodable, FetchableRecord, TableRecord { - var id: Int64 - var name: String - var color: Color -} -struct PlayerInfo: Decodable, FetchableRecord { - var player: Player - var team: Team? - var maxScore: Int -} - -extension PlayerInfo { - /// The request for all player infos - static func all() -> AdaptedFetchRequest> { - let sql = """ - SELECT - \(Player.selectionSQL()), - \(Team.selectionSQL()), - MAX(round.score) AS maxScore - FROM player - LEFT JOIN team ON ... - LEFT JOIN round ON ... - GROUP BY ... - """ - return SQLRequest(sql: sql).adapted { db in - let adapters = try splittingRowAdapters(columnCounts: [ - Player.numberOfSelectedColumns(db), - Team.numberOfSelectedColumns(db)]) - return ScopeAdapter([ - CodingKeys.player.stringValue: adapters[0], - CodingKeys.team.stringValue: adapters[1]]) - } - } - - /// Fetches all player infos - static func fetchAll(_ db: Database) throws -> [PlayerInfo] { - return try all().fetchAll(db) - } -} - -// Fetch player infos -let playerInfos = try dbQueue.read { db in - try PlayerInfo.fetchAll(db) -} - -// Track player infos with RxRGDB: -PlayerInfo.all() - .rx.fetchAll(in: dbQueue) - .subscribe(onNext: { (playerInfos: [PlayerInfo]) in - print("Player infos have changed") - }) -``` - -> :bulb: In this chapter, we have learned how to use the `Decodable` protocol and its associated `CodingKeys` enum in order to dry up our code. - - -Database Changes Observation -============================ - -**SQLite notifies its host application of changes performed to the database, as well of transaction commits and rollbacks.** - -GRDB puts this SQLite feature to some good use, and lets you observe the database in various ways: - -- [After Commit Hook](#after-commit-hook): Handle successful transactions one by one. -- [ValueObservation and DatabaseRegionObservation]: Automated tracking of database requests. -- [FetchedRecordsController]: Animate table views according to database changes. -- [TransactionObserver Protocol](#transactionobserver-protocol): Low-level database observation. -- [RxGRDB]: Automated tracking of database changes, with [RxSwift](https://github.com/ReactiveX/RxSwift). - -Database observation requires that a single [database queue](#database-queues) or [pool](#database-pools) is kept open for all the duration of the database usage. - - -## After Commit Hook - -When your application needs to make sure a specific database transaction has been successfully committed before it executes some work, use the `Database.afterNextTransactionCommit(_:)` method. - -Its closure argument is called right after database changes have been successfully written to disk: - -```swift -try dbQueue.write { db in - db.afterNextTransactionCommit { db in - print("success") - } - ... -} // prints "success" -``` - -The closure runs in a protected dispatch queue, serialized with all database updates. - -**This "after commit hook" helps synchronizing the database with other resources, such as files, or system sensors.** - -In the example below, a [location manager](https://developer.apple.com/documentation/corelocation/cllocationmanager) starts monitoring a CLRegion if and only if it has successfully been stored in the database: - -```swift -/// Inserts a region in the database, and start monitoring upon -/// successful insertion. -func startMonitoring(_ db: Database, region: CLRegion) throws { - // Make sure database is inside a transaction - try db.inSavepoint { - - // Save the region in the database - try insert(...) - - // Start monitoring if and only if the insertion is - // eventually committed - db.afterNextTransactionCommit { _ in - // locationManager prefers the main queue: - DispatchQueue.main.async { - locationManager.startMonitoring(for: region) - } - } - - return .commit - } -} -``` - -The method above won't trigger the location manager if the transaction is eventually rollbacked (explicitly, or because of an error), as in the sample code below: - -```swift -try dbQueue.write { db in - // success - try startMonitoring(db, region) - - // On error, the transaction is rollbacked, the region is not inserted, and - // the location manager is not invoked. - try failableMethod(db) -} -``` - - -## ValueObservation and DatabaseRegionObservation - -**ValueObservation and DatabaseRegionObservation** are two database observations tools that track changes in database [requests](#requests). - -```swift -// Let's observe all players! -let request = Player.all() -``` - -[ValueObservation] notifies your application with **fresh values** (this is what most applications need :+1:): - -```swift -let observation = ValueObservation.trackingAll(request) -let observer = observation.start(in: dbQueue) { (players: [Player]) in - let names = players.map { $0.name }.joined(separator: ", ") - print("Fresh players: \(names)") -} - -try dbQueue.write { db in - try Player(name: "Arthur").insert(db) -} -// Prints "Fresh players: Arthur, ..." -``` - -[DatabaseRegionObservation] notifies your application with **database connections**, right after an impactful database transaction has been committed (reserved for more advanced use cases :nerd_face:): - -```swift -let observation = DatabaseRegionObservation(tracking: request) -let observer = observation.start(in: dbQueue) { (db: Database) in - print("Players have changed.") -} - -try dbQueue.write { db in - try Player(name: "Barbara").insert(db) -} -// Prints "Players have changed." -``` - -- [ValueObservation] -- [DatabaseRegionObservation] - - -## ValueObservation - -**ValueObservation tracks changes in the results of database [requests](#requests), and notifies fresh values whenever the database changes.** - -Changes are only notified after they have been committed in the database. No insertion, update, or deletion in tracked tables is missed. This includes indirect changes triggered by [foreign keys](https://www.sqlite.org/foreignkeys.html#fk_actions) or [SQL triggers](https://www.sqlite.org/lang_createtrigger.html). - - -- **[ValueObservation Usage](#valueobservation-usage)** -- [ValueObservation.trackingCount, trackingOne, trackingAll](#valueobservationtrackingcount-trackingone-trackingall) -- [ValueObservation.tracking(_:fetch:)](#valueobservationtracking_fetch) -- [ValueObservation Transformations](#valueobservation-transformations): [map](#valueobservationmap), [compactMap](#valueobservationcompactmap), ... -- [ValueObservation Error Handling](#valueobservation-error-handling) -- [ValueObservation Options](#valueobservation-options) -- [Advanced: ValueObservation.tracking(_:reducer:)](#advanced-valueobservationtracking_reducer) - - -### ValueObservation Usage - -Here is a typical UIViewController which observes the database in order to keep its view up-to-date: - -```swift -class PlayerViewController: UIViewController { - @IBOutlet weak var nameLabel: UILabel! - private var observer: TransactionObserver? - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - // Define a ValueObservation which tracks a player - let request = Player.filter(key: 42) - let observation = ValueObservation.trackingOne(request) - - // Start observing the database - observer = try! observation.start( - in: dbQueue, - onChange: { [unowned self] (player: Player?) in - // Player has been refreshed: update view - self.nameLabel.text = player?.name - }) - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - // Stop observing the database - observer = nil - } -} -``` - -By default, all values are notified on the main queue. Views can be updated right from the `onChange` callback. - -By default, an initial fetch is performed as soon as the observation starts: the view is set up and ready when the `viewWillAppear` method returns. - -The observer returned by the `start` method is stored in a property of the view controller. This allows the view controller to control the duration of the observation. When the observer is deallocated, the observation stops. Meanwhile, all transactions that modify the observed player are notified, and the `nameLabel` is kept up-to-date. - -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample app that uses ValueObservation. -> -> :bulb: **Tip**: When fetching values is slow, and should never ever block the main queue, opt in for async notifications: -> -> ```swift -> override func viewWillAppear(_ animated: Bool) { -> super.viewWillAppear(animated) -> -> // Define a ValueObservation which tracks a player -> let request = Player.filter(key: 42) -> var observation = ValueObservation.trackingOne(request) -> -> // Observation is asynchronous -> observation.scheduling = .async(onQueue: .main, startImmediately: true) -> -> // Start observing the database -> observer = try! observation.start( -> in: dbQueue, -> onChange: { [unowned self] (player: Player?) in -> // Player has been refreshed: update view -> self.activityIndicator.stopAnimating() -> self.nameLabel.text = player?.name -> }) -> -> // Wait for player -> activityIndicator.startAnimating() -> nameLabel.text = nil -> } -> ``` -> -> See [ValueObservation.scheduling](#valueobservationscheduling) for more information. - -### ValueObservation.trackingCount, trackingOne, trackingAll - -Given a [request](#requests), you can track its number of results, the first one, or all of them: - -```swift -ValueObservation.trackingCount(request) -ValueObservation.trackingOne(request) -ValueObservation.trackingAll(request) -``` - -Those observations match the `fetchCount`, `fetchOne`, and `fetchAll` request methods: - -- `trackingCount` notifies counts: - - ```swift - // Observe number of players - let observer = ValueObservation - .trackingCount(Player.all()) - .start(in: dbQueue) { (count: Int) in - print("Number of players have changed: \(count)") - } - ``` - -- `trackingOne` notifies optional values, built from a single database row (if any): - - ```swift - // Observe a single player - let observer = ValueObservation - .trackingOne(Player.filter(key: 1)) - .start(in: dbQueue) { (player: Player?) in - print("Player has changed: \(player)") - } - - // Observe the maximum score - let request = Player.select(max(Column("score")), as: Int.self) - let observer = ValueObservation - .trackingOne(request) - .start(in: dbQueue) { (maximumScore: Int?) in - print("Maximum score has changed: \(maximumScore)") - } - ``` - -- `trackingAll` notifies arrays: - - ```swift - // Observe all players - let observer = ValueObservation - .trackingAll(Player.all()) - .start(in: dbQueue) { (players: [Player]) in - print("Players have changed: \(players)") - } - - // Observe all player names - let request = SQLRequest(sql: "SELECT name FROM player") - let observer = ValueObservation - .trackingAll(request) - .start(in: dbQueue) { (names: [String]) in - print("Player names have changed: \(names)") - } - ``` - -> :point_up: **Note**: the observations returned by the [ValueObservation.trackingCount, trackingOne, and trackingAll](#valueobservationtrackingcount-trackingone-trackingall) methods perform a filtering of consecutive identical values, based on raw database values. - - -### ValueObservation.tracking(_:fetch:) - -Observing the database is not always easy to express with simple requests, as above. - -For example, let's say that we have a struct that defines a "Hall of Fame": - -```swift -struct HallOfFame { - var totalPlayerCount: Int - var bestPlayers: [Player] - - /// Fetch a HallOfFame - static fetch(_ db: Database) throws -> HallOfFame { - let totalPlayerCount = try Player.fetchCount(db) - let bestPlayers = try Player - .order(Column("score").desc) - .limit(10) - .fetchAll(db) - return HallOfFame( - totalPlayerCount: totalPlayerCount, - bestPlayers: bestPlayers) - } -} - -let hallOfFame = try dbQueue.read { db in try HallOfFame.fetch(db) } -print(""" - Best players out of \(hallOfFame.totalPlayerCount): - \(hallOfFame.bestPlayers) - """) -``` - -In order to track changes in the Hall of Fame, we'll use the `ValueObservation.tracking(_:fetch:)` method. It accepts two parameters: - -1. A list of observed requests. -2. A closure that fetches a fresh value whenever one of the observed requests are modified. - -In our case, any change to the `player` table can impact the Hall of Fame. We thus track the request for all players, `Player.all()`, and fetch a new Hall of Fame whenever players change: - -```swift -let observation = ValueObservation.tracking(Player.all(), fetch: { db in - try HallOfFame.fetch(db) -}) - -let observer = observation.start(in: dbQueue) { (hallOfFame: HallOfFame) in - print(""" - Best players out of \(hallOfFame.totalPlayerCount): - \(hallOfFame.bestPlayers) - """) -} -``` - - -#### Filtering out Consecutive Identical Values - -It may happen that a database change does not modify the observed values. The Hall of Fame, for example, is not affected by changes that happen to the worst players. - -When such a database change happens, `ValueObservation.tracking(_:fetch:)` is triggered, just in case the best players would be modified, and ends up notifying identical consecutive values. - -You can filter out those duplicates with the [ValueObservation.distinctUntilChanged](#valueobservationdistinctuntilchanged) method. It requires the observed value to adopt the Equatable protocol: - -```swift -extension HallOfFame: Equatable { ... } - -let observation = ValueObservation - .tracking(Player.all(), fetch: HallOfFame.fetch) - .distinctUntilChanged() - -let observer = observation.start(in: dbQueue) { (hallOfFame: HallOfFame) in - print(""" - Best players out of \(hallOfFame.totalPlayerCount): - \(hallOfFame.bestPlayers) - """) -} -``` - - -#### DatabaseRegionConvertible Observation - -The initial parameter of the `ValueObservation.tracking(_:fetch:)` method can be fed with requests, and generally speaking, values that adopt the [DatabaseRegionConvertible] protocol. - -Thanks to DatabaseRegionConvertible, `TeamInfoRequest` below is not only able to fetch a team and its players, but also to be observed. - -```swift -/// A team and its players -struct TeamInfo { - var team: Team - var players: [Player] -} - -/// A request that can fetch TeamInfo, given a team id. -struct TeamInfoRequest { - var teamId: Int64 - - /// The request for the team - private var teamRequest: QueryInterfaceRequest { - return Team.filter(key: teamId) - } - - /// The request for the players - private var playersRequest: QueryInterfaceRequest { - return Player.filter(Column("teamId") == teamId) - } - - /// Fetch a TeamInfo - func fetch(_ db: Database) throws -> TeamInfo? { - guard let team = try teamRequest.fetchOne(db) else { - return nil - } - let players = try playersRequest.fetchAll(db) - return TeamInfo(team: team, players: players) - } -} - -/// Make TeamInfoRequest observable -extension TeamInfoRequest: DatabaseRegionConvertible { - func databaseRegion(_ db: Database) throws -> DatabaseRegion { - // Returns the union of the team region and the players region - let teamRegion = try teamRequest.databaseRegion(db) - let playersRegion = try playersRequest.databaseRegion(db) - return teamRegion.union(playersRegion) - } -} - -let request = TeamInfoRequest(teamId: 1) - -// Simple fetch -let teamInfo: TeamInfo? = try dbQueue.read(request.fetch) - -// Observation -let observer = ValueObservation - .tracking(request, fetch: request.fetch) - .start(in: dbQueue) { (teamInfo: TeamInfo?) in - print("Team and its players have hanged.") - } -``` - - -### ValueObservation Transformations - -- [ValueObservation.map](#valueobservationmap) -- [ValueObservation.compactMap](#valueobservationcompactmap) -- [ValueObservation.distinctUntilChanged](#valueobservationdistinctuntilchanged) -- [ValueObservation.combine(...)](#valueobservationcombine) - - -#### ValueObservation.map - -The `map` method lets you transform the values notified by a ValueObservation. - -For example: - -```swift -// Observe a player's profile image -let observation = ValueObservation - .trackingOne(Player.filter(key: 42)) - .map { player in player?.loadBigProfileImage() } - -let observer = observation.start(in: dbQueue) { (image: UIImage?) in - print("Player picture has changed") -} -``` - -The transformation closure does not run on the main queue, and is suitable for heavy computations. - - -#### ValueObservation.compactMap - -The `compactMap` method lets you transform and filter the values notified by a ValueObservation. Only non-nil transformed values are notified. - -For example: - -```swift -// Observe a player -let observation = ValueObservation - .trackingOne(Player.filter(key: 42)) - .compactMap { $0 } - -let observer = observation.start(in: dbQueue) { (player: Player) in - print("Player name: \(player.name)") -} -``` - -The transformation closure does not run on the main queue, and is suitable for heavy computations. - - -#### ValueObservation.distinctUntilChanged - -The `distinctUntilChanged` method filters out the consecutive equal values notified by a ValueObservation. The observed values must adopt the standard Equatable protocol. - -For example: - -```swift -let observation = ValueObservation - .trackingOne(Player.filter(key: 42)) - .map { player in player != nil } // existence test - .distinctUntilChanged() - -let observer = observation.start(in: dbQueue) { (exists: Bool) in - if exists { - print("Player 42 exists.") - } else { - print("Player 42 does not exist.") - } -} -``` - -> :point_up: **Note**: the observations returned by the [ValueObservation.trackingCount, trackingOne, and trackingAll](#valueobservationtrackingcount-trackingone-trackingall) methods already perform a similar filtering, based on raw database values. - - -#### ValueObservation.combine(...) - -Sometimes you need to observe several requests at the same time. For example, you need to observe changes in both a team and its players. - -When this happens, **combine** several observations together with the `ValueObservation.combine(...)` method: - -```swift -// The two observed requests (the team and its players) -let teamRequest = Team.filter(key: 1) -let playersRequest = Player.filter(Column("teamId") == 1) - -// Two observations -let teamObservation = ValueObservation.trackingOne(teamRequest) -let playersObservation = ValueObservation.trackingAll(playersRequest) - -// The combined observation -let observation = ValueObservation.combine(teamObservation, playersObservation) - -// Start tracking players and teams -let observer = observation.start(in: dbQueue) { (team: Team?, players: [Player]) in - print("Team or players have changed.") -} -``` - -Combining observations provides the guarantee that notified values are [**consistent**](https://en.wikipedia.org/wiki/Consistency_(database_systems)). - -> :point_up: **Note**: you can combine up to five observations together. Please submit a pull request if you need more. -> -> :point_up: **Note**: readers who are familiar with Reactive Programming will recognize the [CombineLatest](http://reactivex.io/documentation/operators/combinelatest.html) operator in the `ValueObservation.combine` method. The reactive operator does not care about data consistency, though: if you use a Reactive layer such as [RxGRDB], compose observations with `ValueObservation.combine`, not with the CombineLatest operator. - - -### ValueObservation Error Handling - -When you start an observation, you can provide an `onError` callback. This callback is called whenever an error happens when a fresh value is fetched after a database change. It is scheduled just like values (see [ValueObservation.scheduling](#valueobservationscheduling)): - -```swift -let observer = try observation.start( - in: dbQueue, - onError: { error in - print("fresh value could not be fetched") - }, - onChange: { value in - print("fresh value: \(value)") - }) -``` - - -### ValueObservation Options - -Some behaviors of value observations can be configured: - -- [ValueObservation.scheduling](#valueobservationscheduling): Control the dispatching of notified values. -- [ValueObservation.requiresWriteAccess](#valueobservationrequireswriteaccess): Allow observations to write in the database. - - -#### ValueObservation.scheduling - -The `scheduling` property lets you control how fresh values are notified: - -- `.mainQueue` (the default): all values are notified on the main queue. - - If the observation starts on the main queue, an initial value is notified right upon subscription, synchronously: - - ```swift - // On main queue - let observer = ValueObservation - .trackingAll(Player.all()) - .start(in: dbQueue) { (players: [Player]) in - // On main queue - print("fresh players: \(players)") - } - // <- here "fresh players" is already printed. - ``` - - If the observation does not start on the main queue, an initial value is also notified on the main queue, but asynchronously: - - ```swift - // Not on the main queue - let observer = ValueObservation - .trackingAll(Player.all()) - .start(in: dbQueue) { (players: [Player]) in - // On main queue - print("fresh players: \(players)") - } - ``` - - When the database changes, fresh values are asynchronously notified: - - ```swift - // Eventually prints "fresh players" on the main queue - try dbQueue.write { db in - try Player(...).insert(db) - } - ``` - -- `.async(onQueue:startImmediately:)`: all values are asychronously notified on the specified queue. - - An initial value is fetched and notified if `startImmediately` is true. - - For example: - - ```swift - // On main queue - var observation = ValueObservation.trackingAll(Player.all()) - observation.scheduling = .async(onQueue: .main, startImmediately: true) - let observer = try observation.start(in: dbQueue) { (players: [Player]) in - // On main queue - print("fresh players: \(players)")s - } - // <- here "fresh players" is not printed yet. - ``` - -- `unsafe(startImmediately:)`: values are not all notified on the same dispatch queue. - - If `startImmediately` is true, an initial value is notified right upon subscription, synchronously, on the dispatch queue which starts the observation. - - ```swift - // On any queue - var observation = ValueObservation.trackingAll(Player.all()) - observation.scheduling = .unsafe(startImmediately: true) - let observer = try observation.start(in: dbQueue) { (players: [Player]) in - print("fresh players: \(players)") - } - // <- here "fresh players" is already printed. - ``` - - When the database changes, other values are notified on unspecified queues. - - > :point_up: **Note**: this unsafe mode is intended for third-party libraries that provide their own scheduling engine. - - -#### ValueObservation.requiresWriteAccess - -The `requiresWriteAccess` property is false by default. When true, a ValueObservation has a write access to the database, and its fetches are automatically wrapped in a [savepoint](#transactions-and-savepoints): - -```swift -var observation = ValueObservation.tracking(..., fetch: { db in - // write access allowed -}) -observation.requiresWriteAccess = true -``` - -When you use a [database pool](#database-pools), don't use this flag unless you really need it. Observations with write access are less efficient because they block all writes for the whole duration of a fetch. - - -### Advanced: ValueObservation.tracking(_:reducer:) - -The most low-level way to define a ValueObservation is to create one from an observed database region (see above), and a **reducer** that adopts the **ValueReducer** protocol ([**:fire: EXPERIMENTAL**](#what-are-experimental-features)): - -```swift -protocol ValueReducer { - associatedtype Fetched - associatedtype Value - - /// Fetches a database value - func fetch(_ db: Database) throws -> Fetched - - /// Returns a notified value - mutating func value(_ fetched: Fetched) -> Value? -} -``` - -The `fetch` method is called upon changes in the observed [database region](#databaseregion). It runs inside a protected dispatch queue and is guaranteed an immutable view of the last committed state of the database. - -The `value` method transforms a fetched value into a notified value. It returns nil if the observer should not be notified. It runs inside a dispatch queue called the "reduce queue", which is not the main queue, and not a database queue. - -The sample code below counts the number of times the player table is modified: - -```swift -var count = 0 -let reducer = AnyValueReducer( - fetch: { _ in /* don't fetch anything */ }, - value: { _ -> Int? in - defer { count += 1 } - return count }) -let observation = ValueObservation.tracking(Player.all(), reducer: { _ in reducer }) -let observer = observation.start(in: dbQueue) { (count: Int) in - print("Number of transactions that have modified players: \(count)") -} -// Prints "Number of transactions that have modified players: 0" - -try dbQueue.write { db in - try Player(...).insert(db) -} -// Prints "Number of transactions that have modified players: 1" -``` - - -## DatabaseRegionObservation - -**DatabaseRegionObservation tracks changes in database [requests](#requests), and notifies each impactful [transaction](#transactions-and-savepoints).** - -No insertion, update, or deletion in the tracked tables is missed. This includes indirect changes triggered by [foreign keys](https://www.sqlite.org/foreignkeys.html#fk_actions) or [SQL triggers](https://www.sqlite.org/lang_createtrigger.html). - -DatabaseRegionObservation calls your application right after changes have been committed in the database, and before any other thread had any opportunity to perform further changes. *This is a pretty strong guarantee, that most applications do not really need.* Instead, most applications prefer to be notified with fresh values: make sure you check [ValueObservation] before using DatabaseRegionObservation. - - -### DatabaseRegionObservation Usage - -Define an observation by providing one or several requests to track: - -```swift -// Track all players -let observation = DatabaseRegionObservation(tracking: Player.all()) -``` - -Then start the observation from a [database queue](#database-queues) or [pool](#database-pools): - -```swift -let observer = observation.start(in: dbQueue) { (db: Database) in - print("Players were changed") -} -``` - -And enjoy the changes notifications: - -```swift -try dbQueue.write { db in - try Player(name: "Arthur").insert(db) -} -// Prints "Players were changed" -``` - -By default, the observation lasts until the observer returned by the `start` method is deallocated. See [DatabaseRegionObservation.extent](#databaseregionobservationextent) for more details. - -You can also feed DatabaseRegionObservation with [DatabaseRegion], or any type which conforms to the [DatabaseRegionConvertible] protocol. For example: - -```swift -// Observe the full database -let observation = DatabaseRegionObservation(tracking: DatabaseRegion.fullDatabase) -let observer = observation.start(in: dbQueue) { (db: Database) in - print("Database was changed") -} -``` - - -### DatabaseRegionObservation Use Cases - -**There are very few use cases for DatabaseRegionObservation**. - -For example: - -- One needs to write in the database after an impactful transaction. - -- One needs to synchronize the content of the database file with some external resources, like other files, or system sensors like CLRegion monitoring. - -- On iOS, one needs to process a database transaction before the operating system had any opportunity to put the application in the suspended state. - -- One want to build a [database snapshot](#database-snapshots) with a guaranteed snapshot content. - -Outside of those use cases, it is much likely *wrong* to use a DatabaseRegionObservation. Please check other [Database Observation](#database-changes-observation) options. - - -### DatabaseRegionObservation.extent - -The `extent` property lets you specify the duration of the observation. See [Observation Extent](#observation-extent) for more details: - -```swift -// This observation lasts until the database connection is closed -var observation = DatabaseRegionObservation... -observation.extent = .databaseLifetime -_ = observation.start(in: dbQueue) { db in ... } -``` - -The default extent is `.observerLifetime`: the observation stops when the observer returned by `start` is deallocated. - -Regardless of the extent of an observation, you can always stop observation with the `remove(transactionObserver:)` method: - -```swift -// Start -let observer = observation.start(in: dbQueue) { db in ... } - -// Stop -dbQueue.remove(transactionObserver: observer) -``` - - -## FetchedRecordsController - -**FetchedRecordsController tracks changes in the results of a request, feeds table views and collection views, and animates cells when the results of the request change.** - -It looks and behaves very much like [Core Data's NSFetchedResultsController](https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/). - -Given a fetch request, and a type that adopts the [FetchableRecord] protocol, such as a subclass of the [Record](#record-class) class, a FetchedRecordsController is able to track changes in the results of the fetch request, notify of those changes, and return the results of the request in a form that is suitable for a table view or a collection view, with one cell per fetched record. - -> :point_up: **Note**: when you don't need to animate a table or a collection view, use [ValueObservation] or [RxGRDB] instead. -> -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample app that uses FetchedRecordsController. - -- [Creating the Fetched Records Controller](#creating-the-fetched-records-controller) -- [Responding to Changes](#responding-to-changes) -- [The Changes Notifications](#the-changes-notifications) -- [Modifying the Fetch Request](#modifying-the-fetch-request) -- [Table and Collection Views](#table-and-collection-views) - - [Implementing the Table View Datasource Methods](#implementing-the-table-view-datasource-methods) - - [Implementing Table View Updates](#implementing-table-view-updates) -- [FetchedRecordsController Concurrency](#fetchedrecordscontroller-concurrency) - - -### Creating the Fetched Records Controller - -When you initialize a fetched records controller, you provide the following mandatory information: - -- A [database connection](#database-connections) -- The type of the fetched records. It must be a type that adopts the [FetchableRecord] protocol, such as a subclass of the [Record](#record-class) class -- A fetch request - -```swift -class Player : Record { ... } -let dbQueue = DatabaseQueue(...) // or DatabasePool - -// Using a Request from the Query Interface: -let controller = FetchedRecordsController( - dbQueue, - request: Player.order(Column("name"))) - -// Using SQL, and eventual arguments: -let controller = FetchedRecordsController( - dbQueue, - sql: "SELECT * FROM player ORDER BY name WHERE countryCode = ?", - arguments: ["FR"]) -``` - -The fetch request can involve several database tables. The fetched records controller will only track changes in the columns and tables used by the fetch request. - -```swift -let controller = FetchedRecordsController( - dbQueue, - sql: """ - SELECT author.name, COUNT(book.id) AS bookCount - FROM author - LEFT JOIN book ON book.authorId = author.id - GROUP BY author.id - ORDER BY author.name - """) -``` - - -After creating an instance, you invoke `performFetch()` to actually execute -the fetch. - -```swift -try controller.performFetch() -``` - - -### Responding to Changes - -In general, FetchedRecordsController is designed to respond to changes at *the database layer*, by [notifying](#the-changes-notifications) when *database rows* change location or values. - -Changes are not reflected until they are applied in the database by a successful [transaction](#transactions-and-savepoints): - -```swift -// One transaction -try dbQueue.write { db in // or dbPool.write - try player1.insert(db) - try player2.insert(db) -} - -// One transaction -try dbQueue.inTransaction { db in // or dbPool.writeInTransaction - try player1.insert(db) - try player2.insert(db) - return .commit -} - -// Two transactions -try dbQueue.inDatabase { db in // or dbPool.writeWithoutTransaction - try player1.insert(db) - try player2.insert(db) -} -``` - -When you apply several changes to the database, you should group them in a single explicit transaction. The controller will then notify of all changes together. - - -### The Changes Notifications - -An instance of FetchedRecordsController notifies that the controller’s fetched records have been changed by the mean of *callbacks*: - -```swift -let controller = try FetchedRecordsController(...) - -controller.trackChanges( - // controller's records are about to change: - willChange: { controller in ... }, - - // notification of individual record changes: - onChange: { (controller, record, change) in ... }, - - // controller's records have changed: - didChange: { controller in ... }) - -try controller.performFetch() -``` - -See [Implementing Table View Updates](#implementing-table-view-updates) for more detail on table view updates. - -**All callbacks are optional.** When you only need to grab the latest results, you can omit the `didChange` argument name: - -```swift -controller.trackChanges { controller in - let newPlayers = controller.fetchedRecords // [Player] -} -``` - -> :warning: **Warning**: notification of individual record changes (the `onChange` callback) has FetchedRecordsController use a diffing algorithm that has a high complexity, a high memory consumption, and is thus **not suited for large result sets**. One hundred rows is probably OK, but one thousand is probably not. If your application experiences problems with large lists, see [Issue 263](https://github.com/groue/GRDB.swift/issues/263) for more information. - -Callbacks have the fetched record controller itself as an argument: use it in order to avoid memory leaks: - -```swift -// BAD: memory leak -controller.trackChanges { _ in - let newPlayers = controller.fetchedRecords -} - -// GOOD -controller.trackChanges { controller in - let newPlayers = controller.fetchedRecords -} -``` - -**Callbacks are invoked asynchronously.** See [FetchedRecordsController Concurrency](#fetchedrecordscontroller-concurrency) for more information. - -**Values fetched from inside callbacks may be inconsistent with the controller's records.** This is because after database has changed, and before the controller had the opportunity to invoke callbacks in the main thread, other database changes can happen. - -To avoid inconsistencies, provide a `fetchAlongside` argument to the `trackChanges` method, as below: - -```swift -controller.trackChanges( - fetchAlongside: { db in - // Fetch any extra value, for example the number of fetched records: - return try Player.fetchCount(db) - }, - didChange: { (controller, count) in - // The extra value is the second argument. - let recordsCount = controller.fetchedRecords.count - assert(count == recordsCount) // guaranteed - }) -``` - -Whenever the fetched records controller can not look for changes after a transaction has potentially modified the tracked request, an error handler is called. The request observation is not stopped, though: future transactions may successfully be handled, and the notified changes will then be based on the last successful fetch. - -```swift -controller.trackErrors { (controller, error) in - print("Missed a transaction because \(error)") -} -``` - - -### Modifying the Fetch Request - -You can change a fetched records controller's fetch request or SQL query. - -```swift -controller.setRequest(Player.order(Column("name"))) -controller.setRequest(sql: "SELECT ...", arguments: ...) -``` - -The [notification callbacks](#the-changes-notifications) are notified of eventual changes if the new request fetches a different set of records. - -> :point_up: **Note**: This behavior differs from Core Data's NSFetchedResultsController, which does not notify of record changes when the fetch request is replaced. - -**Change callbacks are invoked asynchronously.** This means that modifying the request from the main thread does *not* immediately triggers callbacks. When you need to take immediate action, force the controller to refresh immediately with its `performFetch` method. In this case, changes callbacks are *not* called: - -```swift -// Change request on the main thread: -controller.setRequest(Player.order(Column("name"))) -// Here callbacks have not been called yet. -// You can cancel them, and refresh records immediately: -try controller.performFetch() -``` - -### Table and Collection Views - -FetchedRecordsController let you feed table and collection views, and keep them up-to-date with the database content. - -For nice animated updates, a fetched records controller needs to recognize identical records between two different result sets. When records adopt the [TableRecord] protocol, they are automatically compared according to their primary key: - -```swift -class Player : TableRecord { ... } -let controller = FetchedRecordsController( - dbQueue, - request: Player.all()) -``` - -For other types, the fetched records controller needs you to be more explicit: - -```swift -let controller = FetchedRecordsController( - dbQueue, - request: ..., - isSameRecord: { (player1, player2) in player1.id == player2.id }) -``` - - -#### Implementing the Table View Datasource Methods - -The table view data source asks the fetched records controller to provide relevant information: - -```swift -func numberOfSections(in tableView: UITableView) -> Int { - return fetchedRecordsController.sections.count -} - -func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fetchedRecordsController.sections[section].numberOfRecords -} - -func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = ... - let record = fetchedRecordsController.record(at: indexPath) - // Configure the cell - return cell -} -``` - -> :point_up: **Note**: In its current state, FetchedRecordsController does not support grouping table view rows into custom sections: it generates a unique section. - - -#### Implementing Table View Updates - -When changes in the fetched records should reload the whole table view, you can simply tell so: - -```swift -controller.trackChanges { [unowned self] _ in - self.tableView.reloadData() -} -``` - -Yet, FetchedRecordsController can notify that the controller’s fetched records have been changed due to some add, remove, move, or update operations, and help applying animated changes to a UITableView. - - -##### Typical Table View Updates - -For animated table view updates, use the `willChange` and `didChange` callbacks to bracket events provided by the fetched records controller, as illustrated in the following example: - -```swift -// Assume self has a tableView property, and a cell configuration -// method named configure(_:at:). - -controller.trackChanges( - // controller's records are about to change: - willChange: { [unowned self] _ in - self.tableView.beginUpdates() - }, - - // notification of individual record changes: - onChange: { [unowned self] (controller, record, change) in - switch change { - case .insertion(let indexPath): - self.tableView.insertRows(at: [indexPath], with: .fade) - - case .deletion(let indexPath): - self.tableView.deleteRows(at: [indexPath], with: .fade) - - case .update(let indexPath, _): - if let cell = self.tableView.cellForRow(at: indexPath) { - self.configure(cell, at: indexPath) - } - - case .move(let indexPath, let newIndexPath, _): - self.tableView.deleteRows(at: [indexPath], with: .fade) - self.tableView.insertRows(at: [newIndexPath], with: .fade) - - // // Alternate technique which actually moves cells around: - // let cell = self.tableView.cellForRow(at: indexPath) - // self.tableView.moveRow(at: indexPath, to: newIndexPath) - // if let cell = cell { - // self.configure(cell, at: newIndexPath) - // } - } - }, - - // controller's records have changed: - didChange: { [unowned self] _ in - self.tableView.endUpdates() - }) -``` - -> :warning: **Warning**: notification of individual record changes (the `onChange` callback) has FetchedRecordsController use a diffing algorithm that has a high complexity, a high memory consumption, and is thus **not suited for large result sets**. One hundred rows is probably OK, but one thousand is probably not. If your application experiences problems with large lists, see [Issue 263](https://github.com/groue/GRDB.swift/issues/263) for more information. -> -> :point_up: **Note**: our sample code above uses `unowned` references to the table view controller. This is a safe pattern as long as the table view controller owns the fetched records controller, and is deallocated from the main thread (this is usually the case). In other situations, prefer weak references. -> -> :bulb: **Tip**: see the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample app that uses FetchedRecordsController to animate a table view. - - -### FetchedRecordsController Concurrency - -**A fetched records controller *can not* be used from any thread.** - -When the database itself can be read and modified from [any thread](#database-connections), fetched records controllers **must** be used from the main thread. Record changes are also [notified](#the-changes-notifications) on the main thread. - -**Change callbacks are invoked asynchronously.** This means that changes made from the main thread are *not* immediately notified. When you need to take immediate action, force the controller to refresh immediately with its `performFetch` method. In this case, changes callbacks are *not* called: - -```swift -// Change database on the main thread: -try dbQueue.write { db in - try Player(...).insert(db) -} -// Here callbacks have not been called yet. -// You can cancel them, and refresh records immediately: -try controller.performFetch() -``` - -> :point_up: **Note**: when the main thread does not fit your needs, give a serial dispatch queue to the controller initializer: the controller must then be used from this queue, and record changes are notified on this queue as well. -> -> ```swift -> let queue = DispatchQueue() -> queue.async { -> let controller = try FetchedRecordsController(..., queue: queue) -> controller.trackChanges { /* in queue */ } -> try controller.performFetch() -> } -> ``` - - -## TransactionObserver Protocol - -The `TransactionObserver` protocol lets you **observe individual database changes and transactions**: - -```swift -protocol TransactionObserver : class { - /// Notifies a database change: - /// - event.kind (insert, update, or delete) - /// - event.tableName - /// - event.rowID - /// - /// For performance reasons, the event is only valid for the duration of - /// this method call. If you need to keep it longer, store a copy: - /// event.copy(). - func databaseDidChange(with event: DatabaseEvent) - - /// Filters the database changes that should be notified to the - /// `databaseDidChange(with:)` method. - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool - - /// An opportunity to rollback pending changes by throwing an error. - func databaseWillCommit() throws - - /// Database changes have been committed. - func databaseDidCommit(_ db: Database) - - /// Database changes have been rollbacked. - func databaseDidRollback(_ db: Database) -} -``` - -- [Activate a Transaction Observer](#activate-a-transaction-observer) -- [Database Changes And Transactions](#database-changes-and-transactions) -- [Filtering Database Events](#filtering-database-events) -- [Observation Extent](#observation-extent) -- [DatabaseRegion] -- [Support for SQLite Pre-Update Hooks](#support-for-sqlite-pre-update-hooks) - - -### Activate a Transaction Observer - -**To activate a transaction observer, add it to the database queue or pool:** - -```swift -let observer = MyObserver() -dbQueue.add(transactionObserver: observer) -``` - -By default, database holds weak references to its transaction observers: they are not retained, and stop getting notifications after they are deallocated. See [Observation Extent](#observation-extent) for more options. - - -### Database Changes And Transactions - -**A transaction observer is notified of all database changes**: inserts, updates and deletes. This includes indirect changes triggered by ON DELETE and ON UPDATE actions associated to [foreign keys](https://www.sqlite.org/foreignkeys.html#fk_actions), and [SQL triggers](https://www.sqlite.org/lang_createtrigger.html). - -> :point_up: **Note**: the changes that are not notified are changes to internal system tables (such as `sqlite_master`), changes to [`WITHOUT ROWID`](https://www.sqlite.org/withoutrowid.html) tables, and the deletion of duplicate rows triggered by [`ON CONFLICT REPLACE`](https://www.sqlite.org/lang_conflict.html) clauses (this last exception might change in a future release of SQLite). - -Notified changes are not actually written to disk until the [transaction](#transactions-and-savepoints) commits, and the `databaseDidCommit` callback is called. On the other side, `databaseDidRollback` confirms their invalidation: - -```swift -try dbQueue.write { db in - try db.execute(sql: "INSERT ...") // 1. didChange - try db.execute(sql: "UPDATE ...") // 2. didChange -} // 3. willCommit, 4. didCommit - -try dbQueue.inTransaction { db in - try db.execute(sql: "INSERT ...") // 1. didChange - try db.execute(sql: "UPDATE ...") // 2. didChange - return .rollback // 3. didRollback -} - -try dbQueue.write { db in - try db.execute(sql: "INSERT ...") // 1. didChange - throw SomeError() -} // 2. didRollback -``` - -Database statements that are executed outside of any transaction do not drop off the radar: - -```swift -try dbQueue.inDatabase { db in - try db.execute(sql: "INSERT ...") // 1. didChange, 2. willCommit, 3. didCommit - try db.execute(sql: "UPDATE ...") // 4. didChange, 5. willCommit, 6. didCommit -} -``` - -Changes that are on hold because of a [savepoint](https://www.sqlite.org/lang_savepoint.html) are only notified after the savepoint has been released. This makes sure that notified events are only events that have an opportunity to be committed: - -```swift -try dbQueue.inTransaction { db in - try db.execute(sql: "INSERT ...") // 1. didChange - - try db.execute(sql: "SAVEPOINT foo") - try db.execute(sql: "UPDATE ...") // delayed - try db.execute(sql: "UPDATE ...") // delayed - try db.execute(sql: "RELEASE SAVEPOINT foo") // 2. didChange, 3. didChange - - try db.execute(sql: "SAVEPOINT foo") - try db.execute(sql: "UPDATE ...") // not notified - try db.execute(sql: "ROLLBACK TO SAVEPOINT foo") - - return .commit // 4. willCommit, 5. didCommit -} -``` - - -**Eventual errors** thrown from `databaseWillCommit` are exposed to the application code: - -```swift -do { - try dbQueue.inTransaction { db in - ... - return .commit // 1. willCommit (throws), 2. didRollback - } -} catch { - // 3. The error thrown by the transaction observer. -} -``` - -> :point_up: **Note**: all callbacks are called in a protected dispatch queue, and serialized with all database updates. -> -> :point_up: **Note**: the databaseDidChange(with:) and databaseWillCommit() callbacks must not touch the SQLite database. This limitation does not apply to databaseDidCommit and databaseDidRollback which can use their database argument. - - -[DatabaseRegionObservation], [ValueObservation], [FetchedRecordsController], and [RxGRDB] are based on the TransactionObserver protocol. - -See also [TableChangeObserver.swift](https://gist.github.com/groue/2e21172719e634657dfd), which shows a transaction observer that notifies of modified database tables with NSNotificationCenter. - - -### Filtering Database Events - -**Transaction observers can avoid being notified of database changes they are not interested in.** - -The filtering happens in the `observes(eventsOfKind:)` method, which tells whether the observer wants notification of specific kinds of changes, or not. For example, here is how an observer can focus on the changes that happen on the "player" database table: - -```swift -class PlayerObserver: TransactionObserver { - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - // Only observe changes to the "player" table. - return eventKind.tableName == "player" - } - - func databaseDidChange(with event: DatabaseEvent) { - // This method is only called for changes that happen to - // the "player" table. - } -} -``` - -Generally speaking, the `observes(eventsOfKind:)` method can distinguish insertions from deletions and updates, and is also able to inspect the columns that are about to be changed: - -```swift -class PlayerScoreObserver: TransactionObserver { - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - // Only observe changes to the "score" column of the "player" table. - switch eventKind { - case .insert(let tableName): - return tableName == "player" - case .delete(let tableName): - return tableName == "player" - case .update(let tableName, let columnNames): - return tableName == "player" && columnNames.contains("score") - } - } -} -``` - -When the `observes(eventsOfKind:)` method returns false for all event kinds, the observer is still notified of commits and rollbacks: - -```swift -class PureTransactionObserver: TransactionObserver { - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - // Ignore all individual changes - return false - } - - func databaseDidChange(with event: DatabaseEvent) { /* Never called */ } - func databaseWillCommit() throws { /* Called before commit */ } - func databaseDidRollback(_ db: Database) { /* Called on rollback */ } - func databaseDidCommit(_ db: Database) { /* Called on commit */ } -} -``` - -For more information about event filtering, see [DatabaseRegion]. - - -### Observation Extent - -**You can specify how long an observer is notified of database changes and transactions.** - -The `remove(transactionObserver:)` method explicitly stops notifications, at any time: - -```swift -// From a database queue or pool: -dbQueue.remove(transactionObserver: observer) - -// From a database connection: -dbQueue.inDatabase { db in - db.remove(transactionObserver: observer) -} -``` - -Alternatively, use the `extent` parameter of the `add(transactionObserver:extent:)` method: - -```swift -let observer = MyObserver() - -// On a database queue or pool: -dbQueue.add(transactionObserver: observer) // default extent -dbQueue.add(transactionObserver: observer, extent: .observerLifetime) -dbQueue.add(transactionObserver: observer, extent: .nextTransaction) -dbQueue.add(transactionObserver: observer, extent: .databaseLifetime) - -// On a database connection: -dbQueue.inDatabase { db in - db.add(transactionObserver: ...) -} -``` - -- The default extent is `.observerLifetime`: the database holds a weak reference to the observer, and the observation automatically ends when the observer is deallocated. Meanwhile, observer is notified of all changes and transactions. - -- `.nextTransaction` activates the observer until the current or next transaction completes. The database keeps a strong reference to the observer until its `databaseDidCommit` or `databaseDidRollback` method is eventually called. Hereafter the observer won't get any further notification. - -- `.databaseLifetime` has the database retain and notify the observer until the database connection is closed. - -Finally, an observer may ignore all database changes until the end of the current transaction: - -```swift -class PlayerObserver: TransactionObserver { - var playerTableWasModified = false - - func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { - return eventKind.tableName == "player" - } - - func databaseDidChange(with event: DatabaseEvent) { - playerTableWasModified = true - - // It is pointless to keep on tracking further changes: - stopObservingDatabaseChangesUntilNextTransaction() - } -} -``` - -After `stopObservingDatabaseChangesUntilNextTransaction()`, the `databaseDidChange(with:)` method will not be notified of any change for the remaining duration of the current transaction. This helps GRDB optimize database observation. - - -### DatabaseRegion - -**[DatabaseRegion](https://groue.github.io/GRDB.swift/docs/4.0/Structs/DatabaseRegion.html) is a type that helps observing changes in the results of a database [request](#requests)**. - -A request knows which database modifications can impact its results. It can communicate this information to [transaction observers](#transactionobserver-protocol) by the way of a DatabaseRegion. - -DatabaseRegion fuels, for example, [ValueObservation and DatabaseRegionObservation]. - -**A region notifies *potential* changes, not *actual* changes in the results of a request.** A change is notified if and only if a statement has actually modified the tracked tables and columns by inserting, updating, or deleting a row. - -For example, if you observe the region of `Player.select(max(Column("score")))`, then you'll get be notified of all changes performed on the `score` column of the `player` table (updates, insertions and deletions), even if they do not modify the value of the maximum score. However, you will not get any notification for changes performed on other database tables, or updates to other columns of the player table. - -For more details, see the [reference](http://groue.github.io/GRDB.swift/docs/4.0/Structs/DatabaseRegion.html#/s:4GRDB14DatabaseRegionV10isModified2bySbAA0B5EventV_tF). - - -#### The DatabaseRegionConvertible Protocol - -**DatabaseRegionConvertible** is a protocol for all types that can turn into a [DatabaseRegion]: - -```swift -protocol DatabaseRegionConvertible { - func databaseRegion(_ db: Database) throws -> DatabaseRegion -} -``` - -All [requests](#requests) adopt this protocol, and this allows them to be observed with [DatabaseRegionObservation] and [ValueObservation]. - -Use this protocol when you want to encapsulate your complex requests in a dedicated type, and still profit from observation APIs. See [DatabaseRegionConvertible Observation](#databaseregionconvertible-observation) for more information. - - -### Support for SQLite Pre-Update Hooks - -A [custom SQLite build] can activate [SQLite "preupdate hooks"](https://sqlite.org/c3ref/preupdate_count.html). In this case, TransactionObserverType gets an extra callback which lets you observe individual column values in the rows modified by a transaction: - -```swift -protocol TransactionObserverType : class { - #if SQLITE_ENABLE_PREUPDATE_HOOK - /// Notifies before a database change (insert, update, or delete) - /// with change information (initial / final values for the row's - /// columns). - /// - /// The event is only valid for the duration of this method call. If you - /// need to keep it longer, store a copy: event.copy(). - func databaseWillChange(with event: DatabasePreUpdateEvent) - #endif -} -``` - - -Encryption -========== - -**GRDB can encrypt your database with [SQLCipher](http://sqlcipher.net) v3.4+.** - -Use [CocoaPods](http://cocoapods.org/), and specify in your `Podfile`: - -```ruby -# GRDB with SQLCipher 4 -pod 'GRDB.swift/SQLCipher' -pod 'SQLCipher', '~> 4.0' - -# GRDB with SQLCipher 3 -pod 'GRDB.swift/SQLCipher' -pod 'SQLCipher', '~> 3.4' -``` - -> :warning: **Warning**: SQLCipher 4 is *not compatible** with SQLCipher 3. -> -> When you want to open your existing SQLCipher 3 database with SQLCipher 4, you may want to run the `cipher_compatibility` pragma: -> -> ```swift -> // Open an SQLCipher 3 database with SQLCipher 4 -> var configuration = Configuration() -> configuration.passphrase = "..." -> configuration.prepareDatabase = { db in -> try db.execute(sql: "PRAGMA cipher_compatibility = 3") -> } -> let dbQueue = try DatabaseQueue(path: dbPath, configuration: configuration) -> ``` -> -> See [SQLCipher 4.0.0 Release](https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/) and [Upgrading to SQLCipher 4](https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283) for more information. See also [Advanced configuration options for SQLCipher](#advanced-configuration-options-for-sqlcipher) below. - -**You create and open an encrypted database** by providing a passphrase to your [database connection](#database-connections): - -```swift -var configuration = Configuration() -configuration.passphrase = "secret" -let dbQueue = try DatabaseQueue(path: "...", configuration: configuration) -``` - -**You can change the passphrase** of an already encrypted database: - -```swift -try dbQueue.change(passphrase: "newSecret") -``` - -Providing a passphrase won't encrypt a clear-text database that already exists, though. SQLCipher can't do that, and you will get an error instead: `SQLite error 26: file is encrypted or is not a database`. - -**To encrypt an existing clear-text database**, create a new and empty encrypted database, and copy the content of the clear-text database in it. The technique to do that is [documented](https://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-a-database-errors/868/1) by SQLCipher. With GRDB, it gives: - -```swift -// The clear-text database -let clearDBQueue = try DatabaseQueue(path: "/path/to/clear.db") - -// The encrypted database, at some distinct location: -var configuration = Configuration() -configuration.passphrase = "secret" -let encryptedDBQueue = try DatabaseQueue(path: "/path/to/encrypted.db", configuration: config) - -try clearDBQueue.inDatabase { db in - try db.execute(sql: "ATTACH DATABASE ? AS encrypted KEY ?", arguments: [encryptedDBQueue.path, "secret"]) - try db.execute(sql: "SELECT sqlcipher_export('encrypted')") - try db.execute(sql: "DETACH DATABASE encrypted") -} - -// Now the copy is done, and the clear-text database can be deleted. -``` - -## Advanced configuration options for SQLCipher - -Some advanced SQLCipher configuration steps must happen very early in the database lifetime, and you will have to use the `configuration.prepareDatabase` property in order to run them correctly: - -```swift -var configuration = Configuration() -configuration.passphrase = "secret" -configuration.prepareDatabase = { db in - try db.execute(sql: "PRAGMA cipher_page_size = 4096") - try db.execute(sql: "PRAGMA kdf_iter = 128000") -} -let dbQueue = try DatabaseQueue(path: "...", configuration: configuration) -``` - -See [PRAGMA cipher_page_size](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_page_size) and [PRAGMA kdf_iter](https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter) for more information. - - -## Backup - -**You can backup (copy) a database into another.** - -Backups can for example help you copying an in-memory database to and from a database file when you implement NSDocument subclasses. - -```swift -let source: DatabaseQueue = ... // or DatabasePool -let destination: DatabaseQueue = ... // or DatabasePool -try source.backup(to: destination) -``` - -The `backup` method blocks the current thread until the destination database contains the same contents as the source database. - -When the source is a [database pool](#database-pools), concurrent writes can happen during the backup. Those writes may, or may not, be reflected in the backup, but they won't trigger any error. - - -## Avoiding SQL Injection - -SQL injection is a technique that lets an attacker nuke your database. - -> ![XKCD: Exploits of a Mom](https://imgs.xkcd.com/comics/exploits_of_a_mom.png) -> -> https://xkcd.com/327/ - -Here is an example of code that is vulnerable to SQL injection: - -```swift -// BAD BAD BAD -let id = 1 -let name = textField.text -try dbQueue.write { db in - try db.execute(sql: "UPDATE students SET name = '\(name)' WHERE id = \(id)") -} -``` - -If the user enters a funny string like `Robert'; DROP TABLE students; --`, SQLite will see the following SQL, and drop your database table instead of updating a name as intended: - -```sql -UPDATE students SET name = 'Robert'; -DROP TABLE students; ---' WHERE id = 1 -``` - -To avoid those problems, **never embed raw values in your SQL queries**. The only correct technique is to provide [arguments](#executing-updates) to your raw SQL queries: - -```swift -let name = textField.text -try dbQueue.write { db in - // Good - try db.execute( - sql: "UPDATE students SET name = ? WHERE id = ?", - arguments: [name, id]) - - // Just as good - try db.execute( - sql: "UPDATE students SET name = :name WHERE id = :id", - arguments: ["name": name, "id": id]) -} -``` - -When you use [records](#records) and the [query interface](#the-query-interface), GRDB always prevents SQL injection for you: - -```swift -let id = 1 -let name = textField.text -try dbQueue.write { db in - if var student = try Student.fetchOne(db, key: id) { - student.name = name - try student.update(db) - } -} -``` - - -## Error Handling - -GRDB can throw [DatabaseError](#databaseerror), [PersistenceError](#persistenceerror), or crash your program with a [fatal error](#fatal-errors). - -Considering that a local database is not some JSON loaded from a remote server, GRDB focuses on **trusted databases**. Dealing with [untrusted databases](#how-to-deal-with-untrusted-inputs) requires extra care. - -- [DatabaseError](#databaseerror) -- [PersistenceError](#persistenceerror) -- [Fatal Errors](#fatal-errors) -- [How to Deal with Untrusted Inputs](#how-to-deal-with-untrusted-inputs) -- [Error Log](#error-log) - - -### DatabaseError - -**DatabaseError** are thrown on SQLite errors: - -```swift -do { - try db.execute( - sql: "INSERT INTO pet (masterId, name) VALUES (?, ?)", - arguments: [1, "Bobby"]) -} catch let error as DatabaseError { - // The SQLite error code: 19 (SQLITE_CONSTRAINT) - error.resultCode - - // The extended error code: 787 (SQLITE_CONSTRAINT_FOREIGNKEY) - error.extendedResultCode - - // The eventual SQLite message: FOREIGN KEY constraint failed - error.message - - // The eventual erroneous SQL query - // "INSERT INTO pet (masterId, name) VALUES (?, ?)" - error.sql - - // Full error description: - // "SQLite error 19 with statement `INSERT INTO pet (masterId, name) - // VALUES (?, ?)` arguments [1, "Bobby"]: FOREIGN KEY constraint failed"" - error.description -} -``` - -**SQLite uses codes to distinguish between various errors:** - -```swift -do { - try ... -} catch let error as DatabaseError where error.extendedResultCode == .SQLITE_CONSTRAINT_FOREIGNKEY { - // foreign key constraint error -} catch let error as DatabaseError where error.resultCode == .SQLITE_CONSTRAINT { - // any other constraint error -} catch let error as DatabaseError { - // any other database error -} -``` - -In the example above, `error.extendedResultCode` is a precise [extended result code](https://www.sqlite.org/rescode.html#extended_result_code_list), and `error.resultCode` is a less precise [primary result code](https://www.sqlite.org/rescode.html#primary_result_code_list). Extended result codes are refinements of primary result codes, as `SQLITE_CONSTRAINT_FOREIGNKEY` is to `SQLITE_CONSTRAINT`, for example. See [SQLite result codes](https://www.sqlite.org/rescode.html) for more information. - -As a convenience, extended result codes match their primary result code in a switch statement: - -```swift -do { - try ... -} catch let error as DatabaseError { - switch error.extendedResultCode { - case ResultCode.SQLITE_CONSTRAINT_FOREIGNKEY: - // foreign key constraint error - case ResultCode.SQLITE_CONSTRAINT: - // any other constraint error - default: - // any other database error - } -} -``` - -> :warning: **Warning**: SQLite has progressively introduced extended result codes accross its versions. The [SQLite release notes](http://www.sqlite.org/changes.html) are unfortunately not quite clear about that: write your handling of extended result codes with care. - - -### PersistenceError - -**PersistenceError** is thrown by the [PersistableRecord] protocol, in a single case: when the `update` method could not find any row to update: - -```swift -do { - try player.update(db) -} catch let PersistenceError.recordNotFound(databaseTableName: table, key: key) { - print("Key \(key) was not found in table \(table).") -} -``` - - -### Fatal Errors - -**Fatal errors notify that the program, or the database, has to be changed.** - -They uncover programmer errors, false assumptions, and prevent misuses. Here are a few examples: - -- **The code asks for a non-optional value, when the database contains NULL:** - - ```swift - // fatal error: could not convert NULL to String. - let name: String = row["name"] - ``` - - Solution: fix the contents of the database, use [NOT NULL constraints](#create-tables), or load an optional: - - ```swift - let name: String? = row["name"] - ``` - -- **Conversion from database value to Swift type fails:** - - ```swift - // fatal error: could not convert "Mom’s birthday" to Date. - let date: Date = row["date"] - - // fatal error: could not convert "" to URL. - let url: URL = row["url"] - ``` - - Solution: fix the contents of the database, or use [DatabaseValue](#databasevalue) to handle all possible cases: - - ```swift - let dbValue: DatabaseValue = row["date"] - if dbValue.isNull { - // Handle NULL - } else if let date = Date.fromDatabaseValue(dbValue) { - // Handle valid date - } else { - // Handle invalid date - } - ``` - -- **The database can't guarantee that the code does what it says:** - - ```swift - // fatal error: table player has no unique index on column email - try Player.deleteOne(db, key: ["email": "arthur@example.com"]) - ``` - - Solution: add a unique index to the player.email column, or use the `deleteAll` method to make it clear that you may delete more than one row: - - ```swift - try Player.filter(Column("email") == "arthur@example.com").deleteAll(db) - ``` - -- **Database connections are not reentrant:** - - ```swift - // fatal error: Database methods are not reentrant. - dbQueue.write { db in - dbQueue.write { db in - ... - } - } - ``` - - Solution: avoid reentrancy, and instead pass a database connection along. - - -### How to Deal with Untrusted Inputs - -Let's consider the code below: - -```swift -let sql = "SELECT ..." - -// Some untrusted arguments for the query -let arguments: [String: Any] = ... -let rows = try Row.fetchCursor(db, sql: sql, arguments: StatementArguments(arguments)) - -while let row = try rows.next() { - // Some untrusted database value: - let date: Date? = row[0] -} -``` - -It has two opportunities to throw fatal errors: - -- **Untrusted arguments**: The dictionary may contain values that do not conform to the [DatabaseValueConvertible protocol](#values), or may miss keys required by the statement. -- **Untrusted database content**: The row may contain a non-null value that can't be turned into a date. - -In such a situation, you can still avoid fatal errors by exposing and handling each failure point, one level down in the GRDB API: - -```swift -// Untrusted arguments -if let arguments = StatementArguments(arguments) { - let statement = try db.makeSelectStatement(sql: sql) - try statement.validate(arguments: arguments) - statement.unsafeSetArguments(arguments) - - var cursor = try Row.fetchCursor(statement) - while let row = try iterator.next() { - // Untrusted database content - let dbValue: DatabaseValue = row[0] - if dbValue.isNull { - // Handle NULL - if let date = Date.fromDatabaseValue(dbValue) { - // Handle valid date - } else { - // Handle invalid date - } - } -} -``` - -See [prepared statements](#prepared-statements) and [DatabaseValue](#databasevalue) for more information. - - -### Error Log - -**SQLite can be configured to invoke a callback function containing an error code and a terse error message whenever anomalies occur.** - -This global error callback must be configured early in the lifetime of your application: - -```swift -Database.logError = { (resultCode, message) in - NSLog("%@", "SQLite error \(resultCode): \(message)") -} -``` - -> :warning: **Warning**: Database.logError must be set before any database connection is opened. This includes the connections that your application opens with GRDB, but also connections opened by other tools, such as third-party libraries. Setting it after a connection has been opened is an SQLite misuse, and has no effect. - -See [The Error And Warning Log](https://sqlite.org/errlog.html) for more information. - - -## Unicode - -SQLite lets you store unicode strings in the database. - -However, SQLite does not provide any unicode-aware string transformations or comparisons. - - -### Unicode functions - -The `UPPER` and `LOWER` built-in SQLite functions are not unicode-aware: - -```swift -// "JéRôME" -try String.fetchOne(db, sql: "SELECT UPPER('Jérôme')") -``` - -GRDB extends SQLite with [SQL functions](#custom-sql-functions-and-aggregates) that call the Swift built-in string functions `capitalized`, `lowercased`, `uppercased`, `localizedCapitalized`, `localizedLowercased` and `localizedUppercased`: - -```swift -// "JÉRÔME" -let uppercased = DatabaseFunction.uppercase -try String.fetchOne(db, sql: "SELECT \(uppercased.name)('Jérôme')") -``` - -Those unicode-aware string functions are also readily available in the [query interface](#sql-functions): - -```swift -Player.select(nameColumn.uppercased) -``` - - -### String Comparison - -SQLite compares strings in many occasions: when you sort rows according to a string column, or when you use a comparison operator such as `=` and `<=`. - -The comparison result comes from a *collating function*, or *collation*. SQLite comes with three built-in collations that do not support Unicode: [binary, nocase, and rtrim](https://www.sqlite.org/datatype3.html#collation). - -GRDB comes with five extra collations that leverage unicode-aware comparisons based on the standard Swift String comparison functions and operators: - -- `unicodeCompare` (uses the built-in `<=` and `==` Swift operators) -- `caseInsensitiveCompare` -- `localizedCaseInsensitiveCompare` -- `localizedCompare` -- `localizedStandardCompare` - -A collation can be applied to a table column. All comparisons involving this column will then automatically trigger the comparison function: - -```swift -try db.create(table: "player") { t in - // Guarantees case-insensitive email unicity - t.column("email", .text).unique().collate(.nocase) - - // Sort names in a localized case insensitive way - t.column("name", .text).collate(.localizedCaseInsensitiveCompare) -} - -// Players are sorted in a localized case insensitive way: -let players = try Player.order(nameColumn).fetchAll(db) -``` - -> :warning: **Warning**: SQLite *requires* host applications to provide the definition of any collation other than binary, nocase and rtrim. When a database file has to be shared or migrated to another SQLite library of platform (such as the Android version of your application), make sure you provide a compatible collation. - -If you can't or don't want to define the comparison behavior of a column (see warning above), you can still use an explicit collation in SQL requests and in the [query interface](#the-query-interface): - -```swift -let collation = DatabaseCollation.localizedCaseInsensitiveCompare -let players = try Player.fetchAll(db, - sql: "SELECT * FROM player ORDER BY name COLLATE \(collation.name))") -let players = try Player.order(nameColumn.collating(collation)).fetchAll(db) -``` - - -**You can also define your own collations**: - -```swift -let collation = DatabaseCollation("customCollation") { (lhs, rhs) -> NSComparisonResult in - // return the comparison of lhs and rhs strings. -} -dbQueue.add(collation: collation) // Or dbPool.add(collation: ...) -``` - - -## Memory Management - -Both SQLite and GRDB use non-essential memory that help them perform better. - -You can reclaim this memory with the `releaseMemory` method: - -```swift -// Release as much memory as possible. -dbQueue.releaseMemory() -dbPool.releaseMemory() -``` - -This method blocks the current thread until all current database accesses are completed, and the memory collected. - - -### Memory Management on iOS - -**The iOS operating system likes applications that do not consume much memory.** - -[Database queues](#database-queues) and [pools](#database-pools) can call the `releaseMemory` method for you, when application receives memory warnings, and when application enters background: call the `setupMemoryManagement` method after creating the queue or pool instance: - -``` -let dbQueue = try DatabaseQueue(...) -dbQueue.setupMemoryManagement(in: UIApplication.shared) -``` - - -## Data Protection - -[Data Protection](https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StrategiesforImplementingYourApp/StrategiesforImplementingYourApp.html#//apple_ref/doc/uid/TP40007072-CH5-SW21) lets you protect files so that they are encrypted and unavailable until the device is unlocked. - -Data protection can be enabled [globally](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW30) for all files created by an application. - -You can also explicitly protect a database, by configuring its enclosing *directory*. This will not only protect the database file, but also all [temporary files](https://www.sqlite.org/tempfiles.html) created by SQLite (including the persistent `.shm` and `.wal` files created by [database pools](#database-pools)). - -For example, to explicitly use [complete](https://developer.apple.com/reference/foundation/fileprotectiontype/1616200-complete) protection: - -```swift -// Paths -let fileManager = FileManager.default -let directoryURL = try fileManager - .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - .appendingPathComponent("database", isDirectory: true) -let databaseURL = directoryURL.appendingPathComponent("db.sqlite") - -// Create directory if needed -var isDirectory: ObjCBool = false -if !fileManager.fileExists(atPath: directoryURL.path, isDirectory: &isDirectory) { - try fileManager.createDirectory(atPath: directoryURL.path, withIntermediateDirectories: false) -} else if !isDirectory.boolValue { - throw NSError(domain: NSCocoaErrorDomain, code: NSFileWriteFileExistsError, userInfo: nil) -} - -// Enable data protection -try fileManager.setAttributes([.protectionKey : FileProtectionType.complete], ofItemAtPath: directoryURL.path) - -// Open database -let dbQueue = try DatabaseQueue(path: databaseURL.path) -``` - -When a database is protected, an application that runs in the background on a locked device won't be able to read or write from it. Instead, it will get [DatabaseError](#error-handling) with code [`SQLITE_IOERR`](https://www.sqlite.org/rescode.html#ioerr) (10) "disk I/O error", or [`SQLITE_AUTH`](https://www.sqlite.org/rescode.html#auth) (23) "not authorized". - -You can catch those errors and wait for [UIApplicationDelegate.applicationProtectedDataDidBecomeAvailable(_:)](https://developer.apple.com/reference/uikit/uiapplicationdelegate/1623044-applicationprotecteddatadidbecom) or [UIApplicationProtectedDataDidBecomeAvailable](https://developer.apple.com/reference/uikit/uiapplicationprotecteddatadidbecomeavailable) notification in order to retry the failed database operation. - - -## Concurrency - -- [Guarantees and Rules](#guarantees-and-rules) -- [Differences between Database Queues and Pools](#differences-between-database-queues-and-pools) -- [Advanced DatabasePool](#advanced-databasepool) -- [Database Snapshots](#database-snapshots) -- [DatabaseWriter and DatabaseReader Protocols](#databasewriter-and-databasereader-protocols) -- [Unsafe Concurrency APIs](#unsafe-concurrency-apis) -- [Dealing with External Connections](#dealing-with-external-connections) - - -### Guarantees and Rules - -GRDB ships with three concurrency modes: - -- [DatabaseQueue](#database-queues) opens a single database connection, and serializes all database accesses. -- [DatabasePool](#database-pools) manages a pool of several database connections, and allows concurrent reads and writes. -- [DatabaseSnapshot](#database-snapshots) opens a single read-only database connection on an unchanging database content, and (currently) serializes all database accesses - -**All foster application safety**: regardless of the concurrency mode you choose, GRDB provides you with the same guarantees, as long as you follow three rules. - -- :bowtie: **Guarantee 1: writes are always serialized**. At every moment, there is no more than a single thread that is writing into the database. - - > Database writes always happen in a unique serial dispatch queue, named the *writer protected dispatch queue*. - -- :bowtie: **Guarantee 2: reads are always isolated**. This means that they are guaranteed an immutable view of the last committed state of the database, and that you can perform subsequent fetches without fearing eventual concurrent writes to mess with your application logic: - - ```swift - try dbPool.read { db in // or dbQueue.read - // Guaranteed to be equal - let count1 = try Player.fetchCount(db) - let count2 = try Player.fetchCount(db) - } - ``` - - > In [database queues](#database-queues), reads happen in the same protected dispatch queue as writes: isolation is just a consequence of the serialization of database accesses - > - > [Database pools](#database-pools) and [snapshots](#database-snapshots) both use the "snapshot isolation" made possible by SQLite's WAL mode (see [Isolation In SQLite](https://sqlite.org/isolation.html)). - -- :bowtie: **Guarantee 3: requests don't fail**, unless a database constraint violation, a [programmer mistake](#error-handling), or a very low-level issue such as a disk error or an unreadable database file. GRDB grants *correct* use of SQLite, and particularly avoids locking errors and other SQLite misuses. - -Those guarantees hold as long as you follow three rules: - -- :point_up: **Rule 1**: Have a unique instance of DatabaseQueue or DatabasePool connected to any database file. - - This means that opening a new connection each time you access the database is a bad idea. Do share a single connection instead. - - See the [Demo Application](DemoApps/GRDBDemoiOS/README.md) for a sample app that sets up a single database queue that is available throughout the application. - - If there are several instances of database queues or pools that write in the same database, a multi-threaded application will eventually face "database is locked" errors. See [Dealing with External Connections](#dealing-with-external-connections). - - ```swift - // SAFE CONCURRENCY - func fetchCurrentUser(_ db: Database) throws -> User? { - return try User.fetchOne(db) - } - // dbQueue is a singleton defined somewhere in your app - let user = try dbQueue.read { db in // or dbPool.read - try fetchCurrentUser(db) - } - - // UNSAFE CONCURRENCY - // This method fails when some other thread is currently writing into - // the database. - func currentUser() throws -> User? { - let dbQueue = try DatabaseQueue(...) - return try dbQueue.read { db in - try User.fetchOne(db) - } - } - let user = try currentUser() - ``` - -- :point_up: **Rule 2**: Group related statements within a single call to a DatabaseQueue or DatabasePool database access method (or use [snapshots](#database-snapshots)). - - Database access methods isolate your groups of related statements against eventual database updates performed by other threads, and guarantee a consistent view of the database. This isolation is only guaranteed *inside* the closure argument of those methods. Two consecutive calls *do not* guarantee isolation: - - ```swift - // SAFE CONCURRENCY - try dbPool.read { db in // or dbQueue.read - // Guaranteed to be equal: - let count1 = try Place.fetchCount(db) - let count2 = try Place.fetchCount(db) - } - - // UNSAFE CONCURRENCY - // Those two values may be different because some other thread may have - // modified the database between the two blocks: - let count1 = try dbPool.read { db in try Place.fetchCount(db) } - let count2 = try dbPool.read { db in try Place.fetchCount(db) } - ``` - - In the same vein, when you fetch values that depends on some database updates, group them: - - ```swift - // SAFE CONCURRENCY - try dbPool.write { db in - // The count is guaranteed to be non-zero - try Place(...).insert(db) - let count = try Place.fetchCount(db) - } - - // UNSAFE CONCURRENCY - // The count may be zero because some other thread may have performed - // a deletion between the two blocks: - try dbPool.write { db in try Place(...).insert(db) } - let count = try dbPool.read { db in try Place.fetchCount(db) } - ``` - - On that last example, see [Advanced DatabasePool](#advanced-databasepool) if you look after extra performance. - -- :point_up: **Rule 3**: When you perform several modifications of the database that temporarily put the database in an inconsistent state, make sure those modifications are grouped within a [transaction](#transactions-and-savepoints). - - ```swift - // SAFE CONCURRENCY - try dbPool.write { db in // or dbQueue.write - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) - } - - // SAFE CONCURRENCY - try dbPool.writeInTransaction { db in // or dbQueue.inTransaction - try Credit(destinationAccout, amount).insert(db) - try Debit(sourceAccount, amount).insert(db) - return .commit - } - - // UNSAFE CONCURRENCY - try dbPool.writeWithoutTransaction { db in - try Credit(destinationAccout, amount).insert(db) - // <- Concurrent dbPool.read sees a partial db update here - try Debit(sourceAccount, amount).insert(db) - } - ``` - - Without transaction, `DatabasePool.read { ... }` may see the first statement, but not the second, and access a database where the balance of accounts is not zero. A highly bug-prone situation. - - So do use [transactions](#transactions-and-savepoints) in order to guarantee database consistency accross your application threads: that's what they are made for. - - -### Differences between Database Queues and Pools - -Despite the common [guarantees and rules](#guarantees-and-rules) shared by [database queues](#database-queues) and [pools](#database-pools), those two database accessors don't have the same behavior. - -**Database queues** serialize all database accesses, reads, and writes. There is never more than one thread that uses the database. In the image below, we see how three threads can see the database as time passes: - -![DatabaseQueueScheduling](https://cdn.rawgit.com/groue/GRDB.swift/master/Documentation/Images/DatabaseQueueScheduling.svg) - -**Database pools** also serialize all writes. But they allow concurrent reads and writes, and isolate reads so that they don't see changes performed by other threads. This gives a very different picture: - -![DatabasePoolScheduling](https://cdn.rawgit.com/groue/GRDB.swift/master/Documentation/Images/DatabasePoolScheduling.svg) - -See how, with database pools, two reads can see different database states at the same time. - -For more information about database pools, grab information about SQLite [WAL mode](https://www.sqlite.org/wal.html) and [snapshot isolation](https://sqlite.org/isolation.html). See [Database Observation](#database-changes-observation) when you look after automatic notifications of database changes. - - -### Advanced DatabasePool - -[Database pools](#database-pools) are very concurrent, since all reads can run in parallel, and can even run during write operations. But writes are still serialized: at any given point in time, there is no more than a single thread that is writing into the database. - -When your application modifies the database, and then reads some value that depends on those modifications, you may want to avoid locking the writer queue longer than necessary: - -```swift -try dbPool.write { db in - // Increment the number of players - try Player(...).insert(db) - - // Read the number of players. The writer queue is still locked :-( - let count = try Player.fetchCount(db) -} -``` - -A wrong solution is to chain a write then a read, as below. Don't do that, because another thread may modify the database in between, and make the read unreliable: - -```swift -// WRONG -try dbPool.write { db in - // Increment the number of players - try Player(...).insert(db) -} -// <- other threads can write in the database here -try dbPool.read { db in - // Read some random value :-( - let count = try Player.fetchCount(db) -} -``` - -The correct solution is the `concurrentRead` method, which must be called from within a write block, outside of any transaction. - -`concurrentRead` returns a **future value** which you consume on any dispatch queue, with the `wait()` method: - -```swift -// CORRECT -let futureCount: DatabaseFuture = try dbPool.writeWithoutTransaction { db in - // increment the number of players - try Player(...).insert(db) - - // <- not in a transaction here - let futureCount = dbPool.concurrentRead { db - return try Player.fetchCount(db) - } - return futureCount -} -// <- The writer queue has been unlocked :-) - -// Wait for the player count -let count: Int = try futureCount.wait() -``` - -`concurrentRead` blocks until it can guarantee its closure argument an isolated access to the last committed state of the database. It then asynchronously executes the closure. - -The closure can run concurrently with eventual updates performed after `concurrentRead`: those updates won't be visible from within the closure. In the example below, the number of players is guaranteed to be non-zero, even though it is fetched concurrently with the player deletion: - -```swift -try dbPool.writeWithoutTransaction { db in - // Increment the number of players - try Player(...).insert(db) - - let futureCount = dbPool.concurrentRead { db - // Guaranteed to be non-zero - return try Player.fetchCount(db) - } - - try Player.deleteAll(db) -} -``` - -[Transaction Observers](#transactionobserver-protocol) can also use `concurrentRead` in their `databaseDidCommit` method in order to process database changes without blocking other threads that want to write into the database. - - -### Database Snapshots - -**A database snapshot sees an unchanging database content, as it existed at the moment it was created.** - -"Unchanging" means that a snapshot never sees any database modifications during all its lifetime. And yet it doesn't prevent database updates. This "magic" is made possible by SQLite's WAL mode (see [Isolation In SQLite](https://sqlite.org/isolation.html)). - -You create snapshots from a [database pool](#database-pools): - -```swift -let snapshot = try dbPool.makeSnapshot() -``` - -You can create as many snapshots as you need, regardless of the [maximum number of readers](#databasepool-configuration) in the pool. A snapshot database connection is closed when the snapshot gets deallocated. - -**A snapshot can be used from any thread.** Its `read` methods is synchronous, and blocks the current thread until your database statements are executed: - -```swift -// Read values: -try snapshot.read { db in - let players = try Player.fetchAll(db) - let playerCount = try Player.fetchCount(db) -} - -// Extract a value from the database: -let playerCount = try snapshot.read { db in - try Player.fetchCount(db) -} -``` - -When you want to control the latest committed changes seen by a snapshot, create it from the pool's writer protected dispatch queue, outside of any transaction: - -```swift -let snapshot1 = try dbPool.writeWithoutTransaction { db -> DatabaseSnapshot in - try db.inTransaction { - // delete all players - try Player.deleteAll() - return .commit - } - - // <- not in a transaction here - return dbPool.makeSnapshot() -} -// <- Other threads may modify the database here -let snapshot2 = try dbPool.makeSnapshot() - -try snapshot1.read { db in - // Guaranteed to be zero - try Player.fetchCount(db) -} - -try snapshot2.read { db in - // Could be anything - try Player.fetchCount(db) -} -``` - -> :point_up: **Note**: snapshots currently serialize all database accesses. In the future, snapshots may allow concurrent reads. - - -### DatabaseWriter and DatabaseReader Protocols - -Both DatabaseQueue and DatabasePool adopt the [DatabaseReader](http://groue.github.io/GRDB.swift/docs/4.0/Protocols/DatabaseReader.html) and [DatabaseWriter](http://groue.github.io/GRDB.swift/docs/4.0/Protocols/DatabaseWriter.html) protocols. DatabaseSnapshot adopts DatabaseReader only. - -These protocols provide a unified API that let you write generic code that targets all concurrency modes. They fuel, for example: - -- [Migrations](#migrations) -- [DatabaseRegionObservation] -- [ValueObservation] -- [FetchedRecordsController] -- [RxGRDB] - -Only five types adopt those protocols: DatabaseQueue, DatabasePool, DatabaseSnapshot, AnyDatabaseReader, and AnyDatabaseWriter. Expanding this set is not supported: any future GRDB release may break your custom writers and readers, without notice. - -DatabaseReader and DatabaseWriter provide the *smallest* common guarantees: they don't erase the differences between queues, pools, and snapshots. See for example [Differences between Database Queues and Pools](#differences-between-database-queues-and-pools). - -However, you can prevent some parts of your application from writing in the database by giving them a DatabaseReader: - -```swift -// This class can read in the database, but can't write into it. -class MyReadOnlyComponent { - let reader: DatabaseReader - - init(reader: DatabaseReader) { - self.reader = reader - } -} - -let dbQueue: DatabaseQueue = ... -let component = MyReadOnlyComponent(reader: dbQueue) -``` - -> :point_up: **Note**: DatabaseReader is not a **secure** way to prevent an application component from writing in the database, because write access is just a cast away: -> -> ```swift -> if let dbQueue = reader as? DatabaseQueue { -> try dbQueue.write { ... } -> } -> ``` - - -### Unsafe Concurrency APIs - -**Database queues, pools, snapshots, as well as their common protocols `DatabaseReader` and `DatabaseWriter` provide *unsafe* APIs.** Unsafe APIs lift [concurrency guarantees](#guarantees-and-rules), and allow advanced yet unsafe patterns. - -- **`unsafeRead`** - - The `unsafeRead` method is synchronous, and blocks the current thread until your database statements are executed in a protected dispatch queue. GRDB does just the bare minimum to provide a database connection that can read. - - When used on a database pool, reads are no longer isolated: - - ```swift - dbPool.unsafeRead { db in - // Those two values may be different because some other thread - // may have inserted or deleted a player between the two requests: - let count1 = try Player.fetchCount(db) - let count2 = try Player.fetchCount(db) - } - ``` - - When used on a database queue, the closure argument is allowed to write in the database. - -- **`unsafeReentrantRead`** - - The `unsafeReentrantRead` behaves just as `unsafeRead` (see above), and allows reentrant calls: - - ```swift - dbPool.read { db1 in - // No "Database methods are not reentrant" fatal error: - dbPool.unsafeReentrantRead { db2 in - dbPool.unsafeReentrantRead { db3 in - ... - } - } - } - ``` - - Reentrant database accesses make it very easy to break the second [safety rule](#guarantees-and-rules), which says: "group related statements within a single call to a DatabaseQueue or DatabasePool database access method.". Using a reentrant method is pretty much likely the sign of a wrong application architecture that needs refactoring. - - There is a single valid use case for reentrant methods, which is when you are unable to control database access scheduling. - -- **`unsafeReentrantWrite`** - - The `unsafeReentrantWrite` method is synchronous, and blocks the current thread until your database statements are executed in a protected dispatch queue. Writes are serialized: eventual concurrent database updates are postponed until the block has executed. - - Reentrant calls are allowed: - - ```swift - dbQueue.write { db1 in - // No "Database methods are not reentrant" fatal error: - dbQueue.unsafeReentrantWrite { db2 in - dbQueue.unsafeReentrantWrite { db3 in - ... - } - } - } - ``` - - Reentrant database accesses make it very easy to break the second [safety rule](#guarantees-and-rules), which says: "group related statements within a single call to a DatabaseQueue or DatabasePool database access method.". Using a reentrant method is pretty much likely the sign of a wrong application architecture that needs refactoring. - - There is a single valid use case for reentrant methods, which is when you are unable to control database access scheduling. - - -### Dealing with External Connections - -The first rule of GRDB is: - -- **[Rule 1](#guarantees-and-rules)**: Have a unique instance of DatabaseQueue or DatabasePool connected to any database file. - -This means that dealing with external connections is not a focus of GRDB. [Guarantees](#guarantees-and-rules) of GRDB may or may not hold as soon as some external connection modifies a database. - -If you absolutely need multiple connections, then: - -- Reconsider your position -- Read about [isolation in SQLite](https://www.sqlite.org/isolation.html) -- Learn about [locks and transactions](https://www.sqlite.org/lang_transaction.html) -- Become a master of the [WAL mode](https://www.sqlite.org/wal.html) -- Prepare to setup a [busy handler](https://www.sqlite.org/c3ref/busy_handler.html) with [Configuration.busyMode](http://groue.github.io/GRDB.swift/docs/4.0/Structs/Configuration.html) -- [Ask questions](https://github.com/groue/GRDB.swift/issues) - - -## Performance - -GRDB is a reasonably fast library, and can deliver quite efficient SQLite access. See [Comparing the Performances of Swift SQLite libraries](https://github.com/groue/GRDB.swift/wiki/Performance) for an overview. - -You'll find below general advice when you do look after performance: - -- Focus -- Know your platform -- Use transactions -- Don't do useless work -- Learn about SQL strengths and weaknesses -- Avoid strings & dictionaries - - -### Performance tip: focus - -You don't know which part of your program needs improvement until you have run a benchmarking tool. - -Don't make any assumption, avoid optimizing code too early, and use [Instruments](https://developer.apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/MeasuringPerformance.html). - - -### Performance tip: know your platform - -If your application processes a huge JSON file and inserts thousands of rows in the database right from the main thread, it will quite likely become unresponsive, and provide a sub-quality user experience. - -If not done yet, read the [Concurrency Programming Guide](https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091) and learn how to perform heavy computations without blocking your application. - -Most GRBD APIs are [synchronous](#database-connections). Spawning them into parallel queues is as easy as: - -```swift -DispatchQueue.global().async { - dbQueue.write { db in - // Perform database work - } - DispatchQueue.main.async { - // update your user interface - } -} -``` - - -### Performance tip: use transactions - -Performing multiple updates to the database is much faster when executed inside a [transaction](#transactions-and-savepoints). This is because a transaction allows SQLite to postpone writing changes to disk until the final commit: - -```swift -// Inefficient -try dbQueue.inDatabase { db in // or dbPool.writeWithoutTransaction - for player in players { - try player.insert(db) - } -} - -// Efficient -try dbQueue.write { db in // or dbPool.write - for player in players { - try player.insert(db) - } -} - -// Efficient -try dbQueue.inTransaction { db in // or dbPool.writeInTransaction - for player in players { - try player.insert(db) - } - return .commit -} -``` - - -### Performance tip: don't do useless work - -Obviously, no code is faster than any code. - - -**Don't fetch columns you don't use** - -```swift -// SELECT * FROM player -try Player.fetchAll(db) - -// SELECT id, name FROM player -try Player.select(idColumn, nameColumn).fetchAll(db) -``` - -If your Player type can't be built without other columns (it has non-optional properties for other columns), *do define and use a different type*. - -See [Columns Selected by a Request] for more information. - - -**Don't fetch rows you don't use** - -Use [fetchOne](#fetching-methods) when you need a single value, and otherwise limit your queries at the database level: - -```swift -// Wrong way: this code may discard hundreds of useless database rows -let players = try Player.order(scoreColumn.desc).fetchAll(db) -let hallOfFame = players.prefix(5) - -// Better way -let hallOfFame = try Player.order(scoreColumn.desc).limit(5).fetchAll(db) -``` - - -**Don't copy values unless necessary** - -Particularly: the Array returned by the `fetchAll` method, and the cursor returned by `fetchCursor` aren't the same: - -`fetchAll` copies all values from the database into memory, when `fetchCursor` iterates database results as they are generated by SQLite, taking profit from SQLite efficiency. - -You should only load arrays if you need to keep them for later use (such as iterating their contents in the main thread). Otherwise, use `fetchCursor`. - -See [fetching methods](#fetching-methods) for more information about `fetchAll` and `fetchCursor`. See also the [Row.dataNoCopy](#data-and-memory-savings) method. - - -**Don't update rows unless necessary** - -An UPDATE statement is costly: SQLite has to look for the updated row, update values, and write changes to disk. - -When the overwritten values are the same as the existing ones, it's thus better to avoid performing the UPDATE statement:. - -```swift -if player.hasDatabaseChanges { - try player.update(db) -} -``` - -See [Record Comparison] for more information. - - -### Performance tip: learn about SQL strengths and weaknesses - -Consider a simple use case: your store application has to display a list of authors with the number of available books: - -- J. M. Coetzee (6) -- Herman Melville (1) -- Alice Munro (3) -- Kim Stanley Robinson (7) -- Oliver Sacks (4) - -The following code is inefficient. It is an example of the [N+1 problem](http://stackoverflow.com/questions/97197/what-is-the-n1-selects-issue), because it performs one query to load the authors, and then N queries, as many as there are authors. This turns very inefficient as the number of authors grows: - -```swift -// SELECT * FROM author -let authors = try Author.fetchAll(db) -for author in authors { - // SELECT COUNT(*) FROM book WHERE authorId = ... - author.bookCount = try Book.filter(authorIdColumn == author.id).fetchCount(db) -} -``` - -Instead, perform *a single query*: - -```swift -let sql = """ - SELECT author.*, COUNT(book.id) AS bookCount - FROM author - LEFT JOIN book ON book.authorId = author.id - GROUP BY author.id - """ -let authors = try Author.fetchAll(db, sql: sql) -``` - -In the example above, consider extending your Author with an extra bookCount property, or define and use a different type. - -Generally, define indexes on your database tables, and use SQLite's efficient query planning: - -- [Query Planning](https://www.sqlite.org/queryplanner.html) -- [CREATE INDEX](https://www.sqlite.org/lang_createindex.html) -- [The SQLite Query Planner](https://www.sqlite.org/optoverview.html) -- [EXPLAIN QUERY PLAN](https://www.sqlite.org/eqp.html) - - -### Performance tip: avoid strings & dictionaries - -The String and Dictionary Swift types are better avoided when you look for the best performance. - -Now GRDB [records](#records), for your convenience, do use strings and dictionaries: - -```swift -class Player : Record { - var id: Int64? - var name: String - var email: String - - required init(_ row: Row) { - id = row["id"] // String - name = row["name"] // String - email = row["email"] // String - super.init() - } - - override func encode(to container: inout PersistenceContainer) { - container["id"] = id // String - container["name"] = name // String - container["email"] = email // String - } -} -``` - -When convenience hurts performance, you can still use records, but you have better avoiding their string and dictionary-based methods. - -For example, when fetching values, prefer loading columns by index: - -```swift -// Strings & dictionaries -let players = try Player.fetchAll(db) - -// Column indexes -// SELECT id, name, email FROM player -let request = Player.select(idColumn, nameColumn, emailColumn) -let rows = try Row.fetchCursor(db, request) -while let row = try rows.next() { - let id: Int64 = row[0] - let name: String = row[1] - let email: String = row[2] - let player = Player(id: id, name: name, email: email) - ... -} -``` - -When inserting values, use reusable [prepared statements](#prepared-statements), and set statements values with an *array*: - -```swift -// Strings & dictionaries -for player in players { - try player.insert(db) -} - -// Prepared statement -let insertStatement = db.prepareStatement("INSERT INTO player (name, email) VALUES (?, ?)") -for player in players { - // Only use the unsafe arguments setter if you are sure that you provide - // all statement arguments. A mistake can store unexpected values in - // the database. - insertStatement.unsafeSetArguments([player.name, player.email]) - try insertStatement.execute() -} -``` - - -FAQ -=== - -- [How do I create a database in my application?](#how-do-i-create-a-database-in-my-application) -- [How do I open a database stored as a resource of my application?](#how-do-i-open-a-database-stored-as-a-resource-of-my-application) -- [How do I close a database connection?](#how-do-i-close-a-database-connection) -- [How do I print a request as SQL?](#how-do-i-print-a-request-as-sql) -- [Generic parameter 'T' could not be inferred](#generic-parameter-t-could-not-be-inferred) -- [SQLite error 10 "disk I/O error", SQLite error 23 "not authorized"](#sqlite-error-10-disk-io-error-sqlite-error-23-not-authorized) -- [What Are Experimental Features?](#what-are-experimental-features) - - -### How do I create a database in my application? - -This question assumes that your application has to create a new database from scratch. If your app has to open an existing database that is embedded inside your application as a resource, see [How do I open a database stored as a resource of my application?](#how-do-i-open-a-database-stored-as-a-resource-of-my-application) instead. - -The database has to be stored in a valid place where it can be created and modified. For example, in the [Application Support directory](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html): - -```swift -let databaseURL = try FileManager.default - .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - .appendingPathComponent("db.sqlite") -let dbQueue = try DatabaseQueue(path: databaseURL.path) -``` - - -### How do I open a database stored as a resource of my application? - -If your application does not need to modify the database, open a read-only [connection](#database-connections) to your resource: - -```swift -var configuration = Configuration() -configuration.readonly = true -let dbPath = Bundle.main.path(forResource: "db", ofType: "sqlite")! -let dbQueue = try DatabaseQueue(path: dbPath, configuration: configuration) -``` - -If the application should modify the database, you need to copy it to a place where it can be modified. For example, in the [Application Support directory](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html). Only then, open a [connection](#database-connections): - -```swift -let fileManager = FileManager.default -let dbPath = try fileManager - .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - .appendingPathComponent("db.sqlite") - .path -if !fileManager.fileExists(atPath: dbPath) { - let dbResourcePath = Bundle.main.path(forResource: "db", ofType: "sqlite")! - try fileManager.copyItem(atPath: dbResourcePath, toPath: dbPath) -} -let dbQueue = try DatabaseQueue(path: dbPath) -``` - - -### How do I close a database connection? - -Database connections are managed by [database queues](#database-queues) and [pools](#database-pools). A connection is closed when its database queue or pool is deallocated, and all usages of this connection are completed. - -Database accesses that run in background threads postpone the closing of connections. - - -### How do I print a request as SQL? - -When you want to debug a request that does not deliver the expected results, you may want to print the SQL that is actually executed. - -You can turn your request into a `SQLRequest` instance: - -```swift -try dbQueue.read { db in - let request = Wine - .filter(Column("origin") == "Burgundy") - .order(Column("price") - - let sqlRequest = try SQLRequest(db, request: request) - print(sqlRequest.sql) - // Prints SELECT * FROM wine WHERE origin = ? ORDER BY price - print(sqlRequest.arguments) - // Prints ["Burgundy"] -} -``` - -Another option is to setup a tracing function that will print out all SQL requests executed by your application. You provide the trace function when you connect to the database: - -```swift -var config = Configuration() -config.trace = { print($0) } // Prints all SQL statements -let dbQueue = try DatabaseQueue(path: dbPath, configuration: config) - -try dbQueue.read { db in - let wines = Wine - .filter(Column("origin") == "Burgundy") - .order(Column("price") - .fetchAll(db) - // Prints SELECT * FROM wine WHERE origin = 'Burgundy' ORDER BY price -} -``` - - -### Generic parameter 'T' could not be inferred - -You may get this error when using the `read` and `write` methods of database queues and pools: - -```swift -// Generic parameter 'T' could not be inferred -let x = try dbQueue.read { db in - let result = try String.fetchOne(db, ...) - return result -} -``` - -This is a Swift compiler issue (see [SR-1570](https://bugs.swift.org/browse/SR-1570)). - -The general workaround is to explicitly declare the type of the closure result: - -```swift -// General Workaround -let string = try dbQueue.read { db -> String? in - let result = try String.fetchOne(db, ...) - return result -} -``` - -You can also, when possible, write a single-line closure: - -```swift -// Single-line closure workaround: -let string = try dbQueue.read { db in - try String.fetchOne(db, ...) -} -``` - - -### SQLite error 10 "disk I/O error", SQLite error 23 "not authorized" - -Those errors may be the sign that SQLite can't access the database due to [data protection](#data-protection). - -When your application should be able to run in the background on a locked device, it has to catch this error, and, for example, wait for [UIApplicationDelegate.applicationProtectedDataDidBecomeAvailable(_:)](https://developer.apple.com/reference/uikit/uiapplicationdelegate/1623044-applicationprotecteddatadidbecom) or [UIApplicationProtectedDataDidBecomeAvailable](https://developer.apple.com/reference/uikit/uiapplicationprotecteddatadidbecomeavailable) notification and retry the failed database operation. - -This error can also be prevented altogether by using a more relaxed [file protection](https://developer.apple.com/reference/foundation/filemanager/1653059-file_protection_values). - - -### What Are Experimental Features? - -Since GRDB 1.0, all backwards compatibility guarantees of [semantic versioning](http://semver.org) apply: no breaking change will happen until the next major version of the library. - -There is an exception, though: *experimental features*, marked with the "**:fire: EXPERIMENTAL**" badge. Those are advanced features that are too young, or lack user feedback. They are not stabilized yet. - -Those experimental features are not protected by semantic versioning, and may break between two minor releases of the library. To help them becoming stable, [your feedback](https://github.com/groue/GRDB.swift/issues) is greatly appreciated. - - -Sample Code -=========== - -- The [Documentation](#documentation) is full of GRDB snippets. -- [Demo Application](DemoApps/GRDBDemoiOS/README.md): A sample iOS application. -- [WWDC Companion](https://github.com/groue/WWDCCompanion): A sample iOS application. -- Check `GRDB.xcworkspace`: it contains GRDB-enabled playgrounds to play with. -- How to synchronize a database table with a JSON payload: [JSONSynchronization.playground](Playgrounds/JSONSynchronization.playground/Contents.swift) - - ---- - -**Thanks** - -- [Pierlis](http://pierlis.com), where we write great software. -- [@alextrob](https://github.com/alextrob), [@bellebethcooper](https://github.com/bellebethcooper), [@bfad](https://github.com/bfad), [@cfilipov](https://github.com/cfilipov), [@charlesmchen-signal](https://github.com/charlesmchen-signal), [@Chiliec](https://github.com/Chiliec), [@darrenclark](https://github.com/darrenclark), [@davidkraus](https://github.com/davidkraus), [@fpillet](http://github.com/fpillet), [@gusrota](https://github.com/gusrota), [@hartbit](https://github.com/hartbit), [@kdubb](https://github.com/kdubb), [@kluufger](https://github.com/kluufger), [@KyleLeneau](https://github.com/KyleLeneau), [@Marus](https://github.com/Marus), [@michaelkirk-signal](https://github.com/michaelkirk-signal), [@pakko972](https://github.com/pakko972), [@peter-ss](https://github.com/peter-ss), [@pierlo](https://github.com/pierlo), [@pocketpixels](https://github.com/pocketpixels), [@schveiguy](https://github.com/schveiguy), [@SD10](https://github.com/SD10), [@sobri909](https://github.com/sobri909), [@sroddy](https://github.com/sroddy), [@swiftlyfalling](https://github.com/swiftlyfalling), [@valexa](https://github.com/valexa), and [@zmeyc](https://github.com/zmeyc) for their contributions, help, and feedback on GRDB. -- [@aymerick](https://github.com/aymerick) and [@kali](https://github.com/kali) because SQL. -- [ccgus/fmdb](https://github.com/ccgus/fmdb) for its excellency. - -Legacy -====== - -#### Changes Tracking - -This chapter has been renamed [Record Comparison]. - -#### Persistable Protocol - -This protocol has been renamed [PersistableRecord] in GRDB 3.0. - -#### RowConvertible Protocol - -This protocol has been renamed [FetchableRecord] in GRDB 3.0. - -#### TableMapping Protocol - -This protocol has been renamed [TableRecord] in GRDB 3.0. - -#### Customized Decoding of Database Rows - -This chapter has been renamed [Beyond FetchableRecord]. - -[Associations]: Documentation/AssociationsBasics.md -[Beyond FetchableRecord]: #beyond-fetchablerecord -[Codable Records]: #codable-records -[Columns Selected by a Request]: #columns-selected-by-a-request -[Conflict Resolution]: #conflict-resolution -[Customizing the Persistence Methods]: #customizing-the-persistence-methods -[Date and UUID Coding Strategies]: #date-and-uuid-coding-strategies -[Fetching from Requests]: #fetching-from-requests -[The Implicit RowID Primary Key]: #the-implicit-rowid-primary-key -[The userInfo Dictionary]: #the-userinfo-dictionary -[JSON Columns]: #json-columns -[FetchableRecord]: #fetchablerecord-protocol -[EncodableRecord]: #persistablerecord-protocol -[PersistableRecord]: #persistablerecord-protocol -[Record Comparison]: #record-comparison -[Record Customization Options]: #record-customization-options -[TableRecord]: #tablerecord-protocol -[ValueObservation]: #valueobservation -[DatabaseRegionObservation]: #databaseregionobservation -[FetchedRecordsController]: #fetchedrecordscontroller -[RxGRDB]: http://github.com/RxSwiftCommunity/RxGRDB -[DatabaseRegionConvertible]: #the-databaseregionconvertible-protocol -[ValueObservation and DatabaseRegionObservation]: #valueobservation-and-databaseregionobservation -[DatabaseRegion]: #databaseregion -[SQL Interpolation]: Documentation/SQLInterpolation.md -[custom SQLite build]: Documentation/CustomSQLiteBuilds.md diff --git a/Example/Pods/GRDB.swift/Support/GRDB-Bridging.h b/Example/Pods/GRDB.swift/Support/GRDB-Bridging.h deleted file mode 100755 index e69de29..0000000 diff --git a/Example/Pods/GRDB.swift/Support/GRDB.h b/Example/Pods/GRDB.swift/Support/GRDB.h deleted file mode 100755 index 69ad6ed..0000000 --- a/Example/Pods/GRDB.swift/Support/GRDB.h +++ /dev/null @@ -1,10 +0,0 @@ -@import Foundation; - -//! Project version number for GRDB. -FOUNDATION_EXPORT double GRDB_VersionNumber; - -//! Project version string for GRDB. -FOUNDATION_EXPORT const unsigned char GRDB_VersionString[]; - -#import - diff --git a/Example/Pods/GRDB.swift/Support/grdb_config.h b/Example/Pods/GRDB.swift/Support/grdb_config.h deleted file mode 100755 index 641560f..0000000 --- a/Example/Pods/GRDB.swift/Support/grdb_config.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef grdb_config_h -#define grdb_config_h - -#if defined(COCOAPODS) - #if defined(GRDBCIPHER) - #include - #else - #include - #endif -#else - #if defined(GRDBCUSTOMSQLITE) - #include - #else - #include - #endif -#endif - -typedef void(*errorLogCallback)(void *pArg, int iErrCode, const char *zMsg); - -// Wrapper around sqlite3_config(SQLITE_CONFIG_LOG, ...) which is a variadic -// function that can't be used from Swift. -static inline void registerErrorLogCallback(errorLogCallback callback) { - sqlite3_config(SQLITE_CONFIG_LOG, callback, 0); -} - -#endif /* grdb_config_h */ diff --git a/Example/Pods/GRDB.swift/Support/module.modulemap b/Example/Pods/GRDB.swift/Support/module.modulemap deleted file mode 100755 index 04ac555..0000000 --- a/Example/Pods/GRDB.swift/Support/module.modulemap +++ /dev/null @@ -1,8 +0,0 @@ -framework module GRDB { - umbrella header "GRDB.h" - - export * - module * { export * } - - header "grdb_config.h" -} diff --git a/Example/Pods/Local Podspecs/MuslimData.podspec.json b/Example/Pods/Local Podspecs/MuslimData.podspec.json deleted file mode 100755 index 04ddc7f..0000000 --- a/Example/Pods/Local Podspecs/MuslimData.podspec.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "MuslimData", - "version": "1.0.0-beta.8", - "summary": "Islamic library (Prayer Times [fixed and calculated], Names of Allah, and Azkars).", - "description": "MuslimData is an Islamic library that provides Prayer Times (fixed and calculated), Offline Geocoder, Location Search, Names of Allah, Azkars (Husnil Muslim).\n\nFixed and Calculated Prayer Times\nMost cities around the world find their prayer times by using some calculations which is based on location (longitude and latitude) but some other cities have fixed time table for their prayer times. This library contains most fixed and calculated prayer times. Now you can contribute it to improve it and also you can use it in Muslim communities or Muslim apps.", - "homepage": "https://github.com/KosratDAhmad/MuslimData", - "license": { - "type": "MIT", - "file": "LICENSE" - }, - "authors": { - "Kosrat D. Ahmad": "kosrat.d.ahmad@gmail.com" - }, - "source": { - "git": "https://github.com/KosratDAhmad/MuslimData.git", - "tag": "v1.0.0-beta.8" - }, - "platforms": { - "ios": "10.0" - }, - "source_files": "MuslimData/Classes/**/*", - "swift_version": "5", - "resources": "MuslimData/Assets/**/*", - "frameworks": "UIKit", - "dependencies": { - "GRDB.swift": [ - - ] - } -} diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock deleted file mode 100755 index 853bebd..0000000 --- a/Example/Pods/Manifest.lock +++ /dev/null @@ -1,25 +0,0 @@ -PODS: - - GRDB.swift (4.0.1): - - GRDB.swift/standard (= 4.0.1) - - GRDB.swift/standard (4.0.1) - - MuslimData (1.0.0-beta.8): - - GRDB.swift - -DEPENDENCIES: - - MuslimData (from `../`) - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - GRDB.swift - -EXTERNAL SOURCES: - MuslimData: - :path: "../" - -SPEC CHECKSUMS: - GRDB.swift: 106a830decf1d92a3fc63c6d6a2f6586f6187297 - MuslimData: 85ec0559a561eb03188b3e5cbb33dbbb36b91c8d - -PODFILE CHECKSUM: f1a8413ff73c18d27d5283425e84b7905893b20c - -COCOAPODS: 1.6.0 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj deleted file mode 100755 index 9c346df..0000000 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1641 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 000B5FF0F2E60440DE83E1CDEF81BD43 /* DatabaseCollation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BAAE27BDCE90B8C1F6B4992424D9C6 /* DatabaseCollation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 02750A16FAA074E14BB1C0936016B286 /* SQLCollatedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7F2FEBDAA3F216BBB4AE11EC6585D6 /* SQLCollatedExpression.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 07E87312FC3CA6D83F78905E504BCE9C /* DatabaseValueConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B99EBD21496FBA605E3A375E86ED829 /* DatabaseValueConversion.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 0AD24AEB225F135FE242003EA86E7039 /* PrayerAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC6A83C7FE2580E3F2A1AFBB2B117E6 /* PrayerAttribute.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 0C02D06017D674FB3D221B3082D52242 /* DatabaseRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9488B06541B325219BAE37A334A1E59 /* DatabaseRegion.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 0C99CC73940BDB5696917C42DE00B262 /* DatabasePromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C77E730F441F579931C11BA8F749FDF /* DatabasePromise.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 0E0B8626BCD149D02290F230544A789A /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C709D1799799F5A0E43E043E023415B /* UUID.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 10B2282DEF61B85E70A0877BE83B21DE /* FetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F3F554C1B5DCD4EF089F605813FC5D3 /* FetchRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 11AAE1599E6093A219A889D2DAE80ECA /* StatementColumnConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5F36FEF53D52328747380C3D59D187 /* StatementColumnConvertible.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 121E4E7A4C0459415A9B883D506D3FBC /* ValueObservation+DatabaseValueConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8F40C80B8B85C4393ED5F43C8ED116 /* ValueObservation+DatabaseValueConvertible.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 13F6B7CE384A0A2CE2A7E12616535089 /* SQLExpressible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7093F7382D260704506C5464AFBC16F5 /* SQLExpressible.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 15BFA9419F7B8F44B90DB2A0685D8416 /* SQLGenerationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33E1BA72AB090848D099FDE15FFCD602 /* SQLGenerationContext.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 1958078CF71CEA9143902886C5B9C310 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A49D0B537A24E7B6779BDF2FF4BF1376 /* UIKit.framework */; }; - 19EAAEBC608AA44323ABEE5E5C433F82 /* GRDB-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3DE7F469BC348F7C1DDC5196499B58 /* GRDB-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1A5226373797A66B9468D3C1F5C4D825 /* TableRecord+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE3C6FBEF1E948A8881FAC46607E0CE /* TableRecord+Association.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 1BA72BD73CE8FCF542D23CBD4E18A2D8 /* EncodableRecord+Encodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CAEAC5CC5B2720DC623C44C895C435 /* EncodableRecord+Encodable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 1C3D3D4CBD9412DA99FE9CE8577F8856 /* GRDB-4.0.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B8A534766D304CF9DE1A5C2C35051 /* GRDB-4.0.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 1CAA3EDF7EBE5B64C6F193EB77CF398D /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25525D31CDCACA2AEE38C6A90DEDD714 /* Database.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 1EF47C87D5BC5C950E090F8519C6FD30 /* ValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CE31B27E7F34691556AD03AFBB63F5 /* ValueObservation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 20467EF687049794ACE9F46BD89B36A9 /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E40BE124BC4154995B1B2A7A660D03 /* NSData.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 2136CBA14769165E58DDA619D2903003 /* DatabaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49192A23515A2FDF0209EBF9BA4DAB65 /* DatabaseError.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 22B2CC4684AD540D4DC22B38F0A1D4B8 /* DatabaseMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E166D7E44FE057A6E248D6B423B0C81 /* DatabaseMigrator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 2844C8F86E572981747D5F7D57A47079 /* SQLInterpolation+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5BDCC7B5AB732EC9F8A60A48C1D510 /* SQLInterpolation+QueryInterface.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 28AC5449494C7BB3E1D2C61A23D88B3E /* ValueObservation+DistinctUntilChanged.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8CAE41207247FFE6C23DD13296EA70 /* ValueObservation+DistinctUntilChanged.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 296E2869F5F3E220C5132E55AF60E2D3 /* GRDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 7FFD2B745CCCE37869DFC4E8FA83908E /* GRDB.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2A050272F5AA9419EACC2B68583FBF0C /* StatementAuthorizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83D32E01380A9B4A0B3817B6B914A51 /* StatementAuthorizer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 2B130AEAB2EE4BD7997DB369572A684A /* FetchableRecord+TableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2058FFAD3AA3F5F1DA38DAB365079462 /* FetchableRecord+TableRecord.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 2D0F97A52B813E8B003D46C5 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 2D0F97A42B813E8B003D46C5 /* CHANGELOG.md */; }; - 2D6CD44729CC5BF2009B527C /* muslim_db_v2.0.0.db in Resources */ = {isa = PBXBuildFile; fileRef = 2D6CD44629CC5BF2009B527C /* muslim_db_v2.0.0.db */; }; - 2DAF0C709FAF6D288751DB00EB06B40C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD7232797ACF3F6F7685B58BCDC00C7 /* Foundation.framework */; }; - 2DE6E95C2BA8D8010082A3CE /* MuslimRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE6E95B2BA8D8010082A3CE /* MuslimRepository.swift */; }; - 2DFE19682BAB81E900665C6B /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFE19672BAB81E900665C6B /* Repository.swift */; }; - 318B2D4FBE83F08BB67C2AA6A52977AB /* FTS5+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5779BFB97BB16F7C257E39CD28630594 /* FTS5+QueryInterface.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 33FDF2697ECE2E34513CACBC1EDFDB51 /* DatabaseValueConvertible+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1585FD241A9AC0D79B3E084E1A8B7B77 /* DatabaseValueConvertible+RawRepresentable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 35A9B68C8D7AC42A8187DC1F8AEC095A /* FTS3TokenizerDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C9ECD60B68221B76E253AA39B4B52E /* FTS3TokenizerDescriptor.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 394451DC1DB1945ED4FF137FDE90A8E4 /* ValueObservation+FetchableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112165439418B44C4BDCFB45D22CBE34 /* ValueObservation+FetchableRecord.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 3A22C174B394CB9AF865557B3EA0D761 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 730FF805483A83B2584256EEDC5D2639 /* Language.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 3E373FA762B90937FE09682DC5BE6FCE /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF596B48C538AB646A0EAF252A33E089 /* Migration.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 41677C6E7B38372C96FF8074BE163189 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB99538C9E87FA9C44300FFCA0B04EE /* Statement.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 423266C51CEEE11551EF493E0E9D41FC /* ReadWriteBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3087A8A5BD99215CD4DB3460F8AFEA73 /* ReadWriteBox.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 4232F4B0DC26B878201F9523CEF8F761 /* PrayerTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6368FFE5512AD8AC9D36EB8427AFA109 /* PrayerTime.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 43694454258CB0903238325B1122E5A1 /* NSNull.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E4C6CC7D33B6BEE3483F48A38E6A45 /* NSNull.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 4429FB29F5620E0A1845105ACD23FDEA /* ValueObservation+MapReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E9C257C00F610EA08A4E186851F37D0 /* ValueObservation+MapReducer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 443EAD393975455814198C533E80384A /* ValueObservation+Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9768AEB6008CAEF84324EC4C8938989B /* ValueObservation+Map.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 459E5AD9A0DF5E01D04CAD6D23929282 /* FTS3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF0BA87045E3E9FABE15B6BF3A8D5A2 /* FTS3.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 4697DE621262D990332671D683A6EB00 /* RowAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32170F8226285700A097463A2C81B28E /* RowAdapter.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 46DCBB7C162FC503B1AF52D6A59CB87F /* DatabasePool.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D8132A3643601639C3E59717516CA9 /* DatabasePool.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 473A628F8D25C06F0352CDB563A8F71E /* SQLInterpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5880470B1529D3998D951354FE8AAA99 /* SQLInterpolation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 499A61F937A27F0C2BAF4ECB402B4152 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = E190980D696557F2DD2A9B526828DF03 /* FTS4.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 49D85D0B0DAA2BDADA2BBD09C2319058 /* SQLOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566BFB5B270273A425E6317B162D24F /* SQLOperators.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 49E4580E0F098D7B0E49A777CEEF7085 /* TimeFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F554C03188A6F204B2036E5CF4B82D /* TimeFormat.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 4F3B7D7E1C44ECB7A2EBEC681ACE4090 /* AzkarChapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94586B16D5D2DCA4B4E22C167DD17B61 /* AzkarChapter.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 4F791AEE35BB3276A68EDD48C35BC44E /* SQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04EB54A8111D289DE44844E4A57E6B3 /* SQLRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 5301D3C1ECA5E2D47002FBE7724941FF /* AzkarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FFBDBD6E148D8CD2D3E5D40636ABE46 /* AzkarItem.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 538BFA3A72212A24728F903FD732CE4F /* GRDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7AD95BB7089C067668FE8E0C1262934 /* GRDB.framework */; }; - 54EB702C1486DC310031B3DAED1FE3DC /* SQLSelectable+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CDDA18855D4ADEBEFF1A3AE06CC5E3 /* SQLSelectable+QueryInterface.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 55127675DE18916F1A1148A62002E454 /* DatabaseWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518451B0E85374EA68E5B75FB38C27E1 /* DatabaseWriter.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 567C4757D6C8959A805420C387798D94 /* DBHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9AF286580ABB38F1448D6568171C6 /* DBHelper.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 5B889DD74524BF47702D94225C8676BF /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35291486FD48076A204DF52CCD6A72AC /* Location.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 5DDB2B240C6F6C89EBB5A129D2F6B960 /* HasManyThroughAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852D3895437736AC3C233945C2E20301 /* HasManyThroughAssociation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 5EA0894094250A2D9B56630274A92DD8 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD0CA7B983012A8B03615622D294247 /* Data.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 5F76D077FDBDB17308D80FBCDDD32F35 /* Pods-MuslimData_Tests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D50A25ECF1C68729F9ECA92736E0517 /* Pods-MuslimData_Tests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 612D5576922DA12E36C71B8D99680421 /* Pods-MuslimData_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 30678A7CEDBC6447D947E5D4EC9E1173 /* Pods-MuslimData_Tests-dummy.m */; }; - 622F287E4490422F197E00D242F18209 /* Database+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFB400CA7F46BD8366FC26A1A6823E7 /* Database+Schema.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 65D90C1E01EF19802462954F69E983E5 /* ValueReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6127107FB86700DAE4E4C94A6B226FDF /* ValueReducer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 66F92BFF9384A67BB00CA346C5554550 /* Database+Statements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2846928F23EC085D3E5E028FFC5B1FF2 /* Database+Statements.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 676EF897E9236CB90575B3F9CFCD3120 /* DatabaseDateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04275E4CF54FAA3C2A6859DDB193D04F /* DatabaseDateComponents.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 69C357CAA8533B915C6268E88A349008 /* FTS5Pattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F1092AFB734F6CA9EDCF3A32CA7041 /* FTS5Pattern.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 6ADA4C725A1F552DE188C42ED13897F5 /* FTS5Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56CDF9772D8CD1E94A5E39433D191434 /* FTS5Tokenizer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 6B8E4A743436EED9C9D1D5D72A0D2F27 /* Inflections+English.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEF107E7E8091B90FAECDFC4C7A6954 /* Inflections+English.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 6B8F3C1D6214E7352C7856A50EB7406B /* Inflections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4970C5782878739D9F25CEE935961DA6 /* Inflections.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 6D2323C4C7DF66474D1ABCAD305F0689 /* PersistableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96925BE6CBF55EFBAD75586DB817EB24 /* PersistableRecord.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 6D2993298E9964AF6D54F095E3DA46EF /* ValueObservation+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C5FC63DA22A393F7DC2691C13091E8F /* ValueObservation+Combine.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 6DE3CE29183585530907EB1C3CCC2726 /* NSString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7795BEC7ABA70F55DE00A1C340CC52 /* NSString.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 72988A6CFA36E266D4FE0AF9DEADFC50 /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE233D66235457A95C4FDD1BC230A497 /* Cursor.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 737942B8F96BC31238E7F0B8F3609D68 /* SchedulingWatchdog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DD660E6576366AC16CF63F89BFD77B /* SchedulingWatchdog.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 739400B0F56D496D47483FC196D4C91A /* SQLiteDateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1BEECB0AD5695FC58BB42959C41DDA /* SQLiteDateParser.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 747435A2AB2CAFBAFD7651734659C6B2 /* VirtualTableModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CA41092FF01838F5755D8533720F37 /* VirtualTableModule.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 750ACAC53406F3B7C04FCD17174BA343 /* FTS3Pattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE5F32BB6F9734F44632A90954DC6F2 /* FTS3Pattern.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 79549123A16832B9B715EB4DCBE521EC /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3708554DF1666EC6C41BBEB89E5AC257 /* URL.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 7D65B46A1F2441D9147FE78F07AB373F /* QueryInterfaceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFC609C58F86B050AED73E530BC3B6AB /* QueryInterfaceRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 7D754D61FF444A9A90C1251F315B5C29 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD7232797ACF3F6F7685B58BCDC00C7 /* Foundation.framework */; }; - 7D94091D2C1067867BA50E9DAF18CE17 /* SQLExpression+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AADE52C012DAEA6407BA8798B200F02 /* SQLExpression+QueryInterface.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 7EFEF55B0CC98496F1408917DD408225 /* FetchableRecord+Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A56E81E436BD44A71DB48A73695EC /* FetchableRecord+Decodable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 80EFCEB2F5ED66AAB527232A517F5F81 /* ValueObservation+CompactMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FBE42988DD91F3D3C2B74D31996523 /* ValueObservation+CompactMap.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 81F2E0E03397CB5AE8A9FA1961C6AB67 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EE764DBA051C2839B7DD11850DA98C /* FTS5.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 821BAA59509043E640391CFA942C918C /* SQLLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DCF7B73D6DBD322B1124B77F7C28B41 /* SQLLiteral.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 82FBE1672788EB416F7D7232748EA236 /* BelongsToAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662BA0B191945070F192D329CA95496F /* BelongsToAssociation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 83728421B1D35408CC150F9A5366A386 /* DatabaseRegionObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DE2E8EEA85B8FB4DB536F2DDA9089D0 /* DatabaseRegionObservation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 85D8463381D9C856410C3B80AE8DD159 /* SQLCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2ACFA1937DAE4E5205B6A924C830CB /* SQLCollection.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 8691CEE725ED2C7C06E086770DB7757B /* Column.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4B190F2E830A1BB55FBA48ACEA7E86 /* Column.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 889F226413C21D59F2BFFBC2C92DB9EA /* SQLFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291780A00DC00211D75FBDEA9B09E764 /* SQLFunctions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 8C8DDF01955D5FB72AE73979CD218F36 /* AssociationAggregate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98583347F707576BCEA189427FC47A69 /* AssociationAggregate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 8F776D68D6DEFD38CCF85B4276D968E0 /* FTS3+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD278A63004131343ECCF5C78B29392 /* FTS3+QueryInterface.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 90DF4F4E9630E63609F3CF6642505E25 /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25DF833796CBC2D0F008C6638CF48BD2 /* Record.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 91F28A0A35BA0F4F8FB3F005D7253C45 /* FTS5CustomTokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F53F41F8B38930F8E492305ADD4866 /* FTS5CustomTokenizer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 92B69B37E5ADEA0DDCDEC06396885052 /* SQLQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AFBABE58AAD9E968921664890D2D50 /* SQLQuery.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 933318275B26D35CB5734E128B3BB5A4 /* Row+QueryInterfaceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BD16BE3201E2C0A1181407D25A3EBC /* Row+QueryInterfaceRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 95327339DAA1511EE65E735E37DA42C5 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89F00D263328D7C901ADE57C1548021 /* Result.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 9A7995D89E2503F59B83841B83A6A3A9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD7232797ACF3F6F7685B58BCDC00C7 /* Foundation.framework */; }; - 9D12AC35A63C05E9F0EE63EB506CEB9F /* ValueObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756861507925251CBACC0D005FE1912 /* ValueObserver.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 9D661C0F8785669F6E4A88754F9C81EF /* TableRecord+QueryInterfaceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB527B155158A83DC29C8AB471EF8300 /* TableRecord+QueryInterfaceRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 9D8D0F9EF2B61EBF2D539080CD08FCE0 /* ValueObservation+Row.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3E15AF93024917493AF2A2ED452332 /* ValueObservation+Row.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 9EF00602B746DE476813B33D60E7B8B5 /* FetchableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C1864F172A3D81F9D25636ADB75ED9 /* FetchableRecord.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - 9F2DF35D15A048345D6F3253521B57F3 /* FetchedRecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F997529A98C052DA7C12591E52C94D6 /* FetchedRecordsController.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - A2C07BA2D862B7CFEE16E632B97EF68C /* AzkarCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2B3F9D2BF7D6132661A531E7BB2F12 /* AzkarCategory.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - A38C252839DFC6B34277E40395DA94C1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374B2EE56EE7DAC67CCB2C047D622957 /* Configuration.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - A4F753F4A25E639F6EBBD469F1C821EF /* HasManyAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73BE27477C2865305F88857F860D067 /* HasManyAssociation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - A69D080C6DC5CCD1C8153338B1B4BEF1 /* DatabaseFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE941C1A7A95F921000AFDFD91F54541 /* DatabaseFunction.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - A7D645073A4902CE19144733BD3B1D6B /* MuslimData-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E1B0453AB3FD95A926B256E8D21F72D9 /* MuslimData-dummy.m */; }; - A9286B0849F46D5349C42CDD9E3FCBD8 /* GRDB.swift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A2F3326B33A6BC66E13954DF736BEAA /* GRDB.swift-dummy.m */; }; - A9346A4922DFB8C725C8722D99258AF8 /* DatabaseValueConvertible+ReferenceConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A115FEA22E3B697DDD8CB0CF3FCAD9 /* DatabaseValueConvertible+ReferenceConvertible.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - AC842DD31D9FD9F8A80F393309AC1A2B /* NSNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4B392CEDEDE8E47D3BD1ECC7EA551E /* NSNumber.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - B03A35D413AA6ACB4F1BE28702E84333 /* DatabaseReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C83188D9DFD01CA9D469EB06279767D /* DatabaseReader.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - B09F1124D798A0E3AFA81B0B79F80E36 /* Pool.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A77728D7700424D943FDE619754A2 /* Pool.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - B5B1E0C592A08E5665915D2BC1A0D9E8 /* DatabaseSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8D1DE939367A8362484A58B33EB6DE /* DatabaseSnapshot.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - B60E22BAD03836CB0720C853D1B19341 /* SQLAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054167CF407C51AC6687ADEBF914CDDC /* SQLAssociation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - B8244EA7A9D7EA478BC043649E3F256D /* DatabaseValueConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00DD889D9B78450041FF5AEE58E86647 /* DatabaseValueConvertible.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - BA76D4525CC0A4865E2AAA90336118D8 /* SQLForeignKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27BA0AAB3F0B8F0F6FBFD49B7969EA9 /* SQLForeignKeyRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - BD802C1DC1B139EDAB311C7709A00CB0 /* DatabaseValueConvertible+Encodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A48FA30EC31431A33C17FAE91902EE /* DatabaseValueConvertible+Encodable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - BE5A72CE583F45FF74F5334921FF9968 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD7232797ACF3F6F7685B58BCDC00C7 /* Foundation.framework */; }; - C184186D7F480467C879626B3D8BF92A /* grdb_config.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E91BB21A1925C004A070AB895D6B14 /* grdb_config.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C31B69B2E5A6B91C7202FCE6A3B6A1E4 /* FTS5TokenizerDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0938A7247E2982B82482760B0AC6123F /* FTS5TokenizerDescriptor.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - C4E2B1658AC3663D15AA9F2531B7BFD6 /* FetchableRecord+QueryInterfaceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2B193D223BFC78855A3B1DE8CED395 /* FetchableRecord+QueryInterfaceRequest.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - C4F0D6EE223CBD552F269B36DE389B8B /* StandardLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABE51A24C723C856DD1DCC752712250 /* StandardLibrary.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - CCA3C990B6DE174297BE934A2728C421 /* SQLSpecificExpressible+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A02DA8BFACEE9E07FBD62D157FED96B /* SQLSpecificExpressible+QueryInterface.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D030773C11115860A55CDFCE6904BF38 /* QueryInterfaceRequest+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6A686C3D9502F7C4A132B40F0F72DD /* QueryInterfaceRequest+Association.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D1A2EF0386442ECD20338199E9DB8099 /* TableDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD91E47F6DD6422D0952C8AC7623582 /* TableDefinition.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D4CA4FA4E5050BD998B823D705F3BE76 /* EncodableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = F114854873706F9F005E2BB1FD9D4C65 /* EncodableRecord.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D628E893B835D23BBAED9343E59610F9 /* SerializedDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = E375E483DE2FE415F9FEA19C9669B526 /* SerializedDatabase.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D67D1521F3C5B0C03409EBAB72604C30 /* SQLRelation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2C6021F8D19343C38E11A931BD52EF /* SQLRelation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D6E75BDE3181F62E6B81499F5989808D /* ValueObservation+Count.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B9F1435B3801DADE391A4A5136738A /* ValueObservation+Count.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D6F8A02145C28A736FD8A0A8BF7EB783 /* CGFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8180C67106D338B3A2A64D08774B6D9 /* CGFloat.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - D828D98E5A5A48B3FDC5B486064626C6 /* SQLQueryGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BABC3D3DC05C7FD6865D180C0A432D /* SQLQueryGenerator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - DB7E6F762C72F6CFE76C9F5313AA2DAC /* Pods-MuslimData_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C7CD5013FEE27004C7AF144E4470ADA /* Pods-MuslimData_Example-dummy.m */; }; - DC050E33E340108BA79B38A9195EAFCC /* RequestProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2B8005D374F24A6943BAA1823F338B /* RequestProtocols.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E0E07E6C3622B91F444A2F81EECCA4A2 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7398318D874AC392EB34280CF2E1899 /* Utils.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E0E942A9C82C4FEF89A2EED08A776332 /* SQLOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED95FFCB43990871E5873CFC4E1C053 /* SQLOrdering.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E3F8678E8337CD65A23D6FB611CD6D89 /* HasOneThroughAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C559E1EEE2B6FBA44944FEE8E9152DF0 /* HasOneThroughAssociation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E48C905B8E714A91CF886741081C1685 /* MuslimData-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EEC87C5D70057CE21632F0E08FAAF615 /* MuslimData-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E4B3988CB85F30F8B1539CDFFC777DE8 /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061437BFF3E1C9882B07C7A0A6F60679 /* DateExtensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E746EBB44033C7F5ECE664C2A2862C8D /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551E845F7B283CFF0E2D215372DA26A9 /* String+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E7EC009AC91B60BFE2989DC43D5253F2 /* Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65E8B14425B931ADE785452F8A6332B /* Association.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - E8C7E279307C3DB919A3E0D67FCC114A /* DatabaseValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89DD2484314D8A3D5ADE1D158C85756 /* DatabaseValue.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - EA0BC2897FF6BE975F969C15629840CB /* Prayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85117D4C9BE4B6480B1C89933815E58 /* Prayer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - ED942EC9C394EBF13A2F679EC94192C7 /* TableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D77E14A5BB1A5D3F2E9581ECBFE4B2 /* TableRecord.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F0EF5074C899CD456B95D2F724F9067B /* Row.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B254E7B4418D6D38E004A377181618F /* Row.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F375CD882264F61911A3B5F7CC6E2310 /* SQLSelectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5B13B296896404F68B81BDDB33598A /* SQLSelectable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F5885BDC210752638AFF3EAF38651DC9 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353188DE84471B3F5021BFC563AD2E4A /* OrderedDictionary.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F5C741AE24465B84517D53DB2FE025A3 /* HasOneAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F74C3288A30C090F9DAE356D7F8CEF9 /* HasOneAssociation.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F6572EE8D863DEBB27A18DC85EFF9C93 /* Pods-MuslimData_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = B0646069865C88FAA7E0EDB542E2ACE1 /* Pods-MuslimData_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F70DB94ADA336509026869AAF426BEA3 /* TransactionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F5F76418FE68B0D427B7D08BF6A9A9 /* TransactionObserver.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F73E45DCB4A2E3D31C00A4EDC76213AC /* DatabaseQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AA10D7887DE3F10F9030EC1A2CC378 /* DatabaseQueue.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F909FEBC62AD743990EB2D7A3D594332 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0210CA581788DF572FE3501F94C639 /* Date.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - F96737610B1BD0E614F153636059C2F4 /* DatabaseValueConvertible+Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC1744C057858332AA73845B0CE26D7 /* DatabaseValueConvertible+Decodable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - FB5122536928EA7E76459AF5E6555CEE /* FTS5WrapperTokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7AA8C9DAF31AD7B74AE9ED132C61DA8 /* FTS5WrapperTokenizer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - FB9485218620ACEFA3967734BC256E2C /* DatabaseSchemaCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA2DEAEDF6FED5B07AE3779109C7886 /* DatabaseSchemaCache.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - FDA5B665C9D4F6C74D991D68EC23A297 /* SQLExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988514746A18BB7288E7AE2B074B7196 /* SQLExpression.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; - FF616834C38743D535B62B7886C74F07 /* Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A16EADAEB6FE46A4F5411CCD2A9F3 /* Name.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 1ED7D8A0A4218E980A192470A3FF01C8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = FB81F088D9E90653987CAA036FACFD28; - remoteInfo = GRDB.swift; - }; - 3DE8392EBF9E858F78A3D0BC375D811B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = FB81F088D9E90653987CAA036FACFD28; - remoteInfo = GRDB.swift; - }; - 4017E5378AFE304FFFFB252C0F0305F9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B618582B07803EA12417D625ABB2CD21; - remoteInfo = MuslimData; - }; - 4F03C233FDF9A651A45430E56D389C40 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 031BFC8F009B7941500B46B8E0782A88; - remoteInfo = "Pods-MuslimData_Example"; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 00DD889D9B78450041FF5AEE58E86647 /* DatabaseValueConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseValueConvertible.swift; path = GRDB/Core/DatabaseValueConvertible.swift; sourceTree = ""; }; - 04275E4CF54FAA3C2A6859DDB193D04F /* DatabaseDateComponents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseDateComponents.swift; path = GRDB/Core/Support/Foundation/DatabaseDateComponents.swift; sourceTree = ""; }; - 054167CF407C51AC6687ADEBF914CDDC /* SQLAssociation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLAssociation.swift; path = GRDB/QueryInterface/SQL/SQLAssociation.swift; sourceTree = ""; }; - 061437BFF3E1C9882B07C7A0A6F60679 /* DateExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DateExtensions.swift; sourceTree = ""; }; - 0938A7247E2982B82482760B0AC6123F /* FTS5TokenizerDescriptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS5TokenizerDescriptor.swift; path = GRDB/FTS/FTS5TokenizerDescriptor.swift; sourceTree = ""; }; - 0A1BEECB0AD5695FC58BB42959C41DDA /* SQLiteDateParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLiteDateParser.swift; path = GRDB/Core/Support/Foundation/SQLiteDateParser.swift; sourceTree = ""; }; - 0C83188D9DFD01CA9D469EB06279767D /* DatabaseReader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseReader.swift; path = GRDB/Core/DatabaseReader.swift; sourceTree = ""; }; - 0D50A25ECF1C68729F9ECA92736E0517 /* Pods-MuslimData_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-MuslimData_Tests-umbrella.h"; sourceTree = ""; }; - 0F3F554C1B5DCD4EF089F605813FC5D3 /* FetchRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FetchRequest.swift; path = GRDB/Core/FetchRequest.swift; sourceTree = ""; }; - 112165439418B44C4BDCFB45D22CBE34 /* ValueObservation+FetchableRecord.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+FetchableRecord.swift"; path = "GRDB/ValueObservation/ValueObservation+FetchableRecord.swift"; sourceTree = ""; }; - 14A7257E831A9D87198C29B792FDF9B6 /* MuslimData.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MuslimData.xcconfig; sourceTree = ""; }; - 1585FD241A9AC0D79B3E084E1A8B7B77 /* DatabaseValueConvertible+RawRepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DatabaseValueConvertible+RawRepresentable.swift"; path = "GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+RawRepresentable.swift"; sourceTree = ""; }; - 1756861507925251CBACC0D005FE1912 /* ValueObserver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ValueObserver.swift; path = GRDB/ValueObservation/ValueObserver.swift; sourceTree = ""; }; - 1C77E730F441F579931C11BA8F749FDF /* DatabasePromise.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabasePromise.swift; path = GRDB/QueryInterface/SQL/DatabasePromise.swift; sourceTree = ""; }; - 1DE3C6FBEF1E948A8881FAC46607E0CE /* TableRecord+Association.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TableRecord+Association.swift"; path = "GRDB/QueryInterface/TableRecord+Association.swift"; sourceTree = ""; }; - 1E4B392CEDEDE8E47D3BD1ECC7EA551E /* NSNumber.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSNumber.swift; path = GRDB/Core/Support/Foundation/NSNumber.swift; sourceTree = ""; }; - 2058FFAD3AA3F5F1DA38DAB365079462 /* FetchableRecord+TableRecord.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FetchableRecord+TableRecord.swift"; path = "GRDB/Record/FetchableRecord+TableRecord.swift"; sourceTree = ""; }; - 208FD7047BA6E0490F50904E4F73EC84 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - 22BAAE27BDCE90B8C1F6B4992424D9C6 /* DatabaseCollation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseCollation.swift; path = GRDB/Core/DatabaseCollation.swift; sourceTree = ""; }; - 24A48FA30EC31431A33C17FAE91902EE /* DatabaseValueConvertible+Encodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DatabaseValueConvertible+Encodable.swift"; path = "GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Encodable.swift"; sourceTree = ""; }; - 25525D31CDCACA2AEE38C6A90DEDD714 /* Database.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Database.swift; path = GRDB/Core/Database.swift; sourceTree = ""; }; - 25DF833796CBC2D0F008C6638CF48BD2 /* Record.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Record.swift; path = GRDB/Record/Record.swift; sourceTree = ""; }; - 2846928F23EC085D3E5E028FFC5B1FF2 /* Database+Statements.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Database+Statements.swift"; path = "GRDB/Core/Database+Statements.swift"; sourceTree = ""; }; - 291780A00DC00211D75FBDEA9B09E764 /* SQLFunctions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLFunctions.swift; path = GRDB/QueryInterface/SQL/SQLFunctions.swift; sourceTree = ""; }; - 2BC5D21AC275156EB1E637182C566346 /* MuslimData.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MuslimData.modulemap; sourceTree = ""; }; - 2C8D1DE939367A8362484A58B33EB6DE /* DatabaseSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseSnapshot.swift; path = GRDB/Core/DatabaseSnapshot.swift; sourceTree = ""; }; - 2D0F97A42B813E8B003D46C5 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = MuslimData/Assets/CHANGELOG.md; sourceTree = ""; }; - 2D6CD44629CC5BF2009B527C /* muslim_db_v2.0.0.db */ = {isa = PBXFileReference; lastKnownFileType = file; name = muslim_db_v2.0.0.db; path = MuslimData/Assets/muslim_db_v2.0.0.db; sourceTree = ""; }; - 2DC6A83C7FE2580E3F2A1AFBB2B117E6 /* PrayerAttribute.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PrayerAttribute.swift; sourceTree = ""; }; - 2DE6E95B2BA8D8010082A3CE /* MuslimRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuslimRepository.swift; sourceTree = ""; }; - 2DFE19672BAB81E900665C6B /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; - 2E88CF46329DBA47B53B98F005E11EE3 /* MuslimData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MuslimData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2F75C56ED8BD1951FC8172C608B20303 /* Pods-MuslimData_Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MuslimData_Example-acknowledgements.plist"; sourceTree = ""; }; - 2FFBDBD6E148D8CD2D3E5D40636ABE46 /* AzkarItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AzkarItem.swift; sourceTree = ""; }; - 30678A7CEDBC6447D947E5D4EC9E1173 /* Pods-MuslimData_Tests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-MuslimData_Tests-dummy.m"; sourceTree = ""; }; - 3087A8A5BD99215CD4DB3460F8AFEA73 /* ReadWriteBox.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReadWriteBox.swift; path = GRDB/Utils/ReadWriteBox.swift; sourceTree = ""; }; - 32170F8226285700A097463A2C81B28E /* RowAdapter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RowAdapter.swift; path = GRDB/Core/RowAdapter.swift; sourceTree = ""; }; - 33E1BA72AB090848D099FDE15FFCD602 /* SQLGenerationContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLGenerationContext.swift; path = GRDB/QueryInterface/SQLGeneration/SQLGenerationContext.swift; sourceTree = ""; }; - 35291486FD48076A204DF52CCD6A72AC /* Location.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; }; - 353188DE84471B3F5021BFC563AD2E4A /* OrderedDictionary.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OrderedDictionary.swift; path = GRDB/Utils/OrderedDictionary.swift; sourceTree = ""; }; - 35BD16BE3201E2C0A1181407D25A3EBC /* Row+QueryInterfaceRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Row+QueryInterfaceRequest.swift"; path = "GRDB/QueryInterface/Row+QueryInterfaceRequest.swift"; sourceTree = ""; }; - 3708554DF1666EC6C41BBEB89E5AC257 /* URL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URL.swift; path = GRDB/Core/Support/Foundation/URL.swift; sourceTree = ""; }; - 374B2EE56EE7DAC67CCB2C047D622957 /* Configuration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Configuration.swift; path = GRDB/Core/Configuration.swift; sourceTree = ""; }; - 37C9ECD60B68221B76E253AA39B4B52E /* FTS3TokenizerDescriptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS3TokenizerDescriptor.swift; path = GRDB/FTS/FTS3TokenizerDescriptor.swift; sourceTree = ""; }; - 3BEF107E7E8091B90FAECDFC4C7A6954 /* Inflections+English.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Inflections+English.swift"; path = "GRDB/Utils/Inflections+English.swift"; sourceTree = ""; }; - 3CA2DEAEDF6FED5B07AE3779109C7886 /* DatabaseSchemaCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseSchemaCache.swift; path = GRDB/Core/DatabaseSchemaCache.swift; sourceTree = ""; }; - 3FF0BA87045E3E9FABE15B6BF3A8D5A2 /* FTS3.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS3.swift; path = GRDB/FTS/FTS3.swift; sourceTree = ""; }; - 401A16EADAEB6FE46A4F5411CCD2A9F3 /* Name.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Name.swift; sourceTree = ""; }; - 45C1864F172A3D81F9D25636ADB75ED9 /* FetchableRecord.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FetchableRecord.swift; path = GRDB/Record/FetchableRecord.swift; sourceTree = ""; }; - 45D4D0D01A44732C4E7F2C500AA45B55 /* GRDB.swift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GRDB.swift-prefix.pch"; sourceTree = ""; }; - 49192A23515A2FDF0209EBF9BA4DAB65 /* DatabaseError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseError.swift; path = GRDB/Core/DatabaseError.swift; sourceTree = ""; }; - 4970C5782878739D9F25CEE935961DA6 /* Inflections.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Inflections.swift; path = GRDB/Utils/Inflections.swift; sourceTree = ""; }; - 4C709D1799799F5A0E43E043E023415B /* UUID.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UUID.swift; path = GRDB/Core/Support/Foundation/UUID.swift; sourceTree = ""; }; - 4E8F40C80B8B85C4393ED5F43C8ED116 /* ValueObservation+DatabaseValueConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+DatabaseValueConvertible.swift"; path = "GRDB/ValueObservation/ValueObservation+DatabaseValueConvertible.swift"; sourceTree = ""; }; - 4EB99538C9E87FA9C44300FFCA0B04EE /* Statement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Statement.swift; path = GRDB/Core/Statement.swift; sourceTree = ""; }; - 518451B0E85374EA68E5B75FB38C27E1 /* DatabaseWriter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseWriter.swift; path = GRDB/Core/DatabaseWriter.swift; sourceTree = ""; }; - 5198A73019ED73FEB081FFEF93349F97 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 51AA10D7887DE3F10F9030EC1A2CC378 /* DatabaseQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseQueue.swift; path = GRDB/Core/DatabaseQueue.swift; sourceTree = ""; }; - 551E845F7B283CFF0E2D215372DA26A9 /* String+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; - 56CDF9772D8CD1E94A5E39433D191434 /* FTS5Tokenizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS5Tokenizer.swift; path = GRDB/FTS/FTS5Tokenizer.swift; sourceTree = ""; }; - 57083C76476CFFD9C6281EB0FA5CF562 /* GRDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRDB.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5779BFB97BB16F7C257E39CD28630594 /* FTS5+QueryInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FTS5+QueryInterface.swift"; path = "GRDB/QueryInterface/FTS5+QueryInterface.swift"; sourceTree = ""; }; - 5880470B1529D3998D951354FE8AAA99 /* SQLInterpolation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLInterpolation.swift; path = GRDB/Core/SQLInterpolation.swift; sourceTree = ""; }; - 58B9F1435B3801DADE391A4A5136738A /* ValueObservation+Count.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+Count.swift"; path = "GRDB/ValueObservation/ValueObservation+Count.swift"; sourceTree = ""; }; - 59FBE42988DD91F3D3C2B74D31996523 /* ValueObservation+CompactMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+CompactMap.swift"; path = "GRDB/ValueObservation/ValueObservation+CompactMap.swift"; sourceTree = ""; }; - 5A02DA8BFACEE9E07FBD62D157FED96B /* SQLSpecificExpressible+QueryInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SQLSpecificExpressible+QueryInterface.swift"; path = "GRDB/QueryInterface/SQL/SQLSpecificExpressible+QueryInterface.swift"; sourceTree = ""; }; - 5A2F3326B33A6BC66E13954DF736BEAA /* GRDB.swift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GRDB.swift-dummy.m"; sourceTree = ""; }; - 5E166D7E44FE057A6E248D6B423B0C81 /* DatabaseMigrator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseMigrator.swift; path = GRDB/Migration/DatabaseMigrator.swift; sourceTree = ""; }; - 5F5B13B296896404F68B81BDDB33598A /* SQLSelectable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLSelectable.swift; path = GRDB/QueryInterface/SQL/SQLSelectable.swift; sourceTree = ""; }; - 5F997529A98C052DA7C12591E52C94D6 /* FetchedRecordsController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FetchedRecordsController.swift; path = GRDB/Record/FetchedRecordsController.swift; sourceTree = ""; }; - 6127107FB86700DAE4E4C94A6B226FDF /* ValueReducer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ValueReducer.swift; path = GRDB/ValueObservation/ValueReducer.swift; sourceTree = ""; }; - 61F8ACBAFE985B333B35C4976084CBA2 /* Pods-MuslimData_Tests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-MuslimData_Tests.modulemap"; sourceTree = ""; }; - 62EE764DBA051C2839B7DD11850DA98C /* FTS5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS5.swift; path = GRDB/FTS/FTS5.swift; sourceTree = ""; }; - 6368FFE5512AD8AC9D36EB8427AFA109 /* PrayerTime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PrayerTime.swift; sourceTree = ""; }; - 65F554C03188A6F204B2036E5CF4B82D /* TimeFormat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TimeFormat.swift; sourceTree = ""; }; - 6627A4FEECAC11B54281F6CF5A53278A /* Pods-MuslimData_Tests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-MuslimData_Tests-acknowledgements.markdown"; sourceTree = ""; }; - 662BA0B191945070F192D329CA95496F /* BelongsToAssociation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BelongsToAssociation.swift; path = GRDB/QueryInterface/Request/Association/BelongsToAssociation.swift; sourceTree = ""; }; - 69D77E14A5BB1A5D3F2E9581ECBFE4B2 /* TableRecord.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableRecord.swift; path = GRDB/Record/TableRecord.swift; sourceTree = ""; }; - 6A0210CA581788DF572FE3501F94C639 /* Date.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Date.swift; path = GRDB/Core/Support/Foundation/Date.swift; sourceTree = ""; }; - 6A6A686C3D9502F7C4A132B40F0F72DD /* QueryInterfaceRequest+Association.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "QueryInterfaceRequest+Association.swift"; path = "GRDB/QueryInterface/Request/QueryInterfaceRequest+Association.swift"; sourceTree = ""; }; - 6AADE52C012DAEA6407BA8798B200F02 /* SQLExpression+QueryInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SQLExpression+QueryInterface.swift"; path = "GRDB/QueryInterface/SQL/SQLExpression+QueryInterface.swift"; sourceTree = ""; }; - 6C7CD5013FEE27004C7AF144E4470ADA /* Pods-MuslimData_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-MuslimData_Example-dummy.m"; sourceTree = ""; }; - 6DE2E8EEA85B8FB4DB536F2DDA9089D0 /* DatabaseRegionObservation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseRegionObservation.swift; path = GRDB/Core/DatabaseRegionObservation.swift; sourceTree = ""; }; - 6E4B190F2E830A1BB55FBA48ACEA7E86 /* Column.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Column.swift; path = GRDB/QueryInterface/SQL/Column.swift; sourceTree = ""; }; - 6F74C3288A30C090F9DAE356D7F8CEF9 /* HasOneAssociation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HasOneAssociation.swift; path = GRDB/QueryInterface/Request/Association/HasOneAssociation.swift; sourceTree = ""; }; - 7093F7382D260704506C5464AFBC16F5 /* SQLExpressible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLExpressible.swift; path = GRDB/QueryInterface/SQL/SQLExpressible.swift; sourceTree = ""; }; - 720E3E47CB7DDE45FA7550A02D097ACD /* MuslimData.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; path = MuslimData.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 730FF805483A83B2584256EEDC5D2639 /* Language.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; - 743A1901BFD326063BEDE3DD34614586 /* Pods-MuslimData_Tests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MuslimData_Tests-acknowledgements.plist"; sourceTree = ""; }; - 74E7C387B223D2421080A1FB6A9C4272 /* Pods-MuslimData_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MuslimData_Example.release.xcconfig"; sourceTree = ""; }; - 758A56E81E436BD44A71DB48A73695EC /* FetchableRecord+Decodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FetchableRecord+Decodable.swift"; path = "GRDB/Record/FetchableRecord+Decodable.swift"; sourceTree = ""; }; - 7B254E7B4418D6D38E004A377181618F /* Row.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Row.swift; path = GRDB/Core/Row.swift; sourceTree = ""; }; - 7DCF7B73D6DBD322B1124B77F7C28B41 /* SQLLiteral.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLLiteral.swift; path = GRDB/Core/SQLLiteral.swift; sourceTree = ""; }; - 7E7795BEC7ABA70F55DE00A1C340CC52 /* NSString.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSString.swift; path = GRDB/Core/Support/Foundation/NSString.swift; sourceTree = ""; }; - 7FD7232797ACF3F6F7685B58BCDC00C7 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - 7FFD2B745CCCE37869DFC4E8FA83908E /* GRDB.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GRDB.h; path = Support/GRDB.h; sourceTree = ""; }; - 852D3895437736AC3C233945C2E20301 /* HasManyThroughAssociation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HasManyThroughAssociation.swift; path = GRDB/QueryInterface/Request/Association/HasManyThroughAssociation.swift; sourceTree = ""; }; - 87F53F41F8B38930F8E492305ADD4866 /* FTS5CustomTokenizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS5CustomTokenizer.swift; path = GRDB/FTS/FTS5CustomTokenizer.swift; sourceTree = ""; }; - 8C5FC63DA22A393F7DC2691C13091E8F /* ValueObservation+Combine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+Combine.swift"; path = "GRDB/ValueObservation/ValueObservation+Combine.swift"; sourceTree = ""; }; - 8CD278A63004131343ECCF5C78B29392 /* FTS3+QueryInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FTS3+QueryInterface.swift"; path = "GRDB/QueryInterface/FTS3+QueryInterface.swift"; sourceTree = ""; }; - 8CFB400CA7F46BD8366FC26A1A6823E7 /* Database+Schema.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Database+Schema.swift"; path = "GRDB/Core/Database+Schema.swift"; sourceTree = ""; }; - 8E9C257C00F610EA08A4E186851F37D0 /* ValueObservation+MapReducer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+MapReducer.swift"; path = "GRDB/ValueObservation/ValueObservation+MapReducer.swift"; sourceTree = ""; }; - 8F2B8005D374F24A6943BAA1823F338B /* RequestProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestProtocols.swift; path = GRDB/QueryInterface/Request/RequestProtocols.swift; sourceTree = ""; }; - 92CAEAC5CC5B2720DC623C44C895C435 /* EncodableRecord+Encodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "EncodableRecord+Encodable.swift"; path = "GRDB/Record/EncodableRecord+Encodable.swift"; sourceTree = ""; }; - 94586B16D5D2DCA4B4E22C167DD17B61 /* AzkarChapter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AzkarChapter.swift; sourceTree = ""; }; - 948C2F723403EE0BBB55F1A5930F0722 /* MuslimData-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "MuslimData-Info.plist"; sourceTree = ""; }; - 94CE31B27E7F34691556AD03AFBB63F5 /* ValueObservation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ValueObservation.swift; path = GRDB/ValueObservation/ValueObservation.swift; sourceTree = ""; }; - 96925BE6CBF55EFBAD75586DB817EB24 /* PersistableRecord.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PersistableRecord.swift; path = GRDB/Record/PersistableRecord.swift; sourceTree = ""; }; - 9768AEB6008CAEF84324EC4C8938989B /* ValueObservation+Map.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+Map.swift"; path = "GRDB/ValueObservation/ValueObservation+Map.swift"; sourceTree = ""; }; - 98583347F707576BCEA189427FC47A69 /* AssociationAggregate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssociationAggregate.swift; path = GRDB/QueryInterface/Request/Association/AssociationAggregate.swift; sourceTree = ""; }; - 988514746A18BB7288E7AE2B074B7196 /* SQLExpression.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLExpression.swift; path = GRDB/QueryInterface/SQL/SQLExpression.swift; sourceTree = ""; }; - 9A0D52E8D92DD8E648D19B837936563A /* Pods_MuslimData_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MuslimData_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9AC1744C057858332AA73845B0CE26D7 /* DatabaseValueConvertible+Decodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DatabaseValueConvertible+Decodable.swift"; path = "GRDB/Core/Support/StandardLibrary/DatabaseValueConvertible+Decodable.swift"; sourceTree = ""; }; - 9B99EBD21496FBA605E3A375E86ED829 /* DatabaseValueConversion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseValueConversion.swift; path = GRDB/Core/DatabaseValueConversion.swift; sourceTree = ""; }; - 9C3DE7F469BC348F7C1DDC5196499B58 /* GRDB-Bridging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GRDB-Bridging.h"; path = "Support/GRDB-Bridging.h"; sourceTree = ""; }; - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9F05530485A897CD1D0A0A27D17A90BE /* Pods-MuslimData_Tests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MuslimData_Tests-Info.plist"; sourceTree = ""; }; - A49D0B537A24E7B6779BDF2FF4BF1376 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; - A83D32E01380A9B4A0B3817B6B914A51 /* StatementAuthorizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementAuthorizer.swift; path = GRDB/Core/StatementAuthorizer.swift; sourceTree = ""; }; - A8CA41092FF01838F5755D8533720F37 /* VirtualTableModule.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VirtualTableModule.swift; path = GRDB/QueryInterface/Schema/VirtualTableModule.swift; sourceTree = ""; }; - AA5BDCC7B5AB732EC9F8A60A48C1D510 /* SQLInterpolation+QueryInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SQLInterpolation+QueryInterface.swift"; path = "GRDB/QueryInterface/SQLInterpolation+QueryInterface.swift"; sourceTree = ""; }; - AB527B155158A83DC29C8AB471EF8300 /* TableRecord+QueryInterfaceRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TableRecord+QueryInterfaceRequest.swift"; path = "GRDB/QueryInterface/TableRecord+QueryInterfaceRequest.swift"; sourceTree = ""; }; - AD435788979CC448129314A6071C6725 /* Pods-MuslimData_Example-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MuslimData_Example-Info.plist"; sourceTree = ""; }; - AF2C6021F8D19343C38E11A931BD52EF /* SQLRelation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLRelation.swift; path = GRDB/QueryInterface/SQL/SQLRelation.swift; sourceTree = ""; }; - AFC609C58F86B050AED73E530BC3B6AB /* QueryInterfaceRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = QueryInterfaceRequest.swift; path = GRDB/QueryInterface/Request/QueryInterfaceRequest.swift; sourceTree = ""; }; - B053232FB4BC0D06EA67CAF95D29114D /* Pods-MuslimData_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MuslimData_Example.debug.xcconfig"; sourceTree = ""; }; - B0646069865C88FAA7E0EDB542E2ACE1 /* Pods-MuslimData_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-MuslimData_Example-umbrella.h"; sourceTree = ""; }; - B10666A74448608EAA8F42120078B6D5 /* Pods-MuslimData_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-MuslimData_Example-acknowledgements.markdown"; sourceTree = ""; }; - B2D8132A3643601639C3E59717516CA9 /* DatabasePool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabasePool.swift; path = GRDB/Core/DatabasePool.swift; sourceTree = ""; }; - B566BFB5B270273A425E6317B162D24F /* SQLOperators.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLOperators.swift; path = GRDB/QueryInterface/SQL/SQLOperators.swift; sourceTree = ""; }; - B5AFBABE58AAD9E968921664890D2D50 /* SQLQuery.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLQuery.swift; path = GRDB/QueryInterface/SQL/SQLQuery.swift; sourceTree = ""; }; - B65E8B14425B931ADE785452F8A6332B /* Association.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Association.swift; path = GRDB/QueryInterface/Request/Association/Association.swift; sourceTree = ""; }; - B6DD660E6576366AC16CF63F89BFD77B /* SchedulingWatchdog.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SchedulingWatchdog.swift; path = GRDB/Core/SchedulingWatchdog.swift; sourceTree = ""; }; - B6F5F76418FE68B0D427B7D08BF6A9A9 /* TransactionObserver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionObserver.swift; path = GRDB/Core/TransactionObserver.swift; sourceTree = ""; }; - B7398318D874AC392EB34280CF2E1899 /* Utils.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = GRDB/Utils/Utils.swift; sourceTree = ""; }; - B85117D4C9BE4B6480B1C89933815E58 /* Prayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Prayer.swift; sourceTree = ""; }; - BB2B3F9D2BF7D6132661A531E7BB2F12 /* AzkarCategory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AzkarCategory.swift; sourceTree = ""; }; - BCC7E5208F1383E700C4820E6EC5F37A /* Pods-MuslimData_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MuslimData_Tests.release.xcconfig"; sourceTree = ""; }; - BD7F2FEBDAA3F216BBB4AE11EC6585D6 /* SQLCollatedExpression.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLCollatedExpression.swift; path = GRDB/QueryInterface/SQL/SQLCollatedExpression.swift; sourceTree = ""; }; - BED95FFCB43990871E5873CFC4E1C053 /* SQLOrdering.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLOrdering.swift; path = GRDB/QueryInterface/SQL/SQLOrdering.swift; sourceTree = ""; }; - BF2ACFA1937DAE4E5205B6A924C830CB /* SQLCollection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLCollection.swift; path = GRDB/QueryInterface/SQL/SQLCollection.swift; sourceTree = ""; }; - BFD91E47F6DD6422D0952C8AC7623582 /* TableDefinition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableDefinition.swift; path = GRDB/QueryInterface/Schema/TableDefinition.swift; sourceTree = ""; }; - C39D8BF4F31FBF5B0CB7FF839884DE79 /* MuslimData-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MuslimData-prefix.pch"; sourceTree = ""; }; - C559E1EEE2B6FBA44944FEE8E9152DF0 /* HasOneThroughAssociation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HasOneThroughAssociation.swift; path = GRDB/QueryInterface/Request/Association/HasOneThroughAssociation.swift; sourceTree = ""; }; - C5F1092AFB734F6CA9EDCF3A32CA7041 /* FTS5Pattern.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS5Pattern.swift; path = GRDB/FTS/FTS5Pattern.swift; sourceTree = ""; }; - C6CDDA18855D4ADEBEFF1A3AE06CC5E3 /* SQLSelectable+QueryInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SQLSelectable+QueryInterface.swift"; path = "GRDB/QueryInterface/SQL/SQLSelectable+QueryInterface.swift"; sourceTree = ""; }; - C7AA8C9DAF31AD7B74AE9ED132C61DA8 /* FTS5WrapperTokenizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS5WrapperTokenizer.swift; path = GRDB/FTS/FTS5WrapperTokenizer.swift; sourceTree = ""; }; - C87A77728D7700424D943FDE619754A2 /* Pool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Pool.swift; path = GRDB/Utils/Pool.swift; sourceTree = ""; }; - C89F00D263328D7C901ADE57C1548021 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = GRDB/Utils/Result.swift; sourceTree = ""; }; - CC3E15AF93024917493AF2A2ED452332 /* ValueObservation+Row.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+Row.swift"; path = "GRDB/ValueObservation/ValueObservation+Row.swift"; sourceTree = ""; }; - CD5F36FEF53D52328747380C3D59D187 /* StatementColumnConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementColumnConvertible.swift; path = GRDB/Core/StatementColumnConvertible.swift; sourceTree = ""; }; - CDE5F32BB6F9734F44632A90954DC6F2 /* FTS3Pattern.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS3Pattern.swift; path = GRDB/FTS/FTS3Pattern.swift; sourceTree = ""; }; - CF0878EB04F3CEA389BCDB89EA02635B /* GRDB.swift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GRDB.swift.modulemap; sourceTree = ""; }; - D1A115FEA22E3B697DDD8CB0CF3FCAD9 /* DatabaseValueConvertible+ReferenceConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DatabaseValueConvertible+ReferenceConvertible.swift"; path = "GRDB/Core/Support/Foundation/DatabaseValueConvertible+ReferenceConvertible.swift"; sourceTree = ""; }; - D425DD46CBA81B4F5985DBFD029CC0EE /* GRDB.swift.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GRDB.swift.xcconfig; sourceTree = ""; }; - D451D66CDD9B42CD20A5663E52C4ECED /* Pods-MuslimData_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-MuslimData_Example-frameworks.sh"; sourceTree = ""; }; - D5A9AF286580ABB38F1448D6568171C6 /* DBHelper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DBHelper.swift; sourceTree = ""; }; - D6E40BE124BC4154995B1B2A7A660D03 /* NSData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSData.swift; path = GRDB/Core/Support/Foundation/NSData.swift; sourceTree = ""; }; - D89DD2484314D8A3D5ADE1D158C85756 /* DatabaseValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseValue.swift; path = GRDB/Core/DatabaseValue.swift; sourceTree = ""; }; - DBD0CA7B983012A8B03615622D294247 /* Data.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Data.swift; path = GRDB/Core/Support/Foundation/Data.swift; sourceTree = ""; }; - DC8CAE41207247FFE6C23DD13296EA70 /* ValueObservation+DistinctUntilChanged.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ValueObservation+DistinctUntilChanged.swift"; path = "GRDB/ValueObservation/ValueObservation+DistinctUntilChanged.swift"; sourceTree = ""; }; - DE941C1A7A95F921000AFDFD91F54541 /* DatabaseFunction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseFunction.swift; path = GRDB/Core/DatabaseFunction.swift; sourceTree = ""; }; - E190980D696557F2DD2A9B526828DF03 /* FTS4.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FTS4.swift; path = GRDB/FTS/FTS4.swift; sourceTree = ""; }; - E1B0453AB3FD95A926B256E8D21F72D9 /* MuslimData-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "MuslimData-dummy.m"; sourceTree = ""; }; - E373F54B34D5D14C05965571F0559EDD /* GRDB.swift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GRDB.swift-Info.plist"; sourceTree = ""; }; - E375E483DE2FE415F9FEA19C9669B526 /* SerializedDatabase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SerializedDatabase.swift; path = GRDB/Core/SerializedDatabase.swift; sourceTree = ""; }; - E3BABC3D3DC05C7FD6865D180C0A432D /* SQLQueryGenerator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLQueryGenerator.swift; path = GRDB/QueryInterface/SQLGeneration/SQLQueryGenerator.swift; sourceTree = ""; }; - E3E4C6CC7D33B6BEE3483F48A38E6A45 /* NSNull.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSNull.swift; path = GRDB/Core/Support/Foundation/NSNull.swift; sourceTree = ""; }; - E73BE27477C2865305F88857F860D067 /* HasManyAssociation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HasManyAssociation.swift; path = GRDB/QueryInterface/Request/Association/HasManyAssociation.swift; sourceTree = ""; }; - E8180C67106D338B3A2A64D08774B6D9 /* CGFloat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGFloat.swift; path = GRDB/Core/Support/CoreGraphics/CGFloat.swift; sourceTree = ""; }; - E8487A5B3066D2B150E82D87B277D480 /* Pods_MuslimData_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MuslimData_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E9488B06541B325219BAE37A334A1E59 /* DatabaseRegion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DatabaseRegion.swift; path = GRDB/Core/DatabaseRegion.swift; sourceTree = ""; }; - EA0B8A534766D304CF9DE1A5C2C35051 /* GRDB-4.0.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "GRDB-4.0.swift"; path = "GRDB/Fixit/GRDB-4.0.swift"; sourceTree = ""; }; - EABE51A24C723C856DD1DCC752712250 /* StandardLibrary.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StandardLibrary.swift; path = GRDB/Core/Support/StandardLibrary/StandardLibrary.swift; sourceTree = ""; }; - ED2B193D223BFC78855A3B1DE8CED395 /* FetchableRecord+QueryInterfaceRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FetchableRecord+QueryInterfaceRequest.swift"; path = "GRDB/QueryInterface/FetchableRecord+QueryInterfaceRequest.swift"; sourceTree = ""; }; - EE233D66235457A95C4FDD1BC230A497 /* Cursor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Cursor.swift; path = GRDB/Core/Cursor.swift; sourceTree = ""; }; - EEC87C5D70057CE21632F0E08FAAF615 /* MuslimData-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MuslimData-umbrella.h"; sourceTree = ""; }; - F04EB54A8111D289DE44844E4A57E6B3 /* SQLRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLRequest.swift; path = GRDB/Core/SQLRequest.swift; sourceTree = ""; }; - F114854873706F9F005E2BB1FD9D4C65 /* EncodableRecord.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EncodableRecord.swift; path = GRDB/Record/EncodableRecord.swift; sourceTree = ""; }; - F27BA0AAB3F0B8F0F6FBFD49B7969EA9 /* SQLForeignKeyRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SQLForeignKeyRequest.swift; path = GRDB/QueryInterface/SQL/SQLForeignKeyRequest.swift; sourceTree = ""; }; - F4E91BB21A1925C004A070AB895D6B14 /* grdb_config.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = grdb_config.h; path = Support/grdb_config.h; sourceTree = ""; }; - F7AD95BB7089C067668FE8E0C1262934 /* GRDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRDB.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - FB29E142D18858D1242D2F378682A8F8 /* Pods-MuslimData_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MuslimData_Tests.debug.xcconfig"; sourceTree = ""; }; - FCEC1934853314773A885F07F9B3BF31 /* Pods-MuslimData_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-MuslimData_Example.modulemap"; sourceTree = ""; }; - FF596B48C538AB646A0EAF252A33E089 /* Migration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Migration.swift; path = GRDB/Migration/Migration.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 0D23A32B323C417A15285A7F385E85AF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D754D61FF444A9A90C1251F315B5C29 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B411DE3B25D665AE58DF39FDE4CE1A94 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 9A7995D89E2503F59B83841B83A6A3A9 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D4B5FD3802E3D47D8F15954F7F057354 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2DAF0C709FAF6D288751DB00EB06B40C /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - E478433A023C0C1A0476B627A242F172 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - BE5A72CE583F45FF74F5334921FF9968 /* Foundation.framework in Frameworks */, - 538BFA3A72212A24728F903FD732CE4F /* GRDB.framework in Frameworks */, - 1958078CF71CEA9143902886C5B9C310 /* UIKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 0540E1C8A51BDA08F9803F4F5C81C4A9 /* DB Helper */ = { - isa = PBXGroup; - children = ( - D5A9AF286580ABB38F1448D6568171C6 /* DBHelper.swift */, - ); - name = "DB Helper"; - path = "MuslimData/Classes/DB Helper"; - sourceTree = ""; - }; - 068C7933E1034C5E720F2FEE596A2051 /* Extensions */ = { - isa = PBXGroup; - children = ( - 061437BFF3E1C9882B07C7A0A6F60679 /* DateExtensions.swift */, - 551E845F7B283CFF0E2D215372DA26A9 /* String+Extensions.swift */, - ); - name = Extensions; - path = MuslimData/Classes/Extensions; - sourceTree = ""; - }; - 0A8C8B1A53515417C6F57ECB1DB76072 /* Support Files */ = { - isa = PBXGroup; - children = ( - CF0878EB04F3CEA389BCDB89EA02635B /* GRDB.swift.modulemap */, - D425DD46CBA81B4F5985DBFD029CC0EE /* GRDB.swift.xcconfig */, - 5A2F3326B33A6BC66E13954DF736BEAA /* GRDB.swift-dummy.m */, - E373F54B34D5D14C05965571F0559EDD /* GRDB.swift-Info.plist */, - 45D4D0D01A44732C4E7F2C500AA45B55 /* GRDB.swift-prefix.pch */, - ); - name = "Support Files"; - path = "../Target Support Files/GRDB.swift"; - sourceTree = ""; - }; - 176ED022F62840DFBB6098DE94236B35 /* Support Files */ = { - isa = PBXGroup; - children = ( - 2BC5D21AC275156EB1E637182C566346 /* MuslimData.modulemap */, - 14A7257E831A9D87198C29B792FDF9B6 /* MuslimData.xcconfig */, - E1B0453AB3FD95A926B256E8D21F72D9 /* MuslimData-dummy.m */, - 948C2F723403EE0BBB55F1A5930F0722 /* MuslimData-Info.plist */, - C39D8BF4F31FBF5B0CB7FF839884DE79 /* MuslimData-prefix.pch */, - EEC87C5D70057CE21632F0E08FAAF615 /* MuslimData-umbrella.h */, - ); - name = "Support Files"; - path = "Example/Pods/Target Support Files/MuslimData"; - sourceTree = ""; - }; - 2817A6C16C274658D2455BB0739C199E /* Location */ = { - isa = PBXGroup; - children = ( - 35291486FD48076A204DF52CCD6A72AC /* Location.swift */, - ); - path = Location; - sourceTree = ""; - }; - 2B81C12C52C5CB2D888908B65FF9D6DD /* GRDB.swift */ = { - isa = PBXGroup; - children = ( - 4022D06EB0FD32BF668EE12AF2306B41 /* standard */, - 0A8C8B1A53515417C6F57ECB1DB76072 /* Support Files */, - ); - path = GRDB.swift; - sourceTree = ""; - }; - 2DE6E95A2BA8D75D0082A3CE /* Repository */ = { - isa = PBXGroup; - children = ( - 2DFE19672BAB81E900665C6B /* Repository.swift */, - 2DE6E95B2BA8D8010082A3CE /* MuslimRepository.swift */, - ); - name = Repository; - path = MuslimData/Classes/Repository; - sourceTree = ""; - }; - 2DE6E95D2BA8F1F30082A3CE /* Models */ = { - isa = PBXGroup; - children = ( - 5B32A0A6C3385773AA423D017A5BFE73 /* Azkars */, - 8D92D353CC7766D3072B3133A23D2705 /* Names */, - 2817A6C16C274658D2455BB0739C199E /* Location */, - F45771AD65677FDC2506D34994A89145 /* PrayerTimes */, - ); - name = Models; - path = MuslimData/Classes/Models; - sourceTree = ""; - }; - 4022D06EB0FD32BF668EE12AF2306B41 /* standard */ = { - isa = PBXGroup; - children = ( - B65E8B14425B931ADE785452F8A6332B /* Association.swift */, - 98583347F707576BCEA189427FC47A69 /* AssociationAggregate.swift */, - 662BA0B191945070F192D329CA95496F /* BelongsToAssociation.swift */, - E8180C67106D338B3A2A64D08774B6D9 /* CGFloat.swift */, - 6E4B190F2E830A1BB55FBA48ACEA7E86 /* Column.swift */, - 374B2EE56EE7DAC67CCB2C047D622957 /* Configuration.swift */, - EE233D66235457A95C4FDD1BC230A497 /* Cursor.swift */, - DBD0CA7B983012A8B03615622D294247 /* Data.swift */, - 25525D31CDCACA2AEE38C6A90DEDD714 /* Database.swift */, - 8CFB400CA7F46BD8366FC26A1A6823E7 /* Database+Schema.swift */, - 2846928F23EC085D3E5E028FFC5B1FF2 /* Database+Statements.swift */, - 22BAAE27BDCE90B8C1F6B4992424D9C6 /* DatabaseCollation.swift */, - 04275E4CF54FAA3C2A6859DDB193D04F /* DatabaseDateComponents.swift */, - 49192A23515A2FDF0209EBF9BA4DAB65 /* DatabaseError.swift */, - DE941C1A7A95F921000AFDFD91F54541 /* DatabaseFunction.swift */, - 5E166D7E44FE057A6E248D6B423B0C81 /* DatabaseMigrator.swift */, - B2D8132A3643601639C3E59717516CA9 /* DatabasePool.swift */, - 1C77E730F441F579931C11BA8F749FDF /* DatabasePromise.swift */, - 51AA10D7887DE3F10F9030EC1A2CC378 /* DatabaseQueue.swift */, - 0C83188D9DFD01CA9D469EB06279767D /* DatabaseReader.swift */, - E9488B06541B325219BAE37A334A1E59 /* DatabaseRegion.swift */, - 6DE2E8EEA85B8FB4DB536F2DDA9089D0 /* DatabaseRegionObservation.swift */, - 3CA2DEAEDF6FED5B07AE3779109C7886 /* DatabaseSchemaCache.swift */, - 2C8D1DE939367A8362484A58B33EB6DE /* DatabaseSnapshot.swift */, - D89DD2484314D8A3D5ADE1D158C85756 /* DatabaseValue.swift */, - 9B99EBD21496FBA605E3A375E86ED829 /* DatabaseValueConversion.swift */, - 00DD889D9B78450041FF5AEE58E86647 /* DatabaseValueConvertible.swift */, - 9AC1744C057858332AA73845B0CE26D7 /* DatabaseValueConvertible+Decodable.swift */, - 24A48FA30EC31431A33C17FAE91902EE /* DatabaseValueConvertible+Encodable.swift */, - 1585FD241A9AC0D79B3E084E1A8B7B77 /* DatabaseValueConvertible+RawRepresentable.swift */, - D1A115FEA22E3B697DDD8CB0CF3FCAD9 /* DatabaseValueConvertible+ReferenceConvertible.swift */, - 518451B0E85374EA68E5B75FB38C27E1 /* DatabaseWriter.swift */, - 6A0210CA581788DF572FE3501F94C639 /* Date.swift */, - F114854873706F9F005E2BB1FD9D4C65 /* EncodableRecord.swift */, - 92CAEAC5CC5B2720DC623C44C895C435 /* EncodableRecord+Encodable.swift */, - 45C1864F172A3D81F9D25636ADB75ED9 /* FetchableRecord.swift */, - 758A56E81E436BD44A71DB48A73695EC /* FetchableRecord+Decodable.swift */, - ED2B193D223BFC78855A3B1DE8CED395 /* FetchableRecord+QueryInterfaceRequest.swift */, - 2058FFAD3AA3F5F1DA38DAB365079462 /* FetchableRecord+TableRecord.swift */, - 5F997529A98C052DA7C12591E52C94D6 /* FetchedRecordsController.swift */, - 0F3F554C1B5DCD4EF089F605813FC5D3 /* FetchRequest.swift */, - 3FF0BA87045E3E9FABE15B6BF3A8D5A2 /* FTS3.swift */, - 8CD278A63004131343ECCF5C78B29392 /* FTS3+QueryInterface.swift */, - CDE5F32BB6F9734F44632A90954DC6F2 /* FTS3Pattern.swift */, - 37C9ECD60B68221B76E253AA39B4B52E /* FTS3TokenizerDescriptor.swift */, - E190980D696557F2DD2A9B526828DF03 /* FTS4.swift */, - 62EE764DBA051C2839B7DD11850DA98C /* FTS5.swift */, - 5779BFB97BB16F7C257E39CD28630594 /* FTS5+QueryInterface.swift */, - 87F53F41F8B38930F8E492305ADD4866 /* FTS5CustomTokenizer.swift */, - C5F1092AFB734F6CA9EDCF3A32CA7041 /* FTS5Pattern.swift */, - 56CDF9772D8CD1E94A5E39433D191434 /* FTS5Tokenizer.swift */, - 0938A7247E2982B82482760B0AC6123F /* FTS5TokenizerDescriptor.swift */, - C7AA8C9DAF31AD7B74AE9ED132C61DA8 /* FTS5WrapperTokenizer.swift */, - 7FFD2B745CCCE37869DFC4E8FA83908E /* GRDB.h */, - EA0B8A534766D304CF9DE1A5C2C35051 /* GRDB-4.0.swift */, - 9C3DE7F469BC348F7C1DDC5196499B58 /* GRDB-Bridging.h */, - F4E91BB21A1925C004A070AB895D6B14 /* grdb_config.h */, - E73BE27477C2865305F88857F860D067 /* HasManyAssociation.swift */, - 852D3895437736AC3C233945C2E20301 /* HasManyThroughAssociation.swift */, - 6F74C3288A30C090F9DAE356D7F8CEF9 /* HasOneAssociation.swift */, - C559E1EEE2B6FBA44944FEE8E9152DF0 /* HasOneThroughAssociation.swift */, - 4970C5782878739D9F25CEE935961DA6 /* Inflections.swift */, - 3BEF107E7E8091B90FAECDFC4C7A6954 /* Inflections+English.swift */, - FF596B48C538AB646A0EAF252A33E089 /* Migration.swift */, - D6E40BE124BC4154995B1B2A7A660D03 /* NSData.swift */, - E3E4C6CC7D33B6BEE3483F48A38E6A45 /* NSNull.swift */, - 1E4B392CEDEDE8E47D3BD1ECC7EA551E /* NSNumber.swift */, - 7E7795BEC7ABA70F55DE00A1C340CC52 /* NSString.swift */, - 353188DE84471B3F5021BFC563AD2E4A /* OrderedDictionary.swift */, - 96925BE6CBF55EFBAD75586DB817EB24 /* PersistableRecord.swift */, - C87A77728D7700424D943FDE619754A2 /* Pool.swift */, - AFC609C58F86B050AED73E530BC3B6AB /* QueryInterfaceRequest.swift */, - 6A6A686C3D9502F7C4A132B40F0F72DD /* QueryInterfaceRequest+Association.swift */, - 3087A8A5BD99215CD4DB3460F8AFEA73 /* ReadWriteBox.swift */, - 25DF833796CBC2D0F008C6638CF48BD2 /* Record.swift */, - 8F2B8005D374F24A6943BAA1823F338B /* RequestProtocols.swift */, - C89F00D263328D7C901ADE57C1548021 /* Result.swift */, - 7B254E7B4418D6D38E004A377181618F /* Row.swift */, - 35BD16BE3201E2C0A1181407D25A3EBC /* Row+QueryInterfaceRequest.swift */, - 32170F8226285700A097463A2C81B28E /* RowAdapter.swift */, - B6DD660E6576366AC16CF63F89BFD77B /* SchedulingWatchdog.swift */, - E375E483DE2FE415F9FEA19C9669B526 /* SerializedDatabase.swift */, - 054167CF407C51AC6687ADEBF914CDDC /* SQLAssociation.swift */, - BD7F2FEBDAA3F216BBB4AE11EC6585D6 /* SQLCollatedExpression.swift */, - BF2ACFA1937DAE4E5205B6A924C830CB /* SQLCollection.swift */, - 7093F7382D260704506C5464AFBC16F5 /* SQLExpressible.swift */, - 988514746A18BB7288E7AE2B074B7196 /* SQLExpression.swift */, - 6AADE52C012DAEA6407BA8798B200F02 /* SQLExpression+QueryInterface.swift */, - F27BA0AAB3F0B8F0F6FBFD49B7969EA9 /* SQLForeignKeyRequest.swift */, - 291780A00DC00211D75FBDEA9B09E764 /* SQLFunctions.swift */, - 33E1BA72AB090848D099FDE15FFCD602 /* SQLGenerationContext.swift */, - 5880470B1529D3998D951354FE8AAA99 /* SQLInterpolation.swift */, - AA5BDCC7B5AB732EC9F8A60A48C1D510 /* SQLInterpolation+QueryInterface.swift */, - 0A1BEECB0AD5695FC58BB42959C41DDA /* SQLiteDateParser.swift */, - 7DCF7B73D6DBD322B1124B77F7C28B41 /* SQLLiteral.swift */, - B566BFB5B270273A425E6317B162D24F /* SQLOperators.swift */, - BED95FFCB43990871E5873CFC4E1C053 /* SQLOrdering.swift */, - B5AFBABE58AAD9E968921664890D2D50 /* SQLQuery.swift */, - E3BABC3D3DC05C7FD6865D180C0A432D /* SQLQueryGenerator.swift */, - AF2C6021F8D19343C38E11A931BD52EF /* SQLRelation.swift */, - F04EB54A8111D289DE44844E4A57E6B3 /* SQLRequest.swift */, - 5F5B13B296896404F68B81BDDB33598A /* SQLSelectable.swift */, - C6CDDA18855D4ADEBEFF1A3AE06CC5E3 /* SQLSelectable+QueryInterface.swift */, - 5A02DA8BFACEE9E07FBD62D157FED96B /* SQLSpecificExpressible+QueryInterface.swift */, - EABE51A24C723C856DD1DCC752712250 /* StandardLibrary.swift */, - 4EB99538C9E87FA9C44300FFCA0B04EE /* Statement.swift */, - A83D32E01380A9B4A0B3817B6B914A51 /* StatementAuthorizer.swift */, - CD5F36FEF53D52328747380C3D59D187 /* StatementColumnConvertible.swift */, - BFD91E47F6DD6422D0952C8AC7623582 /* TableDefinition.swift */, - 69D77E14A5BB1A5D3F2E9581ECBFE4B2 /* TableRecord.swift */, - 1DE3C6FBEF1E948A8881FAC46607E0CE /* TableRecord+Association.swift */, - AB527B155158A83DC29C8AB471EF8300 /* TableRecord+QueryInterfaceRequest.swift */, - B6F5F76418FE68B0D427B7D08BF6A9A9 /* TransactionObserver.swift */, - 3708554DF1666EC6C41BBEB89E5AC257 /* URL.swift */, - B7398318D874AC392EB34280CF2E1899 /* Utils.swift */, - 4C709D1799799F5A0E43E043E023415B /* UUID.swift */, - 94CE31B27E7F34691556AD03AFBB63F5 /* ValueObservation.swift */, - 8C5FC63DA22A393F7DC2691C13091E8F /* ValueObservation+Combine.swift */, - 59FBE42988DD91F3D3C2B74D31996523 /* ValueObservation+CompactMap.swift */, - 58B9F1435B3801DADE391A4A5136738A /* ValueObservation+Count.swift */, - 4E8F40C80B8B85C4393ED5F43C8ED116 /* ValueObservation+DatabaseValueConvertible.swift */, - DC8CAE41207247FFE6C23DD13296EA70 /* ValueObservation+DistinctUntilChanged.swift */, - 112165439418B44C4BDCFB45D22CBE34 /* ValueObservation+FetchableRecord.swift */, - 9768AEB6008CAEF84324EC4C8938989B /* ValueObservation+Map.swift */, - 8E9C257C00F610EA08A4E186851F37D0 /* ValueObservation+MapReducer.swift */, - CC3E15AF93024917493AF2A2ED452332 /* ValueObservation+Row.swift */, - 1756861507925251CBACC0D005FE1912 /* ValueObserver.swift */, - 6127107FB86700DAE4E4C94A6B226FDF /* ValueReducer.swift */, - A8CA41092FF01838F5755D8533720F37 /* VirtualTableModule.swift */, - ); - name = standard; - sourceTree = ""; - }; - 56E59EAC7BD785ACAD5B73A9F16A4562 /* Targets Support Files */ = { - isa = PBXGroup; - children = ( - BC6A3109B0382DD7ED30F7B029C8D579 /* Pods-MuslimData_Example */, - 8433FF044CF8AAB9BB28545CD240BCBE /* Pods-MuslimData_Tests */, - ); - name = "Targets Support Files"; - sourceTree = ""; - }; - 5B32A0A6C3385773AA423D017A5BFE73 /* Azkars */ = { - isa = PBXGroup; - children = ( - BB2B3F9D2BF7D6132661A531E7BB2F12 /* AzkarCategory.swift */, - 94586B16D5D2DCA4B4E22C167DD17B61 /* AzkarChapter.swift */, - 2FFBDBD6E148D8CD2D3E5D40636ABE46 /* AzkarItem.swift */, - ); - path = Azkars; - sourceTree = ""; - }; - 6A7E60A4A85B5746C77AA59AF4549B00 /* Development Pods */ = { - isa = PBXGroup; - children = ( - D6987B66A5B198235A8BB606A32FC336 /* MuslimData */, - ); - name = "Development Pods"; - sourceTree = ""; - }; - 6C85BC022E11396A542DB08BF82032BE /* Pod */ = { - isa = PBXGroup; - children = ( - 208FD7047BA6E0490F50904E4F73EC84 /* LICENSE */, - 720E3E47CB7DDE45FA7550A02D097ACD /* MuslimData.podspec */, - 5198A73019ED73FEB081FFEF93349F97 /* README.md */, - ); - name = Pod; - sourceTree = ""; - }; - 8433FF044CF8AAB9BB28545CD240BCBE /* Pods-MuslimData_Tests */ = { - isa = PBXGroup; - children = ( - 61F8ACBAFE985B333B35C4976084CBA2 /* Pods-MuslimData_Tests.modulemap */, - 6627A4FEECAC11B54281F6CF5A53278A /* Pods-MuslimData_Tests-acknowledgements.markdown */, - 743A1901BFD326063BEDE3DD34614586 /* Pods-MuslimData_Tests-acknowledgements.plist */, - 30678A7CEDBC6447D947E5D4EC9E1173 /* Pods-MuslimData_Tests-dummy.m */, - 9F05530485A897CD1D0A0A27D17A90BE /* Pods-MuslimData_Tests-Info.plist */, - 0D50A25ECF1C68729F9ECA92736E0517 /* Pods-MuslimData_Tests-umbrella.h */, - FB29E142D18858D1242D2F378682A8F8 /* Pods-MuslimData_Tests.debug.xcconfig */, - BCC7E5208F1383E700C4820E6EC5F37A /* Pods-MuslimData_Tests.release.xcconfig */, - ); - name = "Pods-MuslimData_Tests"; - path = "Target Support Files/Pods-MuslimData_Tests"; - sourceTree = ""; - }; - 866E63CE55E402583D758698042106F7 /* iOS */ = { - isa = PBXGroup; - children = ( - 7FD7232797ACF3F6F7685B58BCDC00C7 /* Foundation.framework */, - A49D0B537A24E7B6779BDF2FF4BF1376 /* UIKit.framework */, - ); - name = iOS; - sourceTree = ""; - }; - 8D92D353CC7766D3072B3133A23D2705 /* Names */ = { - isa = PBXGroup; - children = ( - 730FF805483A83B2584256EEDC5D2639 /* Language.swift */, - 401A16EADAEB6FE46A4F5411CCD2A9F3 /* Name.swift */, - ); - path = Names; - sourceTree = ""; - }; - A08711B133A02CDCC7C1F09A0D69CB4A /* Products */ = { - isa = PBXGroup; - children = ( - 57083C76476CFFD9C6281EB0FA5CF562 /* GRDB.framework */, - 2E88CF46329DBA47B53B98F005E11EE3 /* MuslimData.framework */, - E8487A5B3066D2B150E82D87B277D480 /* Pods_MuslimData_Example.framework */, - 9A0D52E8D92DD8E648D19B837936563A /* Pods_MuslimData_Tests.framework */, - ); - name = Products; - sourceTree = ""; - }; - B7C590C380D5FD85D98A6911C4AD19CF /* Pods */ = { - isa = PBXGroup; - children = ( - 2B81C12C52C5CB2D888908B65FF9D6DD /* GRDB.swift */, - ); - name = Pods; - sourceTree = ""; - }; - BC6A3109B0382DD7ED30F7B029C8D579 /* Pods-MuslimData_Example */ = { - isa = PBXGroup; - children = ( - FCEC1934853314773A885F07F9B3BF31 /* Pods-MuslimData_Example.modulemap */, - B10666A74448608EAA8F42120078B6D5 /* Pods-MuslimData_Example-acknowledgements.markdown */, - 2F75C56ED8BD1951FC8172C608B20303 /* Pods-MuslimData_Example-acknowledgements.plist */, - 6C7CD5013FEE27004C7AF144E4470ADA /* Pods-MuslimData_Example-dummy.m */, - D451D66CDD9B42CD20A5663E52C4ECED /* Pods-MuslimData_Example-frameworks.sh */, - AD435788979CC448129314A6071C6725 /* Pods-MuslimData_Example-Info.plist */, - B0646069865C88FAA7E0EDB542E2ACE1 /* Pods-MuslimData_Example-umbrella.h */, - B053232FB4BC0D06EA67CAF95D29114D /* Pods-MuslimData_Example.debug.xcconfig */, - 74E7C387B223D2421080A1FB6A9C4272 /* Pods-MuslimData_Example.release.xcconfig */, - ); - name = "Pods-MuslimData_Example"; - path = "Target Support Files/Pods-MuslimData_Example"; - sourceTree = ""; - }; - CF1408CF629C7361332E53B88F7BD30C = { - isa = PBXGroup; - children = ( - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - 6A7E60A4A85B5746C77AA59AF4549B00 /* Development Pods */, - CF8934902F6F6F4DED9F9936B5B2FFC5 /* Frameworks */, - B7C590C380D5FD85D98A6911C4AD19CF /* Pods */, - A08711B133A02CDCC7C1F09A0D69CB4A /* Products */, - 56E59EAC7BD785ACAD5B73A9F16A4562 /* Targets Support Files */, - ); - sourceTree = ""; - }; - CF862A95B5D3FFC023257833F1193CE5 /* Resources */ = { - isa = PBXGroup; - children = ( - 2D6CD44629CC5BF2009B527C /* muslim_db_v2.0.0.db */, - 2D0F97A42B813E8B003D46C5 /* CHANGELOG.md */, - ); - name = Resources; - sourceTree = ""; - }; - CF8934902F6F6F4DED9F9936B5B2FFC5 /* Frameworks */ = { - isa = PBXGroup; - children = ( - F7AD95BB7089C067668FE8E0C1262934 /* GRDB.framework */, - 866E63CE55E402583D758698042106F7 /* iOS */, - ); - name = Frameworks; - sourceTree = ""; - }; - D6987B66A5B198235A8BB606A32FC336 /* MuslimData */ = { - isa = PBXGroup; - children = ( - 0540E1C8A51BDA08F9803F4F5C81C4A9 /* DB Helper */, - 068C7933E1034C5E720F2FEE596A2051 /* Extensions */, - 2DE6E95D2BA8F1F30082A3CE /* Models */, - 2DE6E95A2BA8D75D0082A3CE /* Repository */, - 6C85BC022E11396A542DB08BF82032BE /* Pod */, - CF862A95B5D3FFC023257833F1193CE5 /* Resources */, - 176ED022F62840DFBB6098DE94236B35 /* Support Files */, - ); - name = MuslimData; - path = ../..; - sourceTree = ""; - }; - F45771AD65677FDC2506D34994A89145 /* PrayerTimes */ = { - isa = PBXGroup; - children = ( - B85117D4C9BE4B6480B1C89933815E58 /* Prayer.swift */, - 2DC6A83C7FE2580E3F2A1AFBB2B117E6 /* PrayerAttribute.swift */, - 6368FFE5512AD8AC9D36EB8427AFA109 /* PrayerTime.swift */, - 65F554C03188A6F204B2036E5CF4B82D /* TimeFormat.swift */, - ); - path = PrayerTimes; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 50A49D44D6C6A6A80B9AF3C785EDBBBB /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - F6572EE8D863DEBB27A18DC85EFF9C93 /* Pods-MuslimData_Example-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 5DFB9A73B871F2FB2A9C161679AA7978 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - E48C905B8E714A91CF886741081C1685 /* MuslimData-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 83EA7FD93CDD3F45C25FFE10005193BC /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 5F76D077FDBDB17308D80FBCDDD32F35 /* Pods-MuslimData_Tests-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A78FD3C6EB076C2D222749DACE114640 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 19EAAEBC608AA44323ABEE5E5C433F82 /* GRDB-Bridging.h in Headers */, - 296E2869F5F3E220C5132E55AF60E2D3 /* GRDB.h in Headers */, - C184186D7F480467C879626B3D8BF92A /* grdb_config.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 031BFC8F009B7941500B46B8E0782A88 /* Pods-MuslimData_Example */ = { - isa = PBXNativeTarget; - buildConfigurationList = D6C426F6F52203D774BAE97512281049 /* Build configuration list for PBXNativeTarget "Pods-MuslimData_Example" */; - buildPhases = ( - 50A49D44D6C6A6A80B9AF3C785EDBBBB /* Headers */, - 9708DE97B859FC1A35B3A97953DD0119 /* Sources */, - 0D23A32B323C417A15285A7F385E85AF /* Frameworks */, - 0A2747FD87CF911E39B73E577A2E3074 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - AFFED11E9996F77200F59F6E233634E1 /* PBXTargetDependency */, - F1CCD1F0A0AE20FC5678895260D2C755 /* PBXTargetDependency */, - ); - name = "Pods-MuslimData_Example"; - productName = "Pods-MuslimData_Example"; - productReference = E8487A5B3066D2B150E82D87B277D480 /* Pods_MuslimData_Example.framework */; - productType = "com.apple.product-type.framework"; - }; - 29306E3D5E7D49EB7D15C4173824FD7C /* Pods-MuslimData_Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 04C0E7E86055EA2372B49E9AB0261CD7 /* Build configuration list for PBXNativeTarget "Pods-MuslimData_Tests" */; - buildPhases = ( - 83EA7FD93CDD3F45C25FFE10005193BC /* Headers */, - A93E584E9ED81BF7BBB7E662D140EB45 /* Sources */, - D4B5FD3802E3D47D8F15954F7F057354 /* Frameworks */, - 19E73AACA2A173EB3285C78888D0DEEF /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 88482DAA05E0AFBA543BBABD8DE9CED5 /* PBXTargetDependency */, - ); - name = "Pods-MuslimData_Tests"; - productName = "Pods-MuslimData_Tests"; - productReference = 9A0D52E8D92DD8E648D19B837936563A /* Pods_MuslimData_Tests.framework */; - productType = "com.apple.product-type.framework"; - }; - B618582B07803EA12417D625ABB2CD21 /* MuslimData */ = { - isa = PBXNativeTarget; - buildConfigurationList = E6AC575DD65F02A142A4F738C9CC2961 /* Build configuration list for PBXNativeTarget "MuslimData" */; - buildPhases = ( - 5DFB9A73B871F2FB2A9C161679AA7978 /* Headers */, - 4FD41EEDCFB8F107643B19E63BE98FF5 /* Sources */, - E478433A023C0C1A0476B627A242F172 /* Frameworks */, - 42A04E61C2B5A90096CA89F5158401E9 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 0DA56DE1BA7E04D14E66B0933667A036 /* PBXTargetDependency */, - ); - name = MuslimData; - productName = MuslimData; - productReference = 2E88CF46329DBA47B53B98F005E11EE3 /* MuslimData.framework */; - productType = "com.apple.product-type.framework"; - }; - FB81F088D9E90653987CAA036FACFD28 /* GRDB.swift */ = { - isa = PBXNativeTarget; - buildConfigurationList = 5699C88B5FC491714743229F1BA2B012 /* Build configuration list for PBXNativeTarget "GRDB.swift" */; - buildPhases = ( - A78FD3C6EB076C2D222749DACE114640 /* Headers */, - FA79B4CF9FB4E3726A81462D40C9F326 /* Sources */, - B411DE3B25D665AE58DF39FDE4CE1A94 /* Frameworks */, - F0AD9D498A9E7088AE78AB6990A1B42E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = GRDB.swift; - productName = GRDB.swift; - productReference = 57083C76476CFFD9C6281EB0FA5CF562 /* GRDB.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - BFDFE7DC352907FC980B868725387E98 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0930; - LastUpgradeCheck = 1420; - }; - buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = A08711B133A02CDCC7C1F09A0D69CB4A /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - FB81F088D9E90653987CAA036FACFD28 /* GRDB.swift */, - B618582B07803EA12417D625ABB2CD21 /* MuslimData */, - 031BFC8F009B7941500B46B8E0782A88 /* Pods-MuslimData_Example */, - 29306E3D5E7D49EB7D15C4173824FD7C /* Pods-MuslimData_Tests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 0A2747FD87CF911E39B73E577A2E3074 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 19E73AACA2A173EB3285C78888D0DEEF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 42A04E61C2B5A90096CA89F5158401E9 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2D0F97A52B813E8B003D46C5 /* CHANGELOG.md in Resources */, - 2D6CD44729CC5BF2009B527C /* muslim_db_v2.0.0.db in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F0AD9D498A9E7088AE78AB6990A1B42E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 4FD41EEDCFB8F107643B19E63BE98FF5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A2C07BA2D862B7CFEE16E632B97EF68C /* AzkarCategory.swift in Sources */, - 4F3B7D7E1C44ECB7A2EBEC681ACE4090 /* AzkarChapter.swift in Sources */, - 5301D3C1ECA5E2D47002FBE7724941FF /* AzkarItem.swift in Sources */, - E4B3988CB85F30F8B1539CDFFC777DE8 /* DateExtensions.swift in Sources */, - 567C4757D6C8959A805420C387798D94 /* DBHelper.swift in Sources */, - 3A22C174B394CB9AF865557B3EA0D761 /* Language.swift in Sources */, - 5B889DD74524BF47702D94225C8676BF /* Location.swift in Sources */, - 2DE6E95C2BA8D8010082A3CE /* MuslimRepository.swift in Sources */, - A7D645073A4902CE19144733BD3B1D6B /* MuslimData-dummy.m in Sources */, - FF616834C38743D535B62B7886C74F07 /* Name.swift in Sources */, - EA0BC2897FF6BE975F969C15629840CB /* Prayer.swift in Sources */, - 2DFE19682BAB81E900665C6B /* Repository.swift in Sources */, - 0AD24AEB225F135FE242003EA86E7039 /* PrayerAttribute.swift in Sources */, - 4232F4B0DC26B878201F9523CEF8F761 /* PrayerTime.swift in Sources */, - E746EBB44033C7F5ECE664C2A2862C8D /* String+Extensions.swift in Sources */, - 49E4580E0F098D7B0E49A777CEEF7085 /* TimeFormat.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 9708DE97B859FC1A35B3A97953DD0119 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DB7E6F762C72F6CFE76C9F5313AA2DAC /* Pods-MuslimData_Example-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A93E584E9ED81BF7BBB7E662D140EB45 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 612D5576922DA12E36C71B8D99680421 /* Pods-MuslimData_Tests-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FA79B4CF9FB4E3726A81462D40C9F326 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - E7EC009AC91B60BFE2989DC43D5253F2 /* Association.swift in Sources */, - 8C8DDF01955D5FB72AE73979CD218F36 /* AssociationAggregate.swift in Sources */, - 82FBE1672788EB416F7D7232748EA236 /* BelongsToAssociation.swift in Sources */, - D6F8A02145C28A736FD8A0A8BF7EB783 /* CGFloat.swift in Sources */, - 8691CEE725ED2C7C06E086770DB7757B /* Column.swift in Sources */, - A38C252839DFC6B34277E40395DA94C1 /* Configuration.swift in Sources */, - 72988A6CFA36E266D4FE0AF9DEADFC50 /* Cursor.swift in Sources */, - 5EA0894094250A2D9B56630274A92DD8 /* Data.swift in Sources */, - 622F287E4490422F197E00D242F18209 /* Database+Schema.swift in Sources */, - 66F92BFF9384A67BB00CA346C5554550 /* Database+Statements.swift in Sources */, - 1CAA3EDF7EBE5B64C6F193EB77CF398D /* Database.swift in Sources */, - 000B5FF0F2E60440DE83E1CDEF81BD43 /* DatabaseCollation.swift in Sources */, - 676EF897E9236CB90575B3F9CFCD3120 /* DatabaseDateComponents.swift in Sources */, - 2136CBA14769165E58DDA619D2903003 /* DatabaseError.swift in Sources */, - A69D080C6DC5CCD1C8153338B1B4BEF1 /* DatabaseFunction.swift in Sources */, - 22B2CC4684AD540D4DC22B38F0A1D4B8 /* DatabaseMigrator.swift in Sources */, - 46DCBB7C162FC503B1AF52D6A59CB87F /* DatabasePool.swift in Sources */, - 0C99CC73940BDB5696917C42DE00B262 /* DatabasePromise.swift in Sources */, - F73E45DCB4A2E3D31C00A4EDC76213AC /* DatabaseQueue.swift in Sources */, - B03A35D413AA6ACB4F1BE28702E84333 /* DatabaseReader.swift in Sources */, - 0C02D06017D674FB3D221B3082D52242 /* DatabaseRegion.swift in Sources */, - 83728421B1D35408CC150F9A5366A386 /* DatabaseRegionObservation.swift in Sources */, - FB9485218620ACEFA3967734BC256E2C /* DatabaseSchemaCache.swift in Sources */, - B5B1E0C592A08E5665915D2BC1A0D9E8 /* DatabaseSnapshot.swift in Sources */, - E8C7E279307C3DB919A3E0D67FCC114A /* DatabaseValue.swift in Sources */, - 07E87312FC3CA6D83F78905E504BCE9C /* DatabaseValueConversion.swift in Sources */, - F96737610B1BD0E614F153636059C2F4 /* DatabaseValueConvertible+Decodable.swift in Sources */, - BD802C1DC1B139EDAB311C7709A00CB0 /* DatabaseValueConvertible+Encodable.swift in Sources */, - 33FDF2697ECE2E34513CACBC1EDFDB51 /* DatabaseValueConvertible+RawRepresentable.swift in Sources */, - A9346A4922DFB8C725C8722D99258AF8 /* DatabaseValueConvertible+ReferenceConvertible.swift in Sources */, - B8244EA7A9D7EA478BC043649E3F256D /* DatabaseValueConvertible.swift in Sources */, - 55127675DE18916F1A1148A62002E454 /* DatabaseWriter.swift in Sources */, - F909FEBC62AD743990EB2D7A3D594332 /* Date.swift in Sources */, - 1BA72BD73CE8FCF542D23CBD4E18A2D8 /* EncodableRecord+Encodable.swift in Sources */, - D4CA4FA4E5050BD998B823D705F3BE76 /* EncodableRecord.swift in Sources */, - 7EFEF55B0CC98496F1408917DD408225 /* FetchableRecord+Decodable.swift in Sources */, - C4E2B1658AC3663D15AA9F2531B7BFD6 /* FetchableRecord+QueryInterfaceRequest.swift in Sources */, - 2B130AEAB2EE4BD7997DB369572A684A /* FetchableRecord+TableRecord.swift in Sources */, - 9EF00602B746DE476813B33D60E7B8B5 /* FetchableRecord.swift in Sources */, - 9F2DF35D15A048345D6F3253521B57F3 /* FetchedRecordsController.swift in Sources */, - 10B2282DEF61B85E70A0877BE83B21DE /* FetchRequest.swift in Sources */, - 8F776D68D6DEFD38CCF85B4276D968E0 /* FTS3+QueryInterface.swift in Sources */, - 459E5AD9A0DF5E01D04CAD6D23929282 /* FTS3.swift in Sources */, - 750ACAC53406F3B7C04FCD17174BA343 /* FTS3Pattern.swift in Sources */, - 35A9B68C8D7AC42A8187DC1F8AEC095A /* FTS3TokenizerDescriptor.swift in Sources */, - 499A61F937A27F0C2BAF4ECB402B4152 /* FTS4.swift in Sources */, - 318B2D4FBE83F08BB67C2AA6A52977AB /* FTS5+QueryInterface.swift in Sources */, - 81F2E0E03397CB5AE8A9FA1961C6AB67 /* FTS5.swift in Sources */, - 91F28A0A35BA0F4F8FB3F005D7253C45 /* FTS5CustomTokenizer.swift in Sources */, - 69C357CAA8533B915C6268E88A349008 /* FTS5Pattern.swift in Sources */, - 6ADA4C725A1F552DE188C42ED13897F5 /* FTS5Tokenizer.swift in Sources */, - C31B69B2E5A6B91C7202FCE6A3B6A1E4 /* FTS5TokenizerDescriptor.swift in Sources */, - FB5122536928EA7E76459AF5E6555CEE /* FTS5WrapperTokenizer.swift in Sources */, - 1C3D3D4CBD9412DA99FE9CE8577F8856 /* GRDB-4.0.swift in Sources */, - A9286B0849F46D5349C42CDD9E3FCBD8 /* GRDB.swift-dummy.m in Sources */, - A4F753F4A25E639F6EBBD469F1C821EF /* HasManyAssociation.swift in Sources */, - 5DDB2B240C6F6C89EBB5A129D2F6B960 /* HasManyThroughAssociation.swift in Sources */, - F5C741AE24465B84517D53DB2FE025A3 /* HasOneAssociation.swift in Sources */, - E3F8678E8337CD65A23D6FB611CD6D89 /* HasOneThroughAssociation.swift in Sources */, - 6B8E4A743436EED9C9D1D5D72A0D2F27 /* Inflections+English.swift in Sources */, - 6B8F3C1D6214E7352C7856A50EB7406B /* Inflections.swift in Sources */, - 3E373FA762B90937FE09682DC5BE6FCE /* Migration.swift in Sources */, - 20467EF687049794ACE9F46BD89B36A9 /* NSData.swift in Sources */, - 43694454258CB0903238325B1122E5A1 /* NSNull.swift in Sources */, - AC842DD31D9FD9F8A80F393309AC1A2B /* NSNumber.swift in Sources */, - 6DE3CE29183585530907EB1C3CCC2726 /* NSString.swift in Sources */, - F5885BDC210752638AFF3EAF38651DC9 /* OrderedDictionary.swift in Sources */, - 6D2323C4C7DF66474D1ABCAD305F0689 /* PersistableRecord.swift in Sources */, - B09F1124D798A0E3AFA81B0B79F80E36 /* Pool.swift in Sources */, - D030773C11115860A55CDFCE6904BF38 /* QueryInterfaceRequest+Association.swift in Sources */, - 7D65B46A1F2441D9147FE78F07AB373F /* QueryInterfaceRequest.swift in Sources */, - 423266C51CEEE11551EF493E0E9D41FC /* ReadWriteBox.swift in Sources */, - 90DF4F4E9630E63609F3CF6642505E25 /* Record.swift in Sources */, - DC050E33E340108BA79B38A9195EAFCC /* RequestProtocols.swift in Sources */, - 95327339DAA1511EE65E735E37DA42C5 /* Result.swift in Sources */, - 933318275B26D35CB5734E128B3BB5A4 /* Row+QueryInterfaceRequest.swift in Sources */, - F0EF5074C899CD456B95D2F724F9067B /* Row.swift in Sources */, - 4697DE621262D990332671D683A6EB00 /* RowAdapter.swift in Sources */, - 737942B8F96BC31238E7F0B8F3609D68 /* SchedulingWatchdog.swift in Sources */, - D628E893B835D23BBAED9343E59610F9 /* SerializedDatabase.swift in Sources */, - B60E22BAD03836CB0720C853D1B19341 /* SQLAssociation.swift in Sources */, - 02750A16FAA074E14BB1C0936016B286 /* SQLCollatedExpression.swift in Sources */, - 85D8463381D9C856410C3B80AE8DD159 /* SQLCollection.swift in Sources */, - 13F6B7CE384A0A2CE2A7E12616535089 /* SQLExpressible.swift in Sources */, - 7D94091D2C1067867BA50E9DAF18CE17 /* SQLExpression+QueryInterface.swift in Sources */, - FDA5B665C9D4F6C74D991D68EC23A297 /* SQLExpression.swift in Sources */, - BA76D4525CC0A4865E2AAA90336118D8 /* SQLForeignKeyRequest.swift in Sources */, - 889F226413C21D59F2BFFBC2C92DB9EA /* SQLFunctions.swift in Sources */, - 15BFA9419F7B8F44B90DB2A0685D8416 /* SQLGenerationContext.swift in Sources */, - 2844C8F86E572981747D5F7D57A47079 /* SQLInterpolation+QueryInterface.swift in Sources */, - 473A628F8D25C06F0352CDB563A8F71E /* SQLInterpolation.swift in Sources */, - 739400B0F56D496D47483FC196D4C91A /* SQLiteDateParser.swift in Sources */, - 821BAA59509043E640391CFA942C918C /* SQLLiteral.swift in Sources */, - 49D85D0B0DAA2BDADA2BBD09C2319058 /* SQLOperators.swift in Sources */, - E0E942A9C82C4FEF89A2EED08A776332 /* SQLOrdering.swift in Sources */, - 92B69B37E5ADEA0DDCDEC06396885052 /* SQLQuery.swift in Sources */, - D828D98E5A5A48B3FDC5B486064626C6 /* SQLQueryGenerator.swift in Sources */, - D67D1521F3C5B0C03409EBAB72604C30 /* SQLRelation.swift in Sources */, - 4F791AEE35BB3276A68EDD48C35BC44E /* SQLRequest.swift in Sources */, - 54EB702C1486DC310031B3DAED1FE3DC /* SQLSelectable+QueryInterface.swift in Sources */, - F375CD882264F61911A3B5F7CC6E2310 /* SQLSelectable.swift in Sources */, - CCA3C990B6DE174297BE934A2728C421 /* SQLSpecificExpressible+QueryInterface.swift in Sources */, - C4F0D6EE223CBD552F269B36DE389B8B /* StandardLibrary.swift in Sources */, - 41677C6E7B38372C96FF8074BE163189 /* Statement.swift in Sources */, - 2A050272F5AA9419EACC2B68583FBF0C /* StatementAuthorizer.swift in Sources */, - 11AAE1599E6093A219A889D2DAE80ECA /* StatementColumnConvertible.swift in Sources */, - D1A2EF0386442ECD20338199E9DB8099 /* TableDefinition.swift in Sources */, - 1A5226373797A66B9468D3C1F5C4D825 /* TableRecord+Association.swift in Sources */, - 9D661C0F8785669F6E4A88754F9C81EF /* TableRecord+QueryInterfaceRequest.swift in Sources */, - ED942EC9C394EBF13A2F679EC94192C7 /* TableRecord.swift in Sources */, - F70DB94ADA336509026869AAF426BEA3 /* TransactionObserver.swift in Sources */, - 79549123A16832B9B715EB4DCBE521EC /* URL.swift in Sources */, - E0E07E6C3622B91F444A2F81EECCA4A2 /* Utils.swift in Sources */, - 0E0B8626BCD149D02290F230544A789A /* UUID.swift in Sources */, - 6D2993298E9964AF6D54F095E3DA46EF /* ValueObservation+Combine.swift in Sources */, - 80EFCEB2F5ED66AAB527232A517F5F81 /* ValueObservation+CompactMap.swift in Sources */, - D6E75BDE3181F62E6B81499F5989808D /* ValueObservation+Count.swift in Sources */, - 121E4E7A4C0459415A9B883D506D3FBC /* ValueObservation+DatabaseValueConvertible.swift in Sources */, - 28AC5449494C7BB3E1D2C61A23D88B3E /* ValueObservation+DistinctUntilChanged.swift in Sources */, - 394451DC1DB1945ED4FF137FDE90A8E4 /* ValueObservation+FetchableRecord.swift in Sources */, - 443EAD393975455814198C533E80384A /* ValueObservation+Map.swift in Sources */, - 4429FB29F5620E0A1845105ACD23FDEA /* ValueObservation+MapReducer.swift in Sources */, - 9D8D0F9EF2B61EBF2D539080CD08FCE0 /* ValueObservation+Row.swift in Sources */, - 1EF47C87D5BC5C950E090F8519C6FD30 /* ValueObservation.swift in Sources */, - 9D12AC35A63C05E9F0EE63EB506CEB9F /* ValueObserver.swift in Sources */, - 65D90C1E01EF19802462954F69E983E5 /* ValueReducer.swift in Sources */, - 747435A2AB2CAFBAFD7651734659C6B2 /* VirtualTableModule.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 0DA56DE1BA7E04D14E66B0933667A036 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = GRDB.swift; - target = FB81F088D9E90653987CAA036FACFD28 /* GRDB.swift */; - targetProxy = 3DE8392EBF9E858F78A3D0BC375D811B /* PBXContainerItemProxy */; - }; - 88482DAA05E0AFBA543BBABD8DE9CED5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "Pods-MuslimData_Example"; - target = 031BFC8F009B7941500B46B8E0782A88 /* Pods-MuslimData_Example */; - targetProxy = 4F03C233FDF9A651A45430E56D389C40 /* PBXContainerItemProxy */; - }; - AFFED11E9996F77200F59F6E233634E1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = GRDB.swift; - target = FB81F088D9E90653987CAA036FACFD28 /* GRDB.swift */; - targetProxy = 1ED7D8A0A4218E980A192470A3FF01C8 /* PBXContainerItemProxy */; - }; - F1CCD1F0A0AE20FC5678895260D2C755 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = MuslimData; - target = B618582B07803EA12417D625ABB2CD21 /* MuslimData */; - targetProxy = 4017E5378AFE304FFFFB252C0F0305F9 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 044C78BECED4CB71EBCA877C548CC8EF /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 14A7257E831A9D87198C29B792FDF9B6 /* MuslimData.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/MuslimData/MuslimData-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/MuslimData/MuslimData-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/MuslimData/MuslimData.modulemap"; - PRODUCT_MODULE_NAME = MuslimData; - PRODUCT_NAME = MuslimData; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 3048B0C5C704DFFF688DA57F5380ED58 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_RELEASE=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_INSTALLED_PRODUCT = NO; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.2; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Release; - }; - 35567F50788FA524934891EA64B610A2 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D425DD46CBA81B4F5985DBFD029CC0EE /* GRDB.swift.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GRDB.swift/GRDB.swift-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GRDB.swift/GRDB.swift-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/GRDB.swift/GRDB.swift.modulemap"; - PRODUCT_MODULE_NAME = GRDB; - PRODUCT_NAME = GRDB; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 5B0C8287D755FD95091CF35D87FB8B2D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_DEBUG=1", - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_INSTALLED_PRODUCT = NO; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Debug; - }; - 69BAFAA36E0CE1E40890D16995C6FA3B /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = FB29E142D18858D1242D2F378682A8F8 /* Pods-MuslimData_Tests.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 7B6F7A1FE15E71CC36E5394A9803C9B7 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B053232FB4BC0D06EA67CAF95D29114D /* Pods-MuslimData_Example.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 95F8A44AFCD5FF3F3A2084B71D977628 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = BCC7E5208F1383E700C4820E6EC5F37A /* Pods-MuslimData_Tests.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 98CCEEBA6A09004EE203D45B50911146 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 14A7257E831A9D87198C29B792FDF9B6 /* MuslimData.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/MuslimData/MuslimData-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/MuslimData/MuslimData-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/MuslimData/MuslimData.modulemap"; - PRODUCT_MODULE_NAME = MuslimData; - PRODUCT_NAME = MuslimData; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - DD65C1CE247B0AE7DF1B5CE14B4A09DC /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 74E7C387B223D2421080A1FB6A9C4272 /* Pods-MuslimData_Example.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - F097C3ED0444DF5A5FD8934BE2C3C429 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D425DD46CBA81B4F5985DBFD029CC0EE /* GRDB.swift.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GRDB.swift/GRDB.swift-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GRDB.swift/GRDB.swift-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/GRDB.swift/GRDB.swift.modulemap"; - PRODUCT_MODULE_NAME = GRDB; - PRODUCT_NAME = GRDB; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 04C0E7E86055EA2372B49E9AB0261CD7 /* Build configuration list for PBXNativeTarget "Pods-MuslimData_Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 69BAFAA36E0CE1E40890D16995C6FA3B /* Debug */, - 95F8A44AFCD5FF3F3A2084B71D977628 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 5B0C8287D755FD95091CF35D87FB8B2D /* Debug */, - 3048B0C5C704DFFF688DA57F5380ED58 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 5699C88B5FC491714743229F1BA2B012 /* Build configuration list for PBXNativeTarget "GRDB.swift" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F097C3ED0444DF5A5FD8934BE2C3C429 /* Debug */, - 35567F50788FA524934891EA64B610A2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D6C426F6F52203D774BAE97512281049 /* Build configuration list for PBXNativeTarget "Pods-MuslimData_Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7B6F7A1FE15E71CC36E5394A9803C9B7 /* Debug */, - DD65C1CE247B0AE7DF1B5CE14B4A09DC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - E6AC575DD65F02A142A4F738C9CC2961 /* Build configuration list for PBXNativeTarget "MuslimData" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 98CCEEBA6A09004EE203D45B50911146 /* Debug */, - 044C78BECED4CB71EBCA877C548CC8EF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; -} diff --git a/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100755 index 18d9810..0000000 --- a/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-Info.plist b/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-Info.plist deleted file mode 100755 index b672cd7..0000000 --- a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 4.0.1 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-dummy.m b/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-dummy.m deleted file mode 100755 index a7dc8db..0000000 --- a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_GRDB_swift : NSObject -@end -@implementation PodsDummy_GRDB_swift -@end diff --git a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-prefix.pch b/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-prefix.pch deleted file mode 100755 index beb2a24..0000000 --- a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift-prefix.pch +++ /dev/null @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift.modulemap b/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift.modulemap deleted file mode 100755 index 04ac555..0000000 --- a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift.modulemap +++ /dev/null @@ -1,8 +0,0 @@ -framework module GRDB { - umbrella header "GRDB.h" - - export * - module * { export * } - - header "grdb_config.h" -} diff --git a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift.xcconfig b/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift.xcconfig deleted file mode 100755 index c0f2c1e..0000000 --- a/Example/Pods/Target Support Files/GRDB.swift/GRDB.swift.xcconfig +++ /dev/null @@ -1,10 +0,0 @@ -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -OTHER_LDFLAGS = $(inherited) -l"sqlite3" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/GRDB.swift -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/GRDB.swift/Info.plist b/Example/Pods/Target Support Files/GRDB.swift/Info.plist deleted file mode 100755 index f4b9ea8..0000000 --- a/Example/Pods/Target Support Files/GRDB.swift/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 3.6.2 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/MuslimData/Info.plist b/Example/Pods/Target Support Files/MuslimData/Info.plist deleted file mode 100755 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/MuslimData/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/MuslimData/MuslimData-Info.plist b/Example/Pods/Target Support Files/MuslimData/MuslimData-Info.plist deleted file mode 100755 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/MuslimData/MuslimData-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/MuslimData/MuslimData-dummy.m b/Example/Pods/Target Support Files/MuslimData/MuslimData-dummy.m deleted file mode 100755 index 73ac6fb..0000000 --- a/Example/Pods/Target Support Files/MuslimData/MuslimData-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_MuslimData : NSObject -@end -@implementation PodsDummy_MuslimData -@end diff --git a/Example/Pods/Target Support Files/MuslimData/MuslimData-prefix.pch b/Example/Pods/Target Support Files/MuslimData/MuslimData-prefix.pch deleted file mode 100755 index beb2a24..0000000 --- a/Example/Pods/Target Support Files/MuslimData/MuslimData-prefix.pch +++ /dev/null @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Example/Pods/Target Support Files/MuslimData/MuslimData-umbrella.h b/Example/Pods/Target Support Files/MuslimData/MuslimData-umbrella.h deleted file mode 100755 index b5086c7..0000000 --- a/Example/Pods/Target Support Files/MuslimData/MuslimData-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double MuslimDataVersionNumber; -FOUNDATION_EXPORT const unsigned char MuslimDataVersionString[]; - diff --git a/Example/Pods/Target Support Files/MuslimData/MuslimData.modulemap b/Example/Pods/Target Support Files/MuslimData/MuslimData.modulemap deleted file mode 100755 index e86668f..0000000 --- a/Example/Pods/Target Support Files/MuslimData/MuslimData.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module MuslimData { - umbrella header "MuslimData-umbrella.h" - - export * - module * { export * } -} diff --git a/Example/Pods/Target Support Files/MuslimData/MuslimData.xcconfig b/Example/Pods/Target Support Files/MuslimData/MuslimData.xcconfig deleted file mode 100755 index a94d8b1..0000000 --- a/Example/Pods/Target Support Files/MuslimData/MuslimData.xcconfig +++ /dev/null @@ -1,10 +0,0 @@ -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MuslimData -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/MuslimData/ResourceBundle-MuslimData-Info.plist b/Example/Pods/Target Support Files/MuslimData/ResourceBundle-MuslimData-Info.plist deleted file mode 100755 index 99a40ff..0000000 --- a/Example/Pods/Target Support Files/MuslimData/ResourceBundle-MuslimData-Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - BNDL - CFBundleShortVersionString - 0.1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Info.plist b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Info.plist deleted file mode 100755 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-Info.plist b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-Info.plist deleted file mode 100755 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-acknowledgements.markdown deleted file mode 100755 index 90e9841..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-acknowledgements.markdown +++ /dev/null @@ -1,37 +0,0 @@ -# Acknowledgements -This application makes use of the following third party libraries: - -## GRDB.swift - -Copyright (C) 2019 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -## MuslimData - -Copyright (c) 2018 Kosrat D. Ahmad - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-acknowledgements.plist deleted file mode 100755 index 2183793..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-acknowledgements.plist +++ /dev/null @@ -1,75 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2019 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - License - MIT - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2018 Kosrat D. Ahmad <kosrat.d.ahmad@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - License - MIT - Title - MuslimData - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-dummy.m b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-dummy.m deleted file mode 100755 index 133bfe5..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_Pods_MuslimData_Example : NSObject -@end -@implementation PodsDummy_Pods_MuslimData_Example -@end diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-frameworks.sh b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-frameworks.sh deleted file mode 100755 index 78f9b25..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-frameworks.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -function on_error { - echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" -} -trap 'on_error $LINENO' ERR - -if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then - # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy - # frameworks to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" -mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" -SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" - -# Used as a return value for each invocation of `strip_invalid_archs` function. -STRIP_BINARY_RETVAL=0 - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -# Copies and strips a vendored framework -install_framework() -{ - if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then - local source="${BUILT_PRODUCTS_DIR}/$1" - elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then - local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" - elif [ -r "$1" ]; then - local source="$1" - fi - - local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - - if [ -L "${source}" ]; then - echo "Symlinked..." - source="$(readlink "${source}")" - fi - - # Use filter instead of exclude so missing patterns don't throw errors. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" - - local basename - basename="$(basename -s .framework "$1")" - binary="${destination}/${basename}.framework/${basename}" - - if ! [ -r "$binary" ]; then - binary="${destination}/${basename}" - elif [ -L "${binary}" ]; then - echo "Destination binary is symlinked..." - dirname="$(dirname "${binary}")" - binary="${dirname}/$(readlink "${binary}")" - fi - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then - strip_invalid_archs "$binary" - fi - - # Resign the code if required by the build settings to avoid unstable apps - code_sign_if_enabled "${destination}/$(basename "$1")" - - # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. - if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then - local swift_runtime_libs - swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) - for lib in $swift_runtime_libs; do - echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" - rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" - code_sign_if_enabled "${destination}/${lib}" - done - fi -} - -# Copies and strips a vendored dSYM -install_dsym() { - local source="$1" - if [ -r "$source" ]; then - # Copy the dSYM into a the targets temp dir. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" - - local basename - basename="$(basename -s .framework.dSYM "$source")" - binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then - strip_invalid_archs "$binary" - fi - - if [[ $STRIP_BINARY_RETVAL == 1 ]]; then - # Move the stripped file into its final destination. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" - else - # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. - touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" - fi - fi -} - -# Signs a framework with the provided identity -code_sign_if_enabled() { - if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then - # Use the current code_sign_identity - echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" - - if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - code_sign_cmd="$code_sign_cmd &" - fi - echo "$code_sign_cmd" - eval "$code_sign_cmd" - fi -} - -# Strip invalid architectures -strip_invalid_archs() { - binary="$1" - # Get architectures for current target binary - binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" - # Intersect them with the architectures we are building for - intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" - # If there are no archs supported by this binary then warn the user - if [[ -z "$intersected_archs" ]]; then - echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." - STRIP_BINARY_RETVAL=0 - return - fi - stripped="" - for arch in $binary_archs; do - if ! [[ "${ARCHS}" == *"$arch"* ]]; then - # Strip non-valid architectures in-place - lipo -remove "$arch" -output "$binary" "$binary" - stripped="$stripped $arch" - fi - done - if [[ "$stripped" ]]; then - echo "Stripped $binary of architectures:$stripped" - fi - STRIP_BINARY_RETVAL=1 -} - - -if [[ "$CONFIGURATION" == "Debug" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GRDB.swift/GRDB.framework" - install_framework "${BUILT_PRODUCTS_DIR}/MuslimData/MuslimData.framework" -fi -if [[ "$CONFIGURATION" == "Release" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GRDB.swift/GRDB.framework" - install_framework "${BUILT_PRODUCTS_DIR}/MuslimData/MuslimData.framework" -fi -if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - wait -fi diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-resources.sh b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-resources.sh deleted file mode 100755 index 345301f..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-resources.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then - # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy - # resources to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - -RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt -> "$RESOURCES_TO_COPY" - -XCASSET_FILES=() - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -case "${TARGETED_DEVICE_FAMILY:-}" in - 1,2) - TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" - ;; - 1) - TARGET_DEVICE_ARGS="--target-device iphone" - ;; - 2) - TARGET_DEVICE_ARGS="--target-device ipad" - ;; - 3) - TARGET_DEVICE_ARGS="--target-device tv" - ;; - 4) - TARGET_DEVICE_ARGS="--target-device watch" - ;; - *) - TARGET_DEVICE_ARGS="--target-device mac" - ;; -esac - -install_resource() -{ - if [[ "$1" = /* ]] ; then - RESOURCE_PATH="$1" - else - RESOURCE_PATH="${PODS_ROOT}/$1" - fi - if [[ ! -e "$RESOURCE_PATH" ]] ; then - cat << EOM -error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. -EOM - exit 1 - fi - case $RESOURCE_PATH in - *.storyboard) - echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true - ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} - ;; - *.xib) - echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true - ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} - ;; - *.framework) - echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true - mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - ;; - *.xcdatamodel) - echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true - xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" - ;; - *.xcdatamodeld) - echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true - xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" - ;; - *.xcmappingmodel) - echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true - xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" - ;; - *.xcassets) - ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" - XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") - ;; - *) - echo "$RESOURCE_PATH" || true - echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" - ;; - esac -} - -mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then - mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -fi -rm -f "$RESOURCES_TO_COPY" - -if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] -then - # Find all other xcassets (this unfortunately includes those of path pods and other targets). - OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) - while read line; do - if [[ $line != "${PODS_ROOT}*" ]]; then - XCASSET_FILES+=("$line") - fi - done <<<"$OTHER_XCASSETS" - - if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then - printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - else - printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" - fi -fi diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-umbrella.h b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-umbrella.h deleted file mode 100755 index 6f5d42e..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double Pods_MuslimData_ExampleVersionNumber; -FOUNDATION_EXPORT const unsigned char Pods_MuslimData_ExampleVersionString[]; - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.debug.xcconfig b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.debug.xcconfig deleted file mode 100755 index e666dbe..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.debug.xcconfig +++ /dev/null @@ -1,12 +0,0 @@ -ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData/MuslimData.framework/Headers" -LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData/MuslimData.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData" -OTHER_LDFLAGS = $(inherited) -l"sqlite3" -framework "Foundation" -framework "GRDB" -framework "MuslimData" -framework "UIKit" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.modulemap b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.modulemap deleted file mode 100755 index 3d39baa..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Pods_MuslimData_Example { - umbrella header "Pods-MuslimData_Example-umbrella.h" - - export * - module * { export * } -} diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.release.xcconfig b/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.release.xcconfig deleted file mode 100755 index e666dbe..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Example/Pods-MuslimData_Example.release.xcconfig +++ /dev/null @@ -1,12 +0,0 @@ -ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData/MuslimData.framework/Headers" -LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData/MuslimData.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData" -OTHER_LDFLAGS = $(inherited) -l"sqlite3" -framework "Foundation" -framework "GRDB" -framework "MuslimData" -framework "UIKit" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Info.plist b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Info.plist deleted file mode 100755 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-Info.plist b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-Info.plist deleted file mode 100755 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-acknowledgements.markdown deleted file mode 100755 index 102af75..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-acknowledgements.markdown +++ /dev/null @@ -1,3 +0,0 @@ -# Acknowledgements -This application makes use of the following third party libraries: -Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-acknowledgements.plist deleted file mode 100755 index 7acbad1..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-acknowledgements.plist +++ /dev/null @@ -1,29 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-dummy.m b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-dummy.m deleted file mode 100755 index 8674ab7..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_Pods_MuslimData_Tests : NSObject -@end -@implementation PodsDummy_Pods_MuslimData_Tests -@end diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-frameworks.sh b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-frameworks.sh deleted file mode 100755 index 08e3eaa..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-frameworks.sh +++ /dev/null @@ -1,146 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then - # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy - # frameworks to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" -mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" -SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" - -# Used as a return value for each invocation of `strip_invalid_archs` function. -STRIP_BINARY_RETVAL=0 - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -# Copies and strips a vendored framework -install_framework() -{ - if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then - local source="${BUILT_PRODUCTS_DIR}/$1" - elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then - local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" - elif [ -r "$1" ]; then - local source="$1" - fi - - local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - - if [ -L "${source}" ]; then - echo "Symlinked..." - source="$(readlink "${source}")" - fi - - # Use filter instead of exclude so missing patterns don't throw errors. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" - - local basename - basename="$(basename -s .framework "$1")" - binary="${destination}/${basename}.framework/${basename}" - if ! [ -r "$binary" ]; then - binary="${destination}/${basename}" - fi - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then - strip_invalid_archs "$binary" - fi - - # Resign the code if required by the build settings to avoid unstable apps - code_sign_if_enabled "${destination}/$(basename "$1")" - - # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. - if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then - local swift_runtime_libs - swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) - for lib in $swift_runtime_libs; do - echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" - rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" - code_sign_if_enabled "${destination}/${lib}" - done - fi -} - -# Copies and strips a vendored dSYM -install_dsym() { - local source="$1" - if [ -r "$source" ]; then - # Copy the dSYM into a the targets temp dir. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" - - local basename - basename="$(basename -s .framework.dSYM "$source")" - binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then - strip_invalid_archs "$binary" - fi - - if [[ $STRIP_BINARY_RETVAL == 1 ]]; then - # Move the stripped file into its final destination. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" - else - # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. - touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" - fi - fi -} - -# Signs a framework with the provided identity -code_sign_if_enabled() { - if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then - # Use the current code_sign_identitiy - echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" - - if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - code_sign_cmd="$code_sign_cmd &" - fi - echo "$code_sign_cmd" - eval "$code_sign_cmd" - fi -} - -# Strip invalid architectures -strip_invalid_archs() { - binary="$1" - # Get architectures for current target binary - binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" - # Intersect them with the architectures we are building for - intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" - # If there are no archs supported by this binary then warn the user - if [[ -z "$intersected_archs" ]]; then - echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." - STRIP_BINARY_RETVAL=0 - return - fi - stripped="" - for arch in $binary_archs; do - if ! [[ "${ARCHS}" == *"$arch"* ]]; then - # Strip non-valid architectures in-place - lipo -remove "$arch" -output "$binary" "$binary" || exit 1 - stripped="$stripped $arch" - fi - done - if [[ "$stripped" ]]; then - echo "Stripped $binary of architectures:$stripped" - fi - STRIP_BINARY_RETVAL=1 -} - -if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - wait -fi diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-resources.sh b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-resources.sh deleted file mode 100755 index 345301f..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-resources.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then - # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy - # resources to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - -RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt -> "$RESOURCES_TO_COPY" - -XCASSET_FILES=() - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -case "${TARGETED_DEVICE_FAMILY:-}" in - 1,2) - TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" - ;; - 1) - TARGET_DEVICE_ARGS="--target-device iphone" - ;; - 2) - TARGET_DEVICE_ARGS="--target-device ipad" - ;; - 3) - TARGET_DEVICE_ARGS="--target-device tv" - ;; - 4) - TARGET_DEVICE_ARGS="--target-device watch" - ;; - *) - TARGET_DEVICE_ARGS="--target-device mac" - ;; -esac - -install_resource() -{ - if [[ "$1" = /* ]] ; then - RESOURCE_PATH="$1" - else - RESOURCE_PATH="${PODS_ROOT}/$1" - fi - if [[ ! -e "$RESOURCE_PATH" ]] ; then - cat << EOM -error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. -EOM - exit 1 - fi - case $RESOURCE_PATH in - *.storyboard) - echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true - ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} - ;; - *.xib) - echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true - ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} - ;; - *.framework) - echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true - mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - ;; - *.xcdatamodel) - echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true - xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" - ;; - *.xcdatamodeld) - echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true - xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" - ;; - *.xcmappingmodel) - echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true - xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" - ;; - *.xcassets) - ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" - XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") - ;; - *) - echo "$RESOURCE_PATH" || true - echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" - ;; - esac -} - -mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then - mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -fi -rm -f "$RESOURCES_TO_COPY" - -if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] -then - # Find all other xcassets (this unfortunately includes those of path pods and other targets). - OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) - while read line; do - if [[ $line != "${PODS_ROOT}*" ]]; then - XCASSET_FILES+=("$line") - fi - done <<<"$OTHER_XCASSETS" - - if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then - printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - else - printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" - fi -fi diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-umbrella.h b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-umbrella.h deleted file mode 100755 index abf9b43..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double Pods_MuslimData_TestsVersionNumber; -FOUNDATION_EXPORT const unsigned char Pods_MuslimData_TestsVersionString[]; - diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.debug.xcconfig b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.debug.xcconfig deleted file mode 100755 index c545b14..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.debug.xcconfig +++ /dev/null @@ -1,9 +0,0 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData/MuslimData.framework/Headers" -LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -l"sqlite3" -framework "Foundation" -framework "GRDB" -framework "MuslimData" -framework "UIKit" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.modulemap b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.modulemap deleted file mode 100755 index 8be25e0..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Pods_MuslimData_Tests { - umbrella header "Pods-MuslimData_Tests-umbrella.h" - - export * - module * { export * } -} diff --git a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.release.xcconfig b/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.release.xcconfig deleted file mode 100755 index c545b14..0000000 --- a/Example/Pods/Target Support Files/Pods-MuslimData_Tests/Pods-MuslimData_Tests.release.xcconfig +++ /dev/null @@ -1,9 +0,0 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MuslimData/MuslimData.framework/Headers" -LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -l"sqlite3" -framework "Foundation" -framework "GRDB" -framework "MuslimData" -framework "UIKit" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Tests/AzkarTests.swift b/Example/Tests/AzkarTests.swift deleted file mode 100755 index 6a887af..0000000 --- a/Example/Tests/AzkarTests.swift +++ /dev/null @@ -1,155 +0,0 @@ -// -// AzkarTests.swift -// MuslimData_Tests -// -// Created by Kosrat D. Ahmad on 10/13/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -@testable import MuslimData -import XCTest - -class AzkarTests: XCTestCase { - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testEnAzkarCategories() async throws { - let categories = try await MuslimRepository().getAzkarCategories(language: .en) - assesAzkarCategories(categories: categories) - } - - func testArAzkarCategories() async throws { - let categories = try await MuslimRepository().getAzkarCategories(language: .ar) - assesAzkarCategories(categories: categories) - } - - func testCkbAzkarCategories() async throws { - let categories = try await MuslimRepository().getAzkarCategories(language: .ckb) - assesAzkarCategories(categories: categories) - } - - func testFaAzkarCategories() async throws { - let categories = try await MuslimRepository().getAzkarCategories(language: .fa) - assesAzkarCategories(categories: categories) - } - - func testRuAzkarCategories() async throws { - let categories = try await MuslimRepository().getAzkarCategories(language: .ru) - assesAzkarCategories(categories: categories) - } - - private func assesAzkarCategories(categories: [AzkarCategory]?) { - XCTAssertNotNil(categories) - XCTAssertEqual(categories!.count, 11) - XCTAssertNotNil(categories![Int.random(in: 0 ..< 11)].name) - } - - func testEnAzkarChapters() async throws { - let chapters = try await MuslimRepository().getAzkarChapters(language: .en) - assesAzkarChapters(chapters: chapters) - } - - func testArAzkarChapters() async throws { - let chapters = try await MuslimRepository().getAzkarChapters(language: .ar) - assesAzkarChapters(chapters: chapters) - } - - func testCkbAzkarChapters() async throws { - let chapters = try await MuslimRepository().getAzkarChapters(language: .ckb) - assesAzkarChapters(chapters: chapters) - } - - func testFaAzkarChapters() async throws { - let chapters = try await MuslimRepository().getAzkarChapters(language: .fa) - assesAzkarChapters(chapters: chapters) - } - - func testRuAzkarChapters() async throws { - let chapters = try await MuslimRepository().getAzkarChapters(language: .ru) - assesAzkarChapters(chapters: chapters) - } - - private func assesAzkarChapters(chapters: [AzkarChapter]?) { - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 133) - } - - func testAzkarChaptersByCategory() async throws { - // Test English azkar chapters for category id = 1 - var chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 1) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 7) - - // Test English azkar chapters for category id = 2 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 2) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 14) - - // Test English azkar chapters for category id = 3 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 3) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 7) - - // Test English azkar chapters for category id = 4 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 4) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 15) - - // Test English azkar chapters for category id = 5 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 5) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 11) - - // Test English azkar chapters for category id = 6 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 6) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 19) - - // Test English azkar chapters for category id = 7 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 7) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 9) - - // Test English azkar chapters for category id = 8 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 8) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 8) - - // Test English azkar chapters for category id = 9 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 9) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 20) - - // Test English azkar chapters for category id = 10 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 10) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 10) - - // Test English azkar chapters for category id = 11 - chapters = try await MuslimRepository().getAzkarChapters(language: .en, categoryId: 11) - XCTAssertNotNil(chapters) - XCTAssertEqual(chapters!.count, 13) - } - - func testAzkarItems() async throws { - // Test English azkar items for chapter id = 1 - var items = try await MuslimRepository().getAzkarItems(language: .en, chapterId: 1) - XCTAssertNotNil(items) - XCTAssertEqual(items!.count, 4) - - // Test English azkar items for chapter id = 10 - items = try await MuslimRepository().getAzkarItems(language: .en, chapterId: 10) - XCTAssertNotNil(items) - XCTAssertEqual(items!.count, 2) - - // Test English azkar items for chapter id = 100 - items = try await MuslimRepository().getAzkarItems(language: .en, chapterId: 100) - XCTAssertNotNil(items) - XCTAssertEqual(items!.count, 1) - } -} diff --git a/Example/Tests/NamesTest.swift b/Example/Tests/NamesTest.swift deleted file mode 100755 index 4e245b1..0000000 --- a/Example/Tests/NamesTest.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// NamesTest.swift -// MuslimData_Tests -// -// Created by Kosrat D. Ahmad on 10/12/18. -// Copyright © 2018 CocoaPods. All rights reserved. -// - -@testable import MuslimData -import XCTest - -class NamesTest: XCTestCase { - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testEnglishNames() async throws { - let names = try await MuslimRepository().getNamesOfAllah(language: .en) - assesNames(names: names) - } - - func testArabicNames() async throws { - let names = try await MuslimRepository().getNamesOfAllah(language: .ar) - assesNames(names: names) - } - - func testKurdishNames() async throws { - let names = try await MuslimRepository().getNamesOfAllah(language: .ckb) - assesNames(names: names) - } - - func testFarsiNames() async throws { - let names = try await MuslimRepository().getNamesOfAllah(language: .fa) - assesNames(names: names) - } - - func testRussianNames() async throws { - let names = try await MuslimRepository().getNamesOfAllah(language: .ru) - assesNames(names: names) - } - - private func assesNames(names: [Name]?) { - XCTAssertNotNil(names) - XCTAssertEqual(names!.count, 99) - } -} diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index fe9e485..1e2fadf 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -3,13 +3,32 @@ ## MuslimDate Version 1.x to 2.x ### Introduction -This migration guide assists developers in transitioning from version 1.x to version 2.x of the `muslim-data-ios` library. Version 2 introduces improvements in database schema and table relations, including changes to the `Location` object structure. +This migration guide assists developers in transitioning from version 1.x to version 2.x of the `muslim-data-ios` library. Version 2 introduces improvements in database schema and table relations, including changes to the `Location` object structure and accessing its data through repository pattern. ### Changes Overview - Improved database table normalization and rearranged table relations. - Restructured the `Location` object schema for better data management and consistency. +- Implemented the Repository pattern for accessing data, replacing direct access through class methods. + ### Migration Steps +**Repository Pattern** +- Refactor your code to utilize the MuslimRepository for accessing data instead of direct access through class methods. +- Replace instances of direct data access with calls to appropriate methods provided by `MuslimRepository` class. +- Update your codebase to follow the repository pattern for improved testability and ease of mocking. + +The following code snippet shows how to access to PrayerTimes via `MuslimRepository` and the reset other examples can be found in the [README](README.md) file. +```swift +// Version 1.x +PrayerTime.getPrayerTimes(location: location, date: Date(), attributes: attributes) { prayerTime, error in + print("prayer times: \(prayerTime!)") +} + +// Version 2.x +let prayerTime = try await MuslimRepository().getPrayerTimes(location: location, date: Date(), attributes: attributes) +print("prayer times: \(prayerTime!)") +``` + **Update Location Object** - Modify the `Location` object structure in your code to align with version 2.x. ```swift diff --git a/MuslimData.podspec b/MuslimData.podspec index c60f3bb..d24b231 100755 --- a/MuslimData.podspec +++ b/MuslimData.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'MuslimData' - s.version = '1.6.0' + s.version = '2.0.0' s.summary = 'Islamic library (Prayer Times [fixed and calculated], Names of Allah, and Azkars).' # This description is used to generate tags and improve search results. @@ -24,25 +24,29 @@ Pod::Spec.new do |s| Most cities around the world find their prayer times by using some calculations which is based on location (longitude and latitude) but some other cities have fixed time table for their prayer times. This library contains most fixed and calculated prayer times. Now you can contribute it to improve it and also you can use it in Muslim communities or Muslim apps. DESC - s.homepage = 'https://github.com/KosratDAhmad/MuslimData' + s.homepage = 'https://github.com/kosratdev/muslim-data-ios' # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' - s.license = { :type => 'MIT', :file => 'LICENSE' } + s.license = { :type => 'Apache 2.0', :file => 'LICENSE' } s.author = { 'Kosrat D. Ahmad' => 'kosrat.d.ahmad@gmail.com' } - s.source = { :git => 'https://github.com/KosratDAhmad/MuslimData.git', :tag => s.version.to_s } + s.source = { :git => 'https://github.com/kosratdev/muslim-data-ios.git', :tag => s.version.to_s } # s.social_media_url = 'https://twitter.com/' s.ios.deployment_target = '13.0' + s.osx.deployment_target = '12.0' + s.tvos.deployment_target = '13.0' + s.visionos.deployment_target = '1.0' + s.watchos.deployment_target = '6.0' - s.source_files = 'MuslimData/Classes/**/*' + s.source_files = 'Sources/MuslimData/**/*' # s.resource_bundles = { - # 'MuslimData' => ['MuslimData/Assets/*.png'] + # 'MuslimData' => ['MuslimData/Resources/*.png'] # } - s.swift_version = "5.0" - s.resources = 'MuslimData/Assets/**/*' + s.swift_version = "5.10" + s.resources = 'Sources/MuslimData/Resources/**/*' # s.public_header_files = 'Pod/Classes/**/*.h' - s.frameworks = 'UIKit' - s.dependency 'GRDB.swift', '~> 4.0.1' + s.frameworks = 'Foundation' + s.dependency 'GRDB.swift', '6.26.0' end diff --git a/MuslimData/.swiftformat b/MuslimData/.swiftformat deleted file mode 100755 index 3fa3cc7..0000000 --- a/MuslimData/.swiftformat +++ /dev/null @@ -1,10 +0,0 @@ -# format options! ---swiftversion 5.1 - -# file options ---exclude Pods - -# rules ---disable trailingCommas,unusedArguments ---disable wrapMultilineStatementBraces - diff --git a/MuslimData/.swiftlint.yml b/MuslimData/.swiftlint.yml deleted file mode 100755 index 8a425c0..0000000 --- a/MuslimData/.swiftlint.yml +++ /dev/null @@ -1,12 +0,0 @@ -excluded: - - Pods - - ../MuslimData/Classes/Models/PrayerTimes/Prayer.swift -included: - - ../MuslimData -identifier_name: - excluded: - - id - - en - - ar - - fa - - ru diff --git a/MuslimData/Assets/.gitkeep b/MuslimData/Assets/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/MuslimData/Classes/.gitkeep b/MuslimData/Classes/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..fa2297b --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "8c31a9ebb45254e9bc0b36d90521d6765648b8096a56e4b9d125a6ff9014239c", + "pins" : [ + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/GRDB.swift.git", + "state" : { + "revision" : "77b85bed259b7f107710a0b78c439b1c5839dc45", + "version" : "6.26.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..e7ff948 --- /dev/null +++ b/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version: 5.10 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "MuslimData", + platforms: [ + .iOS(.v13), + .macOS(.v12), + .tvOS(.v13), + .visionOS(.v1), + .watchOS(.v6) + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "MuslimData", + targets: ["MuslimData"]), + ], + dependencies: [ + .package(url: "https://github.com/groue/GRDB.swift.git", exact: "6.26.0") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "MuslimData", + dependencies: [ + .product(name: "GRDB", package: "GRDB.swift") + ], + resources: [.process("Resources")] + ), + .testTarget( + name: "MuslimDataTests", + dependencies: ["MuslimData"]), + ] +) diff --git a/README.md b/README.md index 655576f..aca49a1 100755 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Build Status](https://github.com/kosratdev/muslim-data-ios/actions/workflows/deploy_to_cocoapods.yml/badge.svg)](https://github.com/kosratdev/muslim-data-ios/actions) [![Version](https://img.shields.io/cocoapods/v/MuslimData.svg?style=flat)](https://cocoapods.org/pods/MuslimData) +[![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square) [![License](https://img.shields.io/cocoapods/l/MuslimData.svg?style=flat)](https://cocoapods.org/pods/MuslimData) [![Platform](https://img.shields.io/cocoapods/p/MuslimData.svg?style=flat)](https://cocoapods.org/pods/MuslimData) @@ -15,17 +16,19 @@ If you're upgrading from version 1.x to version 2.x of `muslim-data-ios`, please ## Example -To run the example project, clone the repo, and run `pod install` from the Example directory first. +To run the example project, clone the repo, and run. - + ## Requirements -* iOS 10.0+ -* Xcode 10.0+ +* iOS 13.0+ +* Xcode 11.0+ ## Installation +### CocoaPods + MuslimData is available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile: @@ -33,6 +36,28 @@ it, simply add the following line to your Podfile: pod 'MuslimData' ``` +### Swift Package Manager + +The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. + +Once you have your Swift package set up, adding MuslimData as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift` or the Package list in Xcode. + +```swift +dependencies: [ + .package(url: "https://github.com/kosratdev/muslim-data-ios", .upToNextMajor(from: "2.0.0")) +] +``` + +##### Integrate with Xcode: + +If you prefer to manage your Swift packages via Xcode, you can also add Muslim Data as a dependency using Xcode's UI: + +1. Open your Xcode project. +2. Navigate to the `File` menu > `Swift Packages` > `Add Package Dependency...`. +3. Paste the Muslim Data GitHub repository URL (`https://github.com/kosratdev/muslim-data-ios.git`) into the search bar and click `Next`. +4. Choose the version rule (`Up to Next Major` from version `2.0.0`) and click `Next`. +5. Xcode will resolve the package and integrate it into your project. + ## Usage ### Location Helper @@ -44,7 +69,7 @@ There are some location helper methods that provides **offline** Location Search You can search for any cities or places around the world and this is useful when a use doesn't have internet connection or user's location is turned off so you can search here: ```swift -let locations = try! await MuslimData().searchLocation(locationName: "London") +let locations = try! await MuslimRepository().searchLocation(locationName: "London") guard let locations = locations, locations.count > 0 else { print("Location could not be found!") return diff --git a/MuslimData/Classes/DB Helper/DBHelper.swift b/Sources/MuslimData/DB Helper/DBHelper.swift similarity index 60% rename from MuslimData/Classes/DB Helper/DBHelper.swift rename to Sources/MuslimData/DB Helper/DBHelper.swift index fabe807..0208c3d 100755 --- a/MuslimData/Classes/DB Helper/DBHelper.swift +++ b/Sources/MuslimData/DB Helper/DBHelper.swift @@ -6,25 +6,33 @@ // import GRDB -import UIKit +import Foundation class DBHelper { // MARK: - Properties + private let dbName = "muslim_db_v2.0.0" var dbPool: DatabasePool? static let shared = DBHelper() + var dbPath: String { + #if SWIFT_PACKAGE + // SPM Access + return Bundle.module.path(forResource: dbName, ofType: "db")! + #else + // CocoaPods Access + return Bundle(for: DBHelper.self).path(forResource: dbName, ofType: "db")! + #endif + } + // MARK: - Life cycle private init() { var configuration = Configuration() configuration.readonly = true - let databaseURL = Bundle(for: DBHelper.self).path(forResource: "muslim_db_v2.0.0", ofType: "db")! - dbPool = try? DatabasePool(path: databaseURL, configuration: configuration) - - // Be a nice iOS citizen, and don’t consume too much memory - // See https://github.com/groue/GRDB.swift/#memory-management - dbPool?.setupMemoryManagement(in: UIApplication.shared) + dbPool = try? DatabasePool(path: dbPath, configuration: configuration) + + dbPool?.releaseMemoryEventually() } // MARK: - Public Methods @@ -36,11 +44,11 @@ class DBHelper { /// - Returns: PrayerTime instance. func prayerTimes(location: Location, date: Date) async throws -> PrayerTime? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let result = try Row.fetchOne(dbConnect, sql: """ SELECT * FROM prayer_time WHERE location_id = '\(location.prayerDependentId ?? location.id)' - and date = '\(date.toDBDate())' + AND date = '\(date.toDBDate())' """) guard let row = result else { @@ -81,11 +89,11 @@ class DBHelper { /// - Returns: List of [name]? of Allah func names(language: Language) async throws -> [Name]? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let names = try Name.fetchAll(dbConnect, sql: """ - SELECT name.name , transl.name as translated + SELECT name.name, transl.name AS translated FROM name - INNER JOIN name_translation as transl on transl.name_id = name._id and transl.language = '\(language)' + INNER JOIN name_translation AS transl ON transl.name_id = name._id AND transl.language = '\(language)' """) return names } @@ -102,12 +110,12 @@ class DBHelper { /// - Returns: List of [AzkarCategory]? func azkarCategories(language: Language) async throws -> [AzkarCategory]? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let categories = try AzkarCategory.fetchAll(dbConnect, sql: """ SELECT category._id, category_name - FROM azkar_category as category - INNER JOIN azkar_category_translation as transl on transl.category_id = category._id - and language = '\(language)' + FROM azkar_category AS category + INNER JOIN azkar_category_translation AS transl ON transl.category_id = category._id + AND language = '\(language)' """) return categories } @@ -117,24 +125,45 @@ class DBHelper { } } - /// Get azkar chapters from database which is localized by given language. - /// + /// Get azkar chapters from database which is localized by given language and filtered by category id. + /// /// - Parameters: /// - language: Language of the chapter. /// - categoryId: Optional category id /// - Returns: List of [AzkarChapter]? - func azkarChapters(language: Language, categoryId: Int? = nil) async throws -> [AzkarChapter]? { - var category = "" - if let categoryId = categoryId { - category = " and category_id = \(categoryId)" + func azkarChapters(language: Language, categoryId: Int = -1) async throws -> [AzkarChapter]? { + let category = categoryId == -1 ? "" : " AND category_id = \(String(describing: categoryId))" + + do { + return try await dbPool?.read { dbConnect in + let chapters = try AzkarChapter.fetchAll(dbConnect, sql: """ + SELECT chapter._id, category_id, chapter_name + FROM azkar_chapter AS chapter + INNER JOIN azkar_chapter_translation AS transl ON transl.chapter_id = chapter._id + AND language = '\(language)' \(category) + """) + return chapters + } + } catch { + print("Error: \(error.localizedDescription)") + return nil } + } + + /// Get azkar chapters from database which is localized by given language and filtered by chapter ids. + /// + /// - Parameters: + /// - language: Language of the chapter. + /// - chapterIds: Array of chapter ids + /// - Returns: List of [AzkarChapter]? + func azkarChapters(language: Language, chapterIds: [Int]) async throws -> [AzkarChapter]? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let chapters = try AzkarChapter.fetchAll(dbConnect, sql: """ SELECT chapter._id, category_id, chapter_name - FROM azkar_chapter as chapter - INNER JOIN azkar_chapter_translation as transl on transl.chapter_id = chapter._id - and language = '\(language)' \(category) + FROM azkar_chapter AS chapter + INNER JOIN azkar_chapter_translation AS transl ON transl.chapter_id = chapter._id + AND language = '\(language)' AND chapter._id IN (\(chapterIds.minimalDescription)) """) return chapters } @@ -152,15 +181,15 @@ class DBHelper { /// - Returns: List of [AskarItem]? func azkarItems(language: Language, chapterId: Int) async throws -> [AzkarItem]? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let items = try AzkarItem.fetchAll(dbConnect, sql: """ SELECT item._id, item.chapter_id, item.item, transl.item_translation, ref_transl.reference - FROM azkar_item as item - INNER JOIN azkar_item_translation as transl on transl.item_id = item._id - and transl.language = '\(language)' and item.chapter_id = \(chapterId) - INNER JOIN azkar_reference as ref on ref.item_id = item._id - INNER JOIN azkar_reference_translation as ref_transl on ref_transl.reference_id = ref._id - and ref_transl.language = '\(language)' + FROM azkar_item AS item + INNER JOIN azkar_item_translation AS transl ON transl.item_id = item._id + AND transl.language = '\(language)' AND item.chapter_id = \(chapterId) + INNER JOIN azkar_reference AS ref ON ref.item_id = item._id + INNER JOIN azkar_reference_translation AS ref_transl ON ref_transl.reference_id = ref._id + AND ref_transl.language = '\(language)' """) return items } @@ -176,14 +205,14 @@ class DBHelper { /// - locationName: location name /// - callback: Callback that returns a Location object. /// - Returns: List of found locations - public func searchLocation(_ locationName: String) async throws -> [Location]? { + func searchLocation(_ locationName: String) async throws -> [Location]? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let locations = try Location.fetchAll(dbConnect, sql: """ - SELECT location._id as _id, country.code as country_code, country.name as country_name, - location.name as name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id + SELECT location._id AS _id, country.code AS country_code, country.name AS country_name, + location.name AS name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id FROM location - INNER JOIN country on country._id = location.country_id + INNER JOIN country ON country._id = location.country_id WHERE location.name like '\(locationName)%' """) return locations @@ -200,16 +229,16 @@ class DBHelper { /// - countryCode: Country code /// - locationName: location name /// - Returns: geocoded location - public func geocoder(countryCode: String, locationName: String) async throws -> Location? { + func geocoder(countryCode: String, locationName: String) async throws -> Location? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let location = try Location.fetchOne(dbConnect, sql: """ - SELECT location._id as _id, country.code as country_code, country.name as country_name, - location.name as name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id + SELECT location._id AS _id, country.code AS country_code, country.name AS country_name, + location.name AS name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id FROM location - INNER JOIN country on country._id = location.country_id + INNER JOIN country ON country._id = location.country_id WHERE country.code = '\(countryCode)' COLLATE NOCASE - and location.name = '\(locationName)' COLLATE NOCASE + AND location.name = '\(locationName)' COLLATE NOCASE """) return location } @@ -225,15 +254,15 @@ class DBHelper { /// - latitude: Location latitude. /// - longitude: Location longitude. /// - Returns: reverse geocoded location - public func reverseGeocoder(latitude: Double, longitude: Double) async throws -> Location? { + func reverseGeocoder(latitude: Double, longitude: Double) async throws -> Location? { do { - return try dbPool?.read { dbConnect in + return try await dbPool?.read { dbConnect in let location = try Location.fetchOne(dbConnect, sql: """ - SELECT location._id as _id, country.code as country_code, country.name as country_name, - location.name as name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id + SELECT location._id AS _id, country.code AS country_code, country.name AS country_name, + location.name AS name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id FROM location - INNER JOIN country on country._id = location.country_id - ORDER BY abs(latitude - (\(latitude))) + abs(longitude - (\(longitude))) + INNER JOIN country ON country._id = location.country_id + ORDER BY ABS(latitude - (\(latitude))) + ABS(longitude - (\(longitude))) LIMIT 1 """) return location @@ -246,15 +275,15 @@ class DBHelper { /// Get all the locations that has fixed prayer times. /// - Returns: Location list - public func fixedPrayerTimesList() -> [Location]? { + func fixedPrayerTimesList() -> [Location]? { do { return try dbPool?.read { dbConnect in let locations = try Location.fetchAll(dbConnect, sql: """ - SELECT location._id as _id, country.code as country_code, country.name as country_name, - location.name as name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id + SELECT location._id AS _id, country.code AS country_code, country.name AS country_name, + location.name AS name, latitude, longitude, has_fixed_prayer_time, prayer_dependent_id FROM location - INNER JOIN country on country._id = location.country_id - WHERE has_fixed_prayer_time=1 + INNER JOIN country ON country._id = location.country_id + WHERE has_fixed_prayer_time = 1 """) return locations } diff --git a/MuslimData/Classes/Extensions/DateExtensions.swift b/Sources/MuslimData/Extensions/DateExtensions.swift similarity index 66% rename from MuslimData/Classes/Extensions/DateExtensions.swift rename to Sources/MuslimData/Extensions/DateExtensions.swift index 0c3c794..1d0252e 100755 --- a/MuslimData/Classes/Extensions/DateExtensions.swift +++ b/Sources/MuslimData/Extensions/DateExtensions.swift @@ -7,11 +7,9 @@ import Foundation -// MARK: - Date Extensions +extension Date { -public extension Date { - - /// Format date to "MM-dd" pattern which will be used to get prayers fro this date in the prayer database. + /// Format date to "MM-dd" pattern which will be used to get prayers for this date in the prayer database. /// /// - Returns: Formatted date by "MM-dd" pattern. func toDBDate() -> String { @@ -25,19 +23,30 @@ public extension Date { /// /// - Parameter format: TimeFormat object. /// - Returns: Formatted date to string time. - func toTime(format: TimeFormat) -> String { - let dateFormatter = DateFormatter() - switch format { + func format(format: TimeFormat, locale: Locale) -> String { + return switch format { case .time24: - dateFormatter.dateFormat = "HH:mm" - dateFormatter.locale = Locale(identifier: "en_GB") + formatTime24(locale: locale) case .time12: - dateFormatter.dateFormat = "hh:mm a" - dateFormatter.locale = Locale(identifier: "en_US_POSIX") + formatTime12(locale: locale) } - return dateFormatter.string(from: self) + } + private func formatTime24(locale: Locale) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm" + dateFormatter.locale = locale + return dateFormatter.string(from: self) + } + + private func formatTime12(locale: Locale) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm a" + dateFormatter.locale = locale + return dateFormatter.string(from: self) + } + /// Add minutes to the date. /// /// - Parameter minutes: minutes to be added to the date. diff --git a/Sources/MuslimData/Extensions/SequenceExtensions.swift b/Sources/MuslimData/Extensions/SequenceExtensions.swift new file mode 100644 index 0000000..5070397 --- /dev/null +++ b/Sources/MuslimData/Extensions/SequenceExtensions.swift @@ -0,0 +1,15 @@ +// +// SequenceExtensions.swift +// +// +// Created by Kosrat Ahmed on 24/03/2024. +// + +import Foundation + +extension Sequence { + /// Return a string of the array values without brackets. + var minimalDescription: String { + return map { "\($0)" }.joined(separator: ", ") + } +} diff --git a/MuslimData/Classes/Extensions/String+Extensions.swift b/Sources/MuslimData/Extensions/StringExtensions.swift similarity index 96% rename from MuslimData/Classes/Extensions/String+Extensions.swift rename to Sources/MuslimData/Extensions/StringExtensions.swift index fda8c0e..d98666e 100755 --- a/MuslimData/Classes/Extensions/String+Extensions.swift +++ b/Sources/MuslimData/Extensions/StringExtensions.swift @@ -7,8 +7,6 @@ import Foundation -// MARK: - String Extensions - extension String { /// Convert string time to date object. /// diff --git a/MuslimData/Classes/Models/Azkars/AzkarCategory.swift b/Sources/MuslimData/Models/Azkars/AzkarCategory.swift similarity index 100% rename from MuslimData/Classes/Models/Azkars/AzkarCategory.swift rename to Sources/MuslimData/Models/Azkars/AzkarCategory.swift diff --git a/MuslimData/Classes/Models/Azkars/AzkarChapter.swift b/Sources/MuslimData/Models/Azkars/AzkarChapter.swift similarity index 100% rename from MuslimData/Classes/Models/Azkars/AzkarChapter.swift rename to Sources/MuslimData/Models/Azkars/AzkarChapter.swift diff --git a/MuslimData/Classes/Models/Azkars/AzkarItem.swift b/Sources/MuslimData/Models/Azkars/AzkarItem.swift similarity index 100% rename from MuslimData/Classes/Models/Azkars/AzkarItem.swift rename to Sources/MuslimData/Models/Azkars/AzkarItem.swift diff --git a/MuslimData/Classes/Models/Names/Language.swift b/Sources/MuslimData/Models/Language.swift similarity index 100% rename from MuslimData/Classes/Models/Names/Language.swift rename to Sources/MuslimData/Models/Language.swift diff --git a/MuslimData/Classes/Models/Location/Location.swift b/Sources/MuslimData/Models/Location/Location.swift similarity index 100% rename from MuslimData/Classes/Models/Location/Location.swift rename to Sources/MuslimData/Models/Location/Location.swift diff --git a/MuslimData/Classes/Models/Names/Name.swift b/Sources/MuslimData/Models/Names/Name.swift similarity index 100% rename from MuslimData/Classes/Models/Names/Name.swift rename to Sources/MuslimData/Models/Names/Name.swift diff --git a/MuslimData/Classes/Models/PrayerTimes/Prayer.swift b/Sources/MuslimData/Models/PrayerTimes/Prayer.swift similarity index 100% rename from MuslimData/Classes/Models/PrayerTimes/Prayer.swift rename to Sources/MuslimData/Models/PrayerTimes/Prayer.swift diff --git a/MuslimData/Classes/Models/PrayerTimes/PrayerAttribute.swift b/Sources/MuslimData/Models/PrayerTimes/PrayerAttribute.swift similarity index 100% rename from MuslimData/Classes/Models/PrayerTimes/PrayerAttribute.swift rename to Sources/MuslimData/Models/PrayerTimes/PrayerAttribute.swift diff --git a/MuslimData/Classes/Models/PrayerTimes/PrayerTime.swift b/Sources/MuslimData/Models/PrayerTimes/PrayerTime.swift similarity index 75% rename from MuslimData/Classes/Models/PrayerTimes/PrayerTime.swift rename to Sources/MuslimData/Models/PrayerTimes/PrayerTime.swift index 653f017..ef6bd67 100755 --- a/MuslimData/Classes/Models/PrayerTimes/PrayerTime.swift +++ b/Sources/MuslimData/Models/PrayerTimes/PrayerTime.swift @@ -18,12 +18,21 @@ public struct PrayerTime { public var maghrib: Date public var isha: Date + public init(fajr: Date, sunrise: Date, dhuhr: Date, asr: Date, maghrib: Date, isha: Date) { + self.fajr = fajr + self.sunrise = sunrise + self.dhuhr = dhuhr + self.asr = asr + self.maghrib = maghrib + self.isha = isha + } + // MARK: - Internal Methods /// Apply offests to the current prayer times. /// /// - Parameter offsets: List of double values as prayer offsets. - internal mutating func applyOffsets(_ offsets: [Double]) { + mutating func applyOffsets(_ offsets: [Double]) { fajr = fajr.addMinutes(offsets[0]) sunrise = sunrise.addMinutes(offsets[1]) dhuhr = dhuhr.addMinutes(offsets[2]) @@ -33,7 +42,7 @@ public struct PrayerTime { } /// Apply daylight saving time to the current prayer times. - internal mutating func applyDST() { + mutating func applyDST() { let isDST = TimeZone.current.isDaylightSavingTime() if isDST { @@ -53,9 +62,13 @@ public struct PrayerTime { /// /// - Parameter format: TimeFormat instance. /// - Returns: Array of formatted prayer times - public func formatPrayers(_ format: TimeFormat) -> [String] { - [fajr.toTime(format: format), sunrise.toTime(format: format), dhuhr.toTime(format: format), - asr.toTime(format: format), maghrib.toTime(format: format), isha.toTime(format: format)] + public func formatPrayers(_ format: TimeFormat, locale: Locale = Locale(identifier: "en_US")) -> [String] { + [fajr.format(format: format, locale: locale), + sunrise.format(format: format, locale: locale), + dhuhr.format(format: format, locale: locale), + asr.format(format: format, locale: locale), + maghrib.format(format: format, locale: locale), + isha.format(format: format, locale: locale)] } /// Get next prayer index and if all prayer times passed it will return 0. @@ -100,4 +113,13 @@ public struct PrayerTime { let seconds = Int(time) % 60 return String(format: "%02i:%02i:%02i", hours, minutes, seconds) } + + public subscript(index: Int) -> Date { + let prayers = [fajr, sunrise, dhuhr, asr, maghrib, isha] + return if (index < 0) { + prayers[5] + } else { + prayers[index] + } + } } diff --git a/MuslimData/Classes/Models/PrayerTimes/TimeFormat.swift b/Sources/MuslimData/Models/PrayerTimes/TimeFormat.swift similarity index 100% rename from MuslimData/Classes/Models/PrayerTimes/TimeFormat.swift rename to Sources/MuslimData/Models/PrayerTimes/TimeFormat.swift diff --git a/MuslimData/Classes/Repository/MuslimRepository.swift b/Sources/MuslimData/Repository/MuslimRepository.swift similarity index 87% rename from MuslimData/Classes/Repository/MuslimRepository.swift rename to Sources/MuslimData/Repository/MuslimRepository.swift index c18b162..2815852 100644 --- a/MuslimData/Classes/Repository/MuslimRepository.swift +++ b/Sources/MuslimData/Repository/MuslimRepository.swift @@ -87,22 +87,27 @@ public class MuslimRepository: Repository { /// - language: Language of the azkar chapters. /// - categoryId: Optional category id /// - Returns: List of [AzkarChapter]? - public func getAzkarChapters(language: Language, categoryId: Int? = nil) async throws -> [AzkarChapter]? { + public func getAzkarChapters(language: Language, categoryId: Int = -1) async throws -> [AzkarChapter]? { return try await DBHelper.shared.azkarChapters(language: language, categoryId: categoryId) } + /// Get azkar chapters from the database for the specified language and category id. + /// + /// - Parameters: + /// - language: Language of the azkar chapters. + /// - categoryId: Optional category id + /// - Returns: List of [AzkarChapter]? + public func getAzkarChapters(language: Language, chapterIds: [Int]) async throws -> [AzkarChapter]? { + return try await DBHelper.shared.azkarChapters(language: language, chapterIds: chapterIds) + } + /// Get azkar items for specific azkar chapter from database which is localized by the given language. /// /// - Parameters: - /// - language: Language of the chapter. /// - chapterId: Chapter id for the azkar items. + /// - language: Language of the chapter. /// - Returns: List of [AzkarItem]? - public func getAzkarItems(language: Language, chapterId: Int) async throws -> [AzkarItem]? { + public func getAzkarItems(chapterId: Int, language: Language) async throws -> [AzkarItem]? { return try await DBHelper.shared.azkarItems(language: language, chapterId: chapterId) } - - // TODO: it needs to be deleted when the tests migrated to the package itself. - public func getAllFixedPrayerLocations() async throws -> [Location]? { - return try await DBHelper.shared.fixedPrayerTimesList() - } } diff --git a/MuslimData/Classes/Repository/Repository.swift b/Sources/MuslimData/Repository/Repository.swift similarity index 72% rename from MuslimData/Classes/Repository/Repository.swift rename to Sources/MuslimData/Repository/Repository.swift index 94e875d..f3ac7fa 100644 --- a/MuslimData/Classes/Repository/Repository.swift +++ b/Sources/MuslimData/Repository/Repository.swift @@ -20,9 +20,9 @@ public protocol Repository { func getAzkarCategories(language: Language) async throws -> [AzkarCategory]? - func getAzkarChapters(language: Language, categoryId: Int?) async throws -> [AzkarChapter]? + func getAzkarChapters(language: Language, categoryId: Int) async throws -> [AzkarChapter]? + + func getAzkarChapters(language: Language, chapterIds: [Int]) async throws -> [AzkarChapter]? - func getAzkarItems(language: Language, chapterId: Int) async throws -> [AzkarItem]? - - func getAllFixedPrayerLocations() async throws -> [Location]? + func getAzkarItems(chapterId: Int, language: Language) async throws -> [AzkarItem]? } diff --git a/MuslimData/Assets/CHANGELOG.md b/Sources/MuslimData/Resources/CHANGELOG.md similarity index 95% rename from MuslimData/Assets/CHANGELOG.md rename to Sources/MuslimData/Resources/CHANGELOG.md index 8b7e7eb..bd77383 100644 --- a/MuslimData/Assets/CHANGELOG.md +++ b/Sources/MuslimData/Resources/CHANGELOG.md @@ -22,6 +22,8 @@ Refactor database tables to improve table normalization and handle city mapper i - Database indices have been refactored as shown blow: - `country` table has `code_index` for indexing `code` column. - `location` table has has two indices as listed below: + - `location_country_id_index` for indexing `country_id` column. + - `location_prayer_dependent_id_index` for indexing `prayer_dependent_id` column. - `location_lat_long_index` for indexing `latitude` and `longitude` columns. - `location_name_index` for indexing `name` column. - `prayer_time` table has `prayer_index` for indexing `location_id`, `date` columns. diff --git a/MuslimData/Assets/muslim_db_v2.0.0.db b/Sources/MuslimData/Resources/muslim_db_v2.0.0.db similarity index 90% rename from MuslimData/Assets/muslim_db_v2.0.0.db rename to Sources/MuslimData/Resources/muslim_db_v2.0.0.db index c655d26..843d913 100644 Binary files a/MuslimData/Assets/muslim_db_v2.0.0.db and b/Sources/MuslimData/Resources/muslim_db_v2.0.0.db differ diff --git a/Tests/MuslimDataTests/AzkarTests/AzkarCategoryTests.swift b/Tests/MuslimDataTests/AzkarTests/AzkarCategoryTests.swift new file mode 100644 index 0000000..8ee9b19 --- /dev/null +++ b/Tests/MuslimDataTests/AzkarTests/AzkarCategoryTests.swift @@ -0,0 +1,46 @@ +// +// File.swift +// +// +// Created by Kosrat Ahmed on 24/03/2024. +// + +@testable import MuslimData +import XCTest + +class AzkarCategoryTests: XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testEnAzkarCategories() async throws { + try await assesAzkarCategories(language: .en) + } + + func testArAzkarCategories() async throws { + try await assesAzkarCategories(language: .ar) + } + + func testCkbAzkarCategories() async throws { + try await assesAzkarCategories(language: .ckb) + } + + func testFaAzkarCategories() async throws { + try await assesAzkarCategories(language: .fa) + } + + func testRuAzkarCategories() async throws { + try await assesAzkarCategories(language: .ru) + } + + private func assesAzkarCategories(language: Language) async throws { + let categories = try await MuslimRepository().getAzkarCategories(language: language) + XCTAssertNotNil(categories) + XCTAssertEqual(categories!.count, 11) + XCTAssertNotNil(categories![Int.random(in: 0 ..< 11)].name) + } +} diff --git a/Tests/MuslimDataTests/AzkarTests/AzkarChapterTests.swift b/Tests/MuslimDataTests/AzkarTests/AzkarChapterTests.swift new file mode 100644 index 0000000..ffe56c1 --- /dev/null +++ b/Tests/MuslimDataTests/AzkarTests/AzkarChapterTests.swift @@ -0,0 +1,121 @@ +// +// File.swift +// +// +// Created by Kosrat Ahmed on 24/03/2024. +// + +@testable import MuslimData +import XCTest + +class AzkarChapterTests: XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testEnAzkarChapters() async throws { + try await assesAzkarChapters(language: .en) + } + + func testArAzkarChapters() async throws { + try await assesAzkarChapters(language: .ar) + } + + func testCkbAzkarChapters() async throws { + try await assesAzkarChapters(language: .ckb) + } + + func testFaAzkarChapters() async throws { + try await assesAzkarChapters(language: .fa) + } + + func testRuAzkarChapters() async throws { + try await assesAzkarChapters(language: .ru) + } + + private func assesAzkarChapters(language: Language, categoryId: Int = -1, total: Int = 133) async throws { + let chapters = try await MuslimRepository().getAzkarChapters(language: language, categoryId: categoryId) + XCTAssertNotNil(chapters) + XCTAssertEqual(chapters!.count, total) + } + + func testAzkarChaptersByChapterIds() async throws { + let chapters = try await MuslimRepository().getAzkarChapters(language: .en, chapterIds: Array(1...133)) + XCTAssertNotNil(chapters) + XCTAssertEqual(chapters!.count, 133) + } + + func testEnAzkarChaptersByCategory() async throws { + try await assesAzkarChapters(language: .en, categoryId: 1, total: 7) + try await assesAzkarChapters(language: .en, categoryId: 2, total: 14) + try await assesAzkarChapters(language: .en, categoryId: 3, total: 7) + try await assesAzkarChapters(language: .en, categoryId: 4, total: 15) + try await assesAzkarChapters(language: .en, categoryId: 5, total: 11) + try await assesAzkarChapters(language: .en, categoryId: 6, total: 19) + try await assesAzkarChapters(language: .en, categoryId: 7, total: 9) + try await assesAzkarChapters(language: .en, categoryId: 8, total: 8) + try await assesAzkarChapters(language: .en, categoryId: 9, total: 20) + try await assesAzkarChapters(language: .en, categoryId: 10, total: 10) + try await assesAzkarChapters(language: .en, categoryId: 11, total: 13) + } + + func testArAzkarChaptersByCategory() async throws { + try await assesAzkarChapters(language: .ar, categoryId: 1, total: 7) + try await assesAzkarChapters(language: .ar, categoryId: 2, total: 14) + try await assesAzkarChapters(language: .ar, categoryId: 3, total: 7) + try await assesAzkarChapters(language: .ar, categoryId: 4, total: 15) + try await assesAzkarChapters(language: .ar, categoryId: 5, total: 11) + try await assesAzkarChapters(language: .ar, categoryId: 6, total: 19) + try await assesAzkarChapters(language: .ar, categoryId: 7, total: 9) + try await assesAzkarChapters(language: .ar, categoryId: 8, total: 8) + try await assesAzkarChapters(language: .ar, categoryId: 9, total: 20) + try await assesAzkarChapters(language: .ar, categoryId: 10, total: 10) + try await assesAzkarChapters(language: .ar, categoryId: 11, total: 13) + } + + func testCkbAzkarChaptersByCategory() async throws { + try await assesAzkarChapters(language: .ckb, categoryId: 1, total: 7) + try await assesAzkarChapters(language: .ckb, categoryId: 2, total: 14) + try await assesAzkarChapters(language: .ckb, categoryId: 3, total: 7) + try await assesAzkarChapters(language: .ckb, categoryId: 4, total: 15) + try await assesAzkarChapters(language: .ckb, categoryId: 5, total: 11) + try await assesAzkarChapters(language: .ckb, categoryId: 6, total: 19) + try await assesAzkarChapters(language: .ckb, categoryId: 7, total: 9) + try await assesAzkarChapters(language: .ckb, categoryId: 8, total: 8) + try await assesAzkarChapters(language: .ckb, categoryId: 9, total: 20) + try await assesAzkarChapters(language: .ckb, categoryId: 10, total: 10) + try await assesAzkarChapters(language: .ckb, categoryId: 11, total: 13) + } + + func testFaAzkarChaptersByCategory() async throws { + try await assesAzkarChapters(language: .fa, categoryId: 1, total: 7) + try await assesAzkarChapters(language: .fa, categoryId: 2, total: 14) + try await assesAzkarChapters(language: .fa, categoryId: 3, total: 7) + try await assesAzkarChapters(language: .fa, categoryId: 4, total: 15) + try await assesAzkarChapters(language: .fa, categoryId: 5, total: 11) + try await assesAzkarChapters(language: .fa, categoryId: 6, total: 19) + try await assesAzkarChapters(language: .fa, categoryId: 7, total: 9) + try await assesAzkarChapters(language: .fa, categoryId: 8, total: 8) + try await assesAzkarChapters(language: .fa, categoryId: 9, total: 20) + try await assesAzkarChapters(language: .fa, categoryId: 10, total: 10) + try await assesAzkarChapters(language: .fa, categoryId: 11, total: 13) + } + + func testRuAzkarChaptersByCategory() async throws { + try await assesAzkarChapters(language: .ru, categoryId: 1, total: 7) + try await assesAzkarChapters(language: .ru, categoryId: 2, total: 14) + try await assesAzkarChapters(language: .ru, categoryId: 3, total: 7) + try await assesAzkarChapters(language: .ru, categoryId: 4, total: 15) + try await assesAzkarChapters(language: .ru, categoryId: 5, total: 11) + try await assesAzkarChapters(language: .ru, categoryId: 6, total: 19) + try await assesAzkarChapters(language: .ru, categoryId: 7, total: 9) + try await assesAzkarChapters(language: .ru, categoryId: 8, total: 8) + try await assesAzkarChapters(language: .ru, categoryId: 9, total: 20) + try await assesAzkarChapters(language: .ru, categoryId: 10, total: 10) + try await assesAzkarChapters(language: .ru, categoryId: 11, total: 13) + } +} diff --git a/Tests/MuslimDataTests/AzkarTests/AzkarItemTests.swift b/Tests/MuslimDataTests/AzkarTests/AzkarItemTests.swift new file mode 100755 index 0000000..c0778dd --- /dev/null +++ b/Tests/MuslimDataTests/AzkarTests/AzkarItemTests.swift @@ -0,0 +1,46 @@ +// +// AzkarTests.swift +// MuslimData_Tests +// +// Created by Kosrat D. Ahmad on 10/13/18. +// Copyright © 2018 CocoaPods. All rights reserved. +// + +@testable import MuslimData +import XCTest + +class AzkarItemTests: XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testEnAzkarItems() async throws { + try await assesAzkarItems(language: .en, id: 1, total: 4) + } + + func testArAzkarItems() async throws { + try await assesAzkarItems(language: .ar, id: 25, total: 8) + } + + func testCkbAzkarItems() async throws { + try await assesAzkarItems(language: .ckb, id: 50, total: 2) + } + + func testFaAzkarItems() async throws { + try await assesAzkarItems(language: .fa, id: 75, total: 1) + } + + func testRuAzkarItems() async throws { + try await assesAzkarItems(language: .ru, id: 100, total: 1) + } + + func assesAzkarItems(language: Language, id: Int, total: Int) async throws { + let items = try await MuslimRepository().getAzkarItems(chapterId: id, language: language) + XCTAssertNotNil(items) + XCTAssertEqual(items!.count, total) + } +} diff --git a/Example/Tests/Info.plist b/Tests/MuslimDataTests/Info.plist similarity index 100% rename from Example/Tests/Info.plist rename to Tests/MuslimDataTests/Info.plist diff --git a/Example/Tests/LocationTests.swift b/Tests/MuslimDataTests/LocationTests/LocationTests.swift similarity index 100% rename from Example/Tests/LocationTests.swift rename to Tests/MuslimDataTests/LocationTests/LocationTests.swift diff --git a/Tests/MuslimDataTests/NamesTests/NamesTests.swift b/Tests/MuslimDataTests/NamesTests/NamesTests.swift new file mode 100755 index 0000000..a2a2737 --- /dev/null +++ b/Tests/MuslimDataTests/NamesTests/NamesTests.swift @@ -0,0 +1,47 @@ +// +// NamesTest.swift +// MuslimData_Tests +// +// Created by Kosrat D. Ahmad on 10/12/18. +// Copyright © 2018 CocoaPods. All rights reserved. +// + +@testable import MuslimData +import XCTest + +class NamesTests: XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testEnNamesOfAllah() async throws { + try await assesNames(language: .en) + } + + func testArNamesOfAllah() async throws { + try await assesNames(language: .ar) + } + + func testCkbNamesOfAllah() async throws { + try await assesNames(language: .ckb) + } + + func testFaNamesOfAllah() async throws { + try await assesNames(language: .fa) + + } + + func testRuNamesOfAllah() async throws { + try await assesNames(language: .ru) + } + + private func assesNames(language: Language) async throws { + let names = try await MuslimRepository().getNamesOfAllah(language: language) + XCTAssertNotNil(names) + XCTAssertEqual(names!.count, 99) + } +} diff --git a/Tests/MuslimDataTests/PrayerTests/DateFormatTests.swift b/Tests/MuslimDataTests/PrayerTests/DateFormatTests.swift new file mode 100644 index 0000000..73f4667 --- /dev/null +++ b/Tests/MuslimDataTests/PrayerTests/DateFormatTests.swift @@ -0,0 +1,64 @@ +// +// File.swift +// +// +// Created by Kosrat Ahmed on 24/03/2024. +// + +@testable import MuslimData +import XCTest + +class DateFormatTests: XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testToDBDate() { + XCTAssertEqual(TestUtils.getDate().toDBDate(), "03-11") + } + + func testEnFormatTime24() { + XCTAssertEqual(TestUtils.getDate().format(format: .time24, locale: Locale(identifier: "en_US")), "10:10") + } + + func testArFormatTime24() { + XCTAssertEqual(TestUtils.getDate().format(format: .time24, locale: Locale(identifier: "ar")), "١٠:١٠") + } + + func testCkbFormatTime24() { + XCTAssertEqual(TestUtils.getDate().format(format: .time24, locale: Locale(identifier: "ckb")), "١٠:١٠") + } + + func testEnFormatTime12() { + XCTAssertEqual(TestUtils.getDate().format(format: .time12, locale: Locale(identifier: "en_US")), "10:10 AM") + XCTAssertEqual(TestUtils.getDate().addHours(12).format(format: .time12, locale: Locale(identifier: "en_US")), "10:10 PM") + } + + func testArFormatTime12() { + XCTAssertEqual(TestUtils.getDate().format(format: .time12, locale: Locale(identifier: "ar")), "١٠:١٠ ص") + XCTAssertEqual(TestUtils.getDate().addHours(12).format(format: .time12, locale: Locale(identifier: "ar")), "١٠:١٠ م") + } + + func testCkbFormatTime12() { + XCTAssertEqual(TestUtils.getDate().format(format: .time12, locale: Locale(identifier: "ckb")), "١٠:١٠ ب.ن") + XCTAssertEqual(TestUtils.getDate().addHours(12).format(format: .time12, locale: Locale(identifier: "ckb")), "١٠:١٠ د.ن") + } + + func testStringToDate() { + let actualDate = TestUtils.getDate(year: 2024, month: 3, day: 11, hour: 15, minute: 15, second: 0) + XCTAssertEqual("15:15".toDate(TestUtils.getDate()), actualDate) + XCTAssertEqual("10:10".toDate(TestUtils.getDate(hour: 11, minute: 15)), TestUtils.getDate()) + } + + func testAddHoursToDate() { + XCTAssertEqual(TestUtils.getDate().addHours(5), TestUtils.getDate(hour: 15)) + } + + func testAddMinutesToDate() { + XCTAssertEqual(TestUtils.getDate().addMinutes(5), TestUtils.getDate(minute: 15)) + } +} diff --git a/Tests/MuslimDataTests/PrayerTests/PrayerTests.swift b/Tests/MuslimDataTests/PrayerTests/PrayerTests.swift new file mode 100644 index 0000000..bfd850c --- /dev/null +++ b/Tests/MuslimDataTests/PrayerTests/PrayerTests.swift @@ -0,0 +1,54 @@ +// +// File.swift +// +// +// Created by Kosrat Ahmed on 24/03/2024. +// + +@testable import MuslimData +import XCTest + +class PrayerTests: XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testPrayerTimesOffset() { + var prayer = TestUtils.getPrayers() + prayer.applyOffsets([1, 2, 3, 2, 1, 5]) + + XCTAssertEqual(prayer.fajr, TestUtils.getDate(hour: 5, minute: 1, second: 0)) + XCTAssertEqual(prayer.sunrise, TestUtils.getDate(hour: 7, minute: 2, second: 0)) + XCTAssertEqual(prayer.dhuhr, TestUtils.getDate(hour: 12, minute: 3, second: 0)) + XCTAssertEqual(prayer.asr, TestUtils.getDate(hour: 15, minute: 2, second: 0)) + XCTAssertEqual(prayer.maghrib, TestUtils.getDate(hour: 18, minute: 1, second: 0)) + XCTAssertEqual(prayer.isha, TestUtils.getDate(hour: 19, minute: 5, second: 0)) + } + + func testFormatPrayers() { + let prayer = TestUtils.getPrayers() + let formattedPrayers = prayer.formatPrayers(.time12, locale: Locale(identifier: "en_US")) + + XCTAssertEqual(formattedPrayers[0], "05:00 AM") + XCTAssertEqual(formattedPrayers[1], "07:00 AM") + XCTAssertEqual(formattedPrayers[2], "12:00 PM") + XCTAssertEqual(formattedPrayers[3], "03:00 PM") + XCTAssertEqual(formattedPrayers[4], "06:00 PM") + XCTAssertEqual(formattedPrayers[5], "07:00 PM") + } + + func testPrayerSubscripts() { + let prayer = TestUtils.getPrayers() + + XCTAssertEqual(prayer[0], TestUtils.getDate(hour: 5, minute: 0, second: 0)) + XCTAssertEqual(prayer[1], TestUtils.getDate(hour: 7, minute: 0, second: 0)) + XCTAssertEqual(prayer[2], TestUtils.getDate(hour: 12, minute: 0, second: 0)) + XCTAssertEqual(prayer[3], TestUtils.getDate(hour: 15, minute: 0, second: 0)) + XCTAssertEqual(prayer[4], TestUtils.getDate(hour: 18, minute: 0, second: 0)) + XCTAssertEqual(prayer[5], TestUtils.getDate(hour: 19, minute: 0, second: 0)) + } +} diff --git a/Example/Tests/PrayerTests.swift b/Tests/MuslimDataTests/PrayerTests/PrayerTimesTests.swift similarity index 78% rename from Example/Tests/PrayerTests.swift rename to Tests/MuslimDataTests/PrayerTests/PrayerTimesTests.swift index bd2d23b..7c89c66 100755 --- a/Example/Tests/PrayerTests.swift +++ b/Tests/MuslimDataTests/PrayerTests/PrayerTimesTests.swift @@ -9,31 +9,26 @@ @testable import MuslimData import XCTest -class PrayerTests: XCTestCase { +class PrayerTimesTests: XCTestCase { var attributes: PrayerAttribute! - var date: Date! var offsets: [Double]! override func setUp() { super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - date = Date(timeIntervalSince1970: 1_538_956_800) // 2018/10/08 offsets = [1, 1, 1, -1, -1, -1] attributes = PrayerAttribute(method: .makkah, asrMethod: .shafii, adjustAngle: .angleBased, offsets: offsets) } override func tearDown() { - date = nil attributes = nil super.tearDown() - // Put teardown code here. This method is called after the invocation of each test method in the class. } func testAllFixedPrayerTimes() async throws { - let locations = try await MuslimRepository().getAllFixedPrayerLocations() + let locations = DBHelper.shared.fixedPrayerTimesList() let date = Date(timeIntervalSince1970: 1_709_206_718) // 2024/02/29 for location in locations! { - print(location) + // print(location) let prayerTimes = try await MuslimRepository().getPrayerTimes(location: location, date: date, attributes: attributes) XCTAssertNotNil(prayerTimes) } @@ -43,6 +38,7 @@ class PrayerTests: XCTestCase { // Test calculated prayer times for Mecca, Saudi Arabia let mecca = Location(id: 119_496, name: "Mecca", latitude: 21.42664, longitude: 39.82563, countryCode: "SA", countryName: "Saudi Arabia", hasFixedPrayerTime: false, prayerDependentId: nil) + let date = Date(timeIntervalSince1970: 1_538_956_800) // 2018/10/08 let prayer = try await MuslimRepository().getPrayerTimes(location: mecca, date: date, attributes: attributes) XCTAssertNotNil(prayer) diff --git a/Tests/MuslimDataTests/Utils/TestUtils.swift b/Tests/MuslimDataTests/Utils/TestUtils.swift new file mode 100644 index 0000000..304cc25 --- /dev/null +++ b/Tests/MuslimDataTests/Utils/TestUtils.swift @@ -0,0 +1,49 @@ +// +// File.swift +// +// +// Created by Kosrat Ahmed on 24/03/2024. +// + +@testable import MuslimData +import Foundation + +struct TestUtils { + /// Generate date (2024/3/11 10:10:00). Also, gets value for each portion of the date. + static func getDate( + year: Int = 2024, + month: Int = 3, + day: Int = 11, + hour: Int = 10, + minute: Int = 10, + second: Int = 0 + ) -> Date { + var components = DateComponents() + components.year = year + components.month = month + components.day = day + components.hour = hour + components.minute = minute + components.second = second + + return Calendar.current.date(from: components)! + } + + /// Generate a prayer time with following times: + /// Fajr = 05:00 + /// Sunrise = 07:00 + /// Dhuhr = 12:00 + /// Asr = 15:00 + /// Maghrib = 18:00 + /// Isha = 19:00 + static func getPrayers() -> PrayerTime { + return PrayerTime( + fajr: getDate(hour: 5, minute: 0, second: 0), + sunrise: getDate(hour: 7, minute: 0, second: 0), + dhuhr: getDate(hour: 12, minute: 0, second: 0), + asr: getDate(hour: 15, minute: 0, second: 0), + maghrib: getDate(hour: 18, minute: 0, second: 0), + isha: getDate(hour: 19, minute: 0, second: 0) + ) + } +} diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj deleted file mode 100755 index e69de29..0000000 diff --git a/screenshots/1-prayer-times.png b/screenshots/1-prayer-times.png old mode 100755 new mode 100644 index d455bd0..58d0c31 Binary files a/screenshots/1-prayer-times.png and b/screenshots/1-prayer-times.png differ diff --git a/screenshots/2-locations-search.png b/screenshots/2-locations-search.png new file mode 100644 index 0000000..e536e02 Binary files /dev/null and b/screenshots/2-locations-search.png differ diff --git a/screenshots/2-locations.png b/screenshots/2-locations.png old mode 100755 new mode 100644 index 4920296..4cfa00b Binary files a/screenshots/2-locations.png and b/screenshots/2-locations.png differ diff --git a/screenshots/3-names.png b/screenshots/3-names.png old mode 100755 new mode 100644 index a5973b6..3ead598 Binary files a/screenshots/3-names.png and b/screenshots/3-names.png differ diff --git a/screenshots/4-azkars.png b/screenshots/4-azkars.png old mode 100755 new mode 100644 index c483aba..b68a0b0 Binary files a/screenshots/4-azkars.png and b/screenshots/4-azkars.png differ diff --git a/screenshots/5-azkar-detail.png b/screenshots/5-azkar-detail.png old mode 100755 new mode 100644 index d004ced..035d9b3 Binary files a/screenshots/5-azkar-detail.png and b/screenshots/5-azkar-detail.png differ diff --git a/scripts/push.sh b/scripts/push.sh deleted file mode 100755 index 3f8b834..0000000 --- a/scripts/push.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source ~/.rvm/scripts/rvm -rvm use default -pod trunk push --allow-warnings