diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..9ede81c --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,7 @@ +on: push +jobs: + test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - run: swift test diff --git a/.gitignore b/.gitignore index 47042c2..b69aff0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store xcuserdata +*.xcodeproj/ +.build/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d1e7a88..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: objective-c -osx_image: xcode7.1 -script: - - set -o pipefail - - xcodebuild -project QueryKit.xcodeproj -scheme QueryKit test -sdk macosx | xcpretty -c - - xcodebuild -project QueryKit.xcodeproj -scheme QueryKit test -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c - - pod lib lint --allow-warnings --quick diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8001372 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# QueryKit Changelog + +## 0.14.1 + +### Bug Fixes + +* Prevents fatal error when handling operators with optional `Date` types. + +## 0.14.0 + +### Breaking + +* Drops support for Swift 3 and Swift 4. Swift 5 or newer must be used. + +### Enhancements + +* Added support for ordering by Swift KeyPath, for example: + + ```swift + queryset.orderBy(\.createdAt, ascending: true) + ``` + +* Added support for filtering and excluding by Swift KeyPath, for example: + + ```swift + queryset.exclude(\.name == "Kyle") + queryset.filter(\.createdAt > Date()) + ``` diff --git a/Configurations/LICENSE b/Configurations/LICENSE deleted file mode 100644 index ad21101..0000000 --- a/Configurations/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Marius Rackwitz - -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/Configurations/UniversalFramework_Base.xcconfig b/Configurations/UniversalFramework_Base.xcconfig deleted file mode 100644 index 261e2b2..0000000 --- a/Configurations/UniversalFramework_Base.xcconfig +++ /dev/null @@ -1,25 +0,0 @@ -// -// Put this file alongside to the other both, as it contains what -// both have in common. Don't rename this file. -// -// Copyright (c) 2014-2015 Marius Rackwitz. All rights reserved. -// - -// Make it universal -SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos watchos watchsimulator appletvos appletvsimulator -VALID_ARCHS[sdk=macosx*] = i386 x86_64 -VALID_ARCHS[sdk=iphoneos*] = arm64 armv7 armv7s -VALID_ARCHS[sdk=iphonesimulator*] = i386 x86_64 -VALID_ARCHS[sdk=watchos*] = armv7k -VALID_ARCHS[sdk=watchsimulator*] = i386 -VALID_ARCHS[sdk=appletv*] = arm64 -VALID_ARCHS[sdk=appletvsimulator*] = x86_64 - -// Dynamic linking uses different default copy paths -LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=watchos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=watchsimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=appletvos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=appletvsimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' diff --git a/Configurations/UniversalFramework_Framework.xcconfig b/Configurations/UniversalFramework_Framework.xcconfig deleted file mode 100644 index 1406c7a..0000000 --- a/Configurations/UniversalFramework_Framework.xcconfig +++ /dev/null @@ -1,32 +0,0 @@ -// -// Inherit from this config in your framework target. -// -// Copyright (c) 2014-2015 Marius Rackwitz. All rights reserved. -// - -#include "UniversalFramework_Base.xcconfig" - -// OSX-specific default settings -FRAMEWORK_VERSION[sdk=macosx*] = A -COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES - -// iOS-specific default settings -CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer -TARGETED_DEVICE_FAMILY[sdk=iphonesimulator*] = 1,2 -TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 - -// TV-specific default settings -TARGETED_DEVICE_FAMILY[sdk=appletvsimulator*] = 3 -TARGETED_DEVICE_FAMILY[sdk=appletv*] = 3 - -// Watch-specific default settings -TARGETED_DEVICE_FAMILY[sdk=watchsimulator*] = 4 -TARGETED_DEVICE_FAMILY[sdk=watch*] = 4 - -ENABLE_BITCODE[sdk=macosx*] = NO -ENABLE_BITCODE[sdk=watchsimulator*] = YES -ENABLE_BITCODE[sdk=watch*] = YES -ENABLE_BITCODE[sdk=iphonesimulator*] = YES -ENABLE_BITCODE[sdk=iphone*] = YES -ENABLE_BITCODE[sdk=appletvsimulator*] = YES -ENABLE_BITCODE[sdk=appletv*] = YES diff --git a/Configurations/UniversalFramework_Test.xcconfig b/Configurations/UniversalFramework_Test.xcconfig deleted file mode 100644 index 0c5f845..0000000 --- a/Configurations/UniversalFramework_Test.xcconfig +++ /dev/null @@ -1,18 +0,0 @@ -// -// Inherit from this config in the test target for your framework. -// -// Copyright (c) 2014-2015 Marius Rackwitz. All rights reserved. -// - -#include "UniversalFramework_Base.xcconfig" - -FRAMEWORK_SEARCH_PATHS = $(inherited) '$(PLATFORM_DIR)/Developer/Library/Frameworks' - -// Yep. -LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) '@executable_path/../Frameworks' '@loader_path/../Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=watchos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=watchsimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=appletvos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -LD_RUNPATH_SEARCH_PATHS[sdk=appletvsimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..ebd1d10 --- /dev/null +++ b/Package.swift @@ -0,0 +1,14 @@ +// swift-tools-version:5.0 +import PackageDescription + + +let package = Package( + name: "QueryKit", + products: [ + .library(name: "QueryKit", targets: ["QueryKit"]), + ], + targets: [ + .target(name: "QueryKit", dependencies: []), + .testTarget(name: "QueryKitTests", dependencies: ["QueryKit"]), + ] +) diff --git a/QueryKit.podspec b/QueryKit.podspec index f80331f..03c48fd 100644 --- a/QueryKit.podspec +++ b/QueryKit.podspec @@ -1,18 +1,19 @@ Pod::Spec.new do |spec| spec.name = 'QueryKit' - spec.version = '0.13.0' + spec.version = '0.14.1' spec.summary = 'A simple type-safe Core Data query language.' - spec.homepage = 'http://querykit.org/' + spec.homepage = 'https://github.com/QueryKit/QueryKit/' spec.license = { :type => 'BSD', :file => 'LICENSE' } spec.author = { 'Kyle Fuller' => 'kyle@fuller.li' } spec.social_media_url = 'https://twitter.com/QueryKit' spec.source = { :git => 'https://github.com/QueryKit/QueryKit.git', :tag => "#{spec.version}" } spec.requires_arc = true + spec.swift_versions = ['5.0', '5.1'] spec.ios.deployment_target = '8.0' spec.osx.deployment_target = '10.9' spec.watchos.deployment_target = '2.0' spec.tvos.deployment_target = '9.0' spec.frameworks = 'CoreData' - spec.source_files = 'QueryKit/*.swift' + spec.source_files = 'Sources/QueryKit/*.swift' end diff --git a/QueryKit.xcodeproj/project.pbxproj b/QueryKit.xcodeproj/project.pbxproj deleted file mode 100644 index bd202ab..0000000 --- a/QueryKit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,456 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 279ED5841BED09EB0011CA09 /* QueryKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 279ED5831BED09EB0011CA09 /* QueryKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 279ED58B1BED09EB0011CA09 /* QueryKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 279ED5801BED09EB0011CA09 /* QueryKit.framework */; }; - 279ED5901BED09EB0011CA09 /* QueryKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279ED58F1BED09EB0011CA09 /* QueryKitTests.swift */; }; - 279ED59F1BED0A5C0011CA09 /* SortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279ED59A1BED0A5C0011CA09 /* SortDescriptor.swift */; }; - 279ED5A01BED0A5C0011CA09 /* QuerySet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279ED59B1BED0A5C0011CA09 /* QuerySet.swift */; }; - 279ED5A11BED0A5C0011CA09 /* Predicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279ED59C1BED0A5C0011CA09 /* Predicate.swift */; }; - 279ED5A21BED0A5C0011CA09 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279ED59D1BED0A5C0011CA09 /* Expression.swift */; }; - 279ED5A31BED0A5C0011CA09 /* Attribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279ED59E1BED0A5C0011CA09 /* Attribute.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 279ED58C1BED09EB0011CA09 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 279ED5771BED09EB0011CA09 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 279ED57F1BED09EB0011CA09; - remoteInfo = QueryKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 279ED5801BED09EB0011CA09 /* QueryKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QueryKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 279ED5831BED09EB0011CA09 /* QueryKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QueryKit.h; sourceTree = ""; }; - 279ED5851BED09EB0011CA09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 279ED58A1BED09EB0011CA09 /* QueryKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QueryKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 279ED58F1BED09EB0011CA09 /* QueryKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryKitTests.swift; sourceTree = ""; }; - 279ED5911BED09EB0011CA09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 279ED59A1BED0A5C0011CA09 /* SortDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDescriptor.swift; sourceTree = ""; }; - 279ED59B1BED0A5C0011CA09 /* QuerySet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuerySet.swift; sourceTree = ""; }; - 279ED59C1BED0A5C0011CA09 /* Predicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Predicate.swift; sourceTree = ""; }; - 279ED59D1BED0A5C0011CA09 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; - 279ED59E1BED0A5C0011CA09 /* Attribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attribute.swift; sourceTree = ""; }; - 279ED5A61BED0AA50011CA09 /* UniversalFramework_Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Base.xcconfig; sourceTree = ""; }; - 279ED5A71BED0AA50011CA09 /* UniversalFramework_Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Framework.xcconfig; sourceTree = ""; }; - 279ED5A81BED0AA50011CA09 /* UniversalFramework_Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Test.xcconfig; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 279ED57C1BED09EB0011CA09 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 279ED5871BED09EB0011CA09 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 279ED58B1BED09EB0011CA09 /* QueryKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 279ED5761BED09EB0011CA09 = { - isa = PBXGroup; - children = ( - 279ED5A41BED0AA50011CA09 /* Configurations */, - 279ED5821BED09EB0011CA09 /* QueryKit */, - 279ED58E1BED09EB0011CA09 /* QueryKitTests */, - 279ED5811BED09EB0011CA09 /* Products */, - ); - sourceTree = ""; - }; - 279ED5811BED09EB0011CA09 /* Products */ = { - isa = PBXGroup; - children = ( - 279ED5801BED09EB0011CA09 /* QueryKit.framework */, - 279ED58A1BED09EB0011CA09 /* QueryKitTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 279ED5821BED09EB0011CA09 /* QueryKit */ = { - isa = PBXGroup; - children = ( - 279ED5831BED09EB0011CA09 /* QueryKit.h */, - 279ED59B1BED0A5C0011CA09 /* QuerySet.swift */, - 279ED59E1BED0A5C0011CA09 /* Attribute.swift */, - 279ED59D1BED0A5C0011CA09 /* Expression.swift */, - 279ED59A1BED0A5C0011CA09 /* SortDescriptor.swift */, - 279ED59C1BED0A5C0011CA09 /* Predicate.swift */, - 279ED5851BED09EB0011CA09 /* Info.plist */, - ); - path = QueryKit; - sourceTree = ""; - }; - 279ED58E1BED09EB0011CA09 /* QueryKitTests */ = { - isa = PBXGroup; - children = ( - 279ED58F1BED09EB0011CA09 /* QueryKitTests.swift */, - 279ED5911BED09EB0011CA09 /* Info.plist */, - ); - path = QueryKitTests; - sourceTree = ""; - }; - 279ED5A41BED0AA50011CA09 /* Configurations */ = { - isa = PBXGroup; - children = ( - 279ED5A61BED0AA50011CA09 /* UniversalFramework_Base.xcconfig */, - 279ED5A71BED0AA50011CA09 /* UniversalFramework_Framework.xcconfig */, - 279ED5A81BED0AA50011CA09 /* UniversalFramework_Test.xcconfig */, - ); - path = Configurations; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 279ED57D1BED09EB0011CA09 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 279ED5841BED09EB0011CA09 /* QueryKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 279ED57F1BED09EB0011CA09 /* QueryKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 279ED5941BED09EB0011CA09 /* Build configuration list for PBXNativeTarget "QueryKit" */; - buildPhases = ( - 279ED57B1BED09EB0011CA09 /* Sources */, - 279ED57C1BED09EB0011CA09 /* Frameworks */, - 279ED57D1BED09EB0011CA09 /* Headers */, - 279ED57E1BED09EB0011CA09 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = QueryKit; - productName = QueryKit; - productReference = 279ED5801BED09EB0011CA09 /* QueryKit.framework */; - productType = "com.apple.product-type.framework"; - }; - 279ED5891BED09EB0011CA09 /* QueryKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 279ED5971BED09EB0011CA09 /* Build configuration list for PBXNativeTarget "QueryKitTests" */; - buildPhases = ( - 279ED5861BED09EB0011CA09 /* Sources */, - 279ED5871BED09EB0011CA09 /* Frameworks */, - 279ED5881BED09EB0011CA09 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 279ED58D1BED09EB0011CA09 /* PBXTargetDependency */, - ); - name = QueryKitTests; - productName = QueryKitTests; - productReference = 279ED58A1BED09EB0011CA09 /* QueryKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 279ED5771BED09EB0011CA09 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0710; - LastUpgradeCheck = 0800; - ORGANIZATIONNAME = QueryKit; - TargetAttributes = { - 279ED57F1BED09EB0011CA09 = { - CreatedOnToolsVersion = 7.1; - LastSwiftMigration = 0800; - }; - 279ED5891BED09EB0011CA09 = { - CreatedOnToolsVersion = 7.1; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = 279ED57A1BED09EB0011CA09 /* Build configuration list for PBXProject "QueryKit" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 279ED5761BED09EB0011CA09; - productRefGroup = 279ED5811BED09EB0011CA09 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 279ED57F1BED09EB0011CA09 /* QueryKit */, - 279ED5891BED09EB0011CA09 /* QueryKitTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 279ED57E1BED09EB0011CA09 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 279ED5881BED09EB0011CA09 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 279ED57B1BED09EB0011CA09 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 279ED5A31BED0A5C0011CA09 /* Attribute.swift in Sources */, - 279ED59F1BED0A5C0011CA09 /* SortDescriptor.swift in Sources */, - 279ED5A11BED0A5C0011CA09 /* Predicate.swift in Sources */, - 279ED5A01BED0A5C0011CA09 /* QuerySet.swift in Sources */, - 279ED5A21BED0A5C0011CA09 /* Expression.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 279ED5861BED09EB0011CA09 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 279ED5901BED09EB0011CA09 /* QueryKitTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 279ED58D1BED09EB0011CA09 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 279ED57F1BED09EB0011CA09 /* QueryKit */; - targetProxy = 279ED58C1BED09EB0011CA09 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 279ED5921BED09EB0011CA09 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - 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_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 = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TVOS_DEPLOYMENT_TARGET = 9.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 2.0; - }; - name = Debug; - }; - 279ED5931BED09EB0011CA09 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - 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 = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TVOS_DEPLOYMENT_TARGET = 9.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 2.0; - }; - name = Release; - }; - 279ED5951BED09EB0011CA09 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 279ED5A71BED0AA50011CA09 /* UniversalFramework_Framework.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = QueryKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = org.querykit.QueryKit; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - 279ED5961BED09EB0011CA09 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 279ED5A71BED0AA50011CA09 /* UniversalFramework_Framework.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = QueryKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = org.querykit.QueryKit; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; - 279ED5981BED09EB0011CA09 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 279ED5A81BED0AA50011CA09 /* UniversalFramework_Test.xcconfig */; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = QueryKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = org.querykit.QueryKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - 279ED5991BED09EB0011CA09 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 279ED5A81BED0AA50011CA09 /* UniversalFramework_Test.xcconfig */; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = QueryKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = org.querykit.QueryKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 279ED57A1BED09EB0011CA09 /* Build configuration list for PBXProject "QueryKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 279ED5921BED09EB0011CA09 /* Debug */, - 279ED5931BED09EB0011CA09 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 279ED5941BED09EB0011CA09 /* Build configuration list for PBXNativeTarget "QueryKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 279ED5951BED09EB0011CA09 /* Debug */, - 279ED5961BED09EB0011CA09 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 279ED5971BED09EB0011CA09 /* Build configuration list for PBXNativeTarget "QueryKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 279ED5981BED09EB0011CA09 /* Debug */, - 279ED5991BED09EB0011CA09 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 279ED5771BED09EB0011CA09 /* Project object */; -} diff --git a/QueryKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/QueryKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1639ef8..0000000 --- a/QueryKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme b/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme deleted file mode 100644 index 434d561..0000000 --- a/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/QueryKit/Info.plist b/QueryKit/Info.plist deleted file mode 100644 index f2e8865..0000000 --- a/QueryKit/Info.plist +++ /dev/null @@ -1,28 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2015 QueryKit. All rights reserved. - NSPrincipalClass - - - diff --git a/QueryKitTests/Info.plist b/QueryKitTests/Info.plist deleted file mode 100644 index ba72822..0000000 --- a/QueryKitTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/README.md b/README.md index c7ca46c..2cc9993 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,14 @@ # QueryKit -[![Build Status](http://img.shields.io/travis/QueryKit/QueryKit/master.svg?style=flat)](https://travis-ci.org/QueryKit/QueryKit) - QueryKit, a simple type-safe Core Data query language. ## Usage -To get the most out of QueryKit, and to get full type-safe queries, you may -add extensions for your Core Data models providing properties which describe -your models. You may use [querykit-cli](https://github.com/QueryKit/querykit-cli) -to generate these automatically. - -An extension for our a `Person` model might look as follows: - ```swift -extension User { - static var name:Attribute { return Attribute("name") } - static var age:Attribute { return Attribute("age") } -} -``` - -This provides static properties on our User model which represent each property -on our Core Data model, these may be used to construct predicates and sort -descriptors with compile time safety, without stringly typing them -into your application. - -```swift -let namePredicate = Person.name == "Kyle" -let agePredicate = Person.age > 25 -let ageSortDescriptor = Person.age.descending() +QuerySet(context, "Person") + .orderedBy(.name, ascending: true) + .filter(\.age >= 18) ``` ### QuerySet @@ -42,20 +21,19 @@ results based on the given parameters. #### Retrieving all objects ```swift -let queryset = Person.queryset(context) +let queryset = QuerySet(context, "Person") ``` #### Retrieving specific objects with filters You may filter a QuerySet using the `filter` and `exclude` methods, which -accept a closure passing the model type allowing you to access the -type-safe attributes. +accept a predicate which can be constructed using KeyPath extensions. -The `filter` and `exclude` methods return brand new QuerySet's including your filter. +The `filter` and `exclude` methods return new QuerySet's including your filter. ```swift -queryset.filter { $0.name == "Kyle" } -queryset.exclude { $0.age > 25 } +queryset.filter(\.name == "Kyle") +queryset.exclude(\.age > 25) ``` You may also use standard `NSPredicate` if you want to construct complicated @@ -72,14 +50,13 @@ The result of refining a QuerySet is itself a QuerySet, so it’s possible to chain refinements together. For example: ```swift -queryset.filter { $0.name == "Kyle" } - .exclude { $0.age < 25 } - .filter { $0.isEmployed } +queryset.filter(\.name == "Kyle") + .exclude(\.age < 25) ``` -Each time you refine a QuerySet, you get a brand-new QuerySet that is in -no way bound to the previous QuerySet. Each refinement creates a separate -and distinct QuerySet that may be stored, used and reused. +Each time you refine a QuerySet, you get a new QuerySet instance that is in no +way bound to the previous QuerySet. Each refinement creates a separate and +distinct QuerySet that may be stored, used and reused. #### QuerySets are lazy @@ -90,17 +67,15 @@ QuerySet is *evaluated*. #### Ordering You may order a QuerySet's results by using the `orderBy` function which -accepts a closure passing the model type, and expects a sort descriptor in -return. +accepts a KeyPath. ```swift -queryset.orderBy { $0.name.ascending() } +queryset.orderBy(\.name, ascending: true) ``` You may also pass in an `NSSortDescriptor` if you would rather. ```swift -queryset.orderBy(Person.name.ascending()) queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) ``` @@ -110,7 +85,7 @@ Using slicing, a QuerySet's results may be limited to a specified range. For example, to get the first 5 items in our QuerySet: ```swift -queryset[0..5] +queryset[0...5] ``` **NOTE**: *Remember, QuerySets are lazily evaluated. Slicing doesn’t evaluate the query.* @@ -160,35 +135,26 @@ count or an error if the operation failed. let deleted = try? queryset.delete() ``` -#### Attribute - -The `Attribute` is a generic structure for creating predicates in a -type-safe manner as shown at the start of the README. - -```swift -let name = Attribute("name") -let age = Attribute("age") -``` - ##### Operators -QueryKit provides custom operator functions allowing you to create predicates. +QueryKit provides KeyPath extensions providing operator functions allowing you +to create predicates. ```swift // Name is equal to Kyle -name == "Kyle" +\Person.name == "Kyle" // Name is either equal to Kyle or Katie -name << ["Kyle", "Katie"] +\.Person.name << ["Kyle", "Katie"] // Age is equal to 27 -age == 27 +\.Person.age == 27 // Age is more than or equal to 25 -age >= 25 +\Person.age >= 25 // Age is within the range 22 to 30. -age << (22...30) +\Person.age << (22...30) ``` The following types of comparisons are supported using Attribute: @@ -212,13 +178,13 @@ QueryKit provides the `!`, `&&` and `||` operators for joining multiple predicat ```swift // Persons name is Kyle or Katie -Person.name == "Kyle" || Person.name == "Katie" +\Person.name == "Kyle" || \Person.name == "Katie" // Persons age is more than 25 and their name is Kyle -Person.age >= 25 && Person.name == "Kyle" +\Person.age >= 25 && \Person.name == "Kyle" // Persons name is not Kyle -!(Person.name == "Kyle") +!(\Person.name == "Kyle") ``` ## Installation diff --git a/QueryKit/Attribute.swift b/Sources/QueryKit/Attribute.swift similarity index 91% rename from QueryKit/Attribute.swift rename to Sources/QueryKit/Attribute.swift index e39f0f0..a917dec 100644 --- a/QueryKit/Attribute.swift +++ b/Sources/QueryKit/Attribute.swift @@ -43,10 +43,7 @@ public struct Attribute : Equatable { } } - let value = unsafeBitCast(value, to: Optional.self) - if let value = value { - return NSExpression(forConstantValue: value) - } + return NSExpression(forConstantValue: value) } return NSExpression(forConstantValue: NSNull()) @@ -111,12 +108,12 @@ prefix public func ! (left: Attribute) -> NSPredicate { } public extension QuerySet { - public func filter(_ attribute:Attribute) -> QuerySet { - return filter(attribute == true) + func filter(_ attribute:Attribute) -> QuerySet { + return filter((attribute == true) as NSPredicate) } - public func exclude(_ attribute:Attribute) -> QuerySet { - return filter(attribute == false) + func exclude(_ attribute:Attribute) -> QuerySet { + return filter((attribute == false) as NSPredicate) } } diff --git a/QueryKit/Expression.swift b/Sources/QueryKit/Expression.swift similarity index 100% rename from QueryKit/Expression.swift rename to Sources/QueryKit/Expression.swift diff --git a/Sources/QueryKit/KeyPath.swift b/Sources/QueryKit/KeyPath.swift new file mode 100644 index 0000000..8991eaa --- /dev/null +++ b/Sources/QueryKit/KeyPath.swift @@ -0,0 +1,81 @@ +import CoreData + +func expression(for keyPath: KeyPath) -> NSExpression { + return NSExpression(forKeyPath: (keyPath as AnyKeyPath)._kvcKeyPathString!) +} + +// MARK: Predicate + +public func == (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs == rhs) +} + +public func != (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs != rhs) +} + +public func > (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs > rhs) +} + +public func >= (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs >= rhs) +} + +public func < (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs < rhs) +} + +public func <= (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs <= rhs) +} + +public func ~= (lhs: KeyPath, rhs: V) -> Predicate { + return Predicate(predicate: lhs ~= rhs) +} + +public func << (lhs: KeyPath, rhs: [V]) -> Predicate { + return Predicate(predicate: lhs << rhs) +} + +public func << (lhs: KeyPath, rhs: Range) -> Predicate { + return Predicate(predicate: lhs << rhs) +} + +// MARK: - NSPredicate + +public func == (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) == NSExpression(forConstantValue: rhs) +} + +public func != (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) != NSExpression(forConstantValue: rhs) +} + +public func > (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) > NSExpression(forConstantValue: rhs) +} + +public func >= (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) >= NSExpression(forConstantValue: rhs) +} + +public func < (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) < NSExpression(forConstantValue: rhs) +} + +public func <= (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) <= NSExpression(forConstantValue: rhs) +} + +public func ~= (lhs: KeyPath, rhs: V) -> NSPredicate { + return expression(for: lhs) ~= NSExpression(forConstantValue: rhs) +} + +public func << (lhs: KeyPath, rhs: [V]) -> NSPredicate { + return expression(for: lhs) << NSExpression(forConstantValue: rhs) +} + +public func << (lhs: KeyPath, rhs: Range) -> NSPredicate { + return expression(for: lhs) << NSExpression(forConstantValue: rhs) +} diff --git a/QueryKit/Predicate.swift b/Sources/QueryKit/Predicate.swift similarity index 100% rename from QueryKit/Predicate.swift rename to Sources/QueryKit/Predicate.swift diff --git a/QueryKit/QueryKit.h b/Sources/QueryKit/QueryKit.h similarity index 100% rename from QueryKit/QueryKit.h rename to Sources/QueryKit/QueryKit.h diff --git a/QueryKit/QuerySet.swift b/Sources/QueryKit/QuerySet.swift similarity index 82% rename from QueryKit/QuerySet.swift rename to Sources/QueryKit/QuerySet.swift index 7ee7745..c63d7a8 100644 --- a/QueryKit/QuerySet.swift +++ b/Sources/QueryKit/QuerySet.swift @@ -4,19 +4,19 @@ import CoreData /// Represents a lazy database lookup for a set of objects. open class QuerySet : Equatable { /// Returns the managed object context that will be used to execute any requests. - open let context:NSManagedObjectContext + public let context: NSManagedObjectContext /// Returns the name of the entity the request is configured to fetch. - open let entityName:String + public let entityName: String /// Returns the sort descriptors of the receiver. - open let sortDescriptors:[NSSortDescriptor] + public let sortDescriptors: [NSSortDescriptor] /// Returns the predicate of the receiver. - open let predicate:NSPredicate? + public let predicate: NSPredicate? /// The range of the query, allows you to offset and limit a query - open let range: Range? + public let range: Range? // MARK: Initialization @@ -63,6 +63,11 @@ extension QuerySet { // MARK: Type-safe Sorting + /// Returns a new QuerySet containing objects ordered by the given key path. + public func orderBy(_ keyPath: KeyPath, ascending: Bool) -> QuerySet { + return orderBy(NSSortDescriptor(key: (keyPath as AnyKeyPath)._kvcKeyPathString!, ascending: ascending)) + } + /// Returns a new QuerySet containing objects ordered by the given sort descriptor. public func orderBy(_ closure:((ModelType.Type) -> (SortDescriptor))) -> QuerySet { return orderBy(closure(ModelType.self).sortDescriptor) @@ -75,6 +80,11 @@ extension QuerySet { // MARK: Filtering + /// Returns a new QuerySet containing objects that match the given predicate. + public func filter(_ predicate: Predicate) -> QuerySet { + return filter(predicate.predicate) + } + /// Returns a new QuerySet containing objects that match the given predicate. public func filter(_ predicate:NSPredicate) -> QuerySet { var futurePredicate = predicate @@ -92,6 +102,11 @@ extension QuerySet { return filter(newPredicate) } + /// Returns a new QuerySet containing objects that exclude the given predicate. + public func exclude(_ predicate: Predicate) -> QuerySet { + return exclude(predicate.predicate) + } + /// Returns a new QuerySet containing objects that exclude the given predicate. public func exclude(_ predicate:NSPredicate) -> QuerySet { let excludePredicate = NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.not, subpredicates: [predicate]) @@ -107,21 +122,25 @@ extension QuerySet { // MARK: Type-safe filtering /// Returns a new QuerySet containing objects that match the given predicate. + @available(*, deprecated, renamed: "filter(_:)", message: "Replaced by KeyPath filtering https://git.io/Jv2v3") public func filter(_ closure:((ModelType.Type) -> (Predicate))) -> QuerySet { return filter(closure(ModelType.self).predicate) } /// Returns a new QuerySet containing objects that exclude the given predicate. + @available(*, deprecated, renamed: "exclude(_:)", message: "Replaced by KeyPath filtering https://git.io/Jv2v3") public func exclude(_ closure:((ModelType.Type) -> (Predicate))) -> QuerySet { return exclude(closure(ModelType.self).predicate) } /// Returns a new QuerySet containing objects that match the given predicatess. + @available(*, deprecated, renamed: "filter(_:)", message: "Replaced by KeyPath filtering https://git.io/Jv2v3") public func filter(_ closures:[((ModelType.Type) -> (Predicate))]) -> QuerySet { return filter(closures.map { $0(ModelType.self).predicate }) } /// Returns a new QuerySet containing objects that exclude the given predicates. + @available(*, deprecated, renamed: "exclude(_:)", message: "Replaced by KeyPath filtering https://git.io/Jv2v3") public func exclude(_ closures:[((ModelType.Type) -> (Predicate))]) -> QuerySet { return exclude(closures.map { $0(ModelType.self).predicate }) } @@ -140,7 +159,13 @@ extension QuerySet { return items.first } - public subscript(range:Range) -> QuerySet { + public subscript(range: ClosedRange) -> QuerySet { + get { + return self[Range(range)] + } + } + + public subscript(range: Range) -> QuerySet { get { var fullRange = range @@ -198,8 +223,11 @@ extension QuerySet { :note: Returns nil if the operation could not be completed. */ public func exists() throws -> Bool { - let result:Int = try count() - return result > 0 + let fetchRequest = self.fetchRequest + fetchRequest.fetchLimit = 1 + + let result = try context.count(for: fetchRequest) + return result != 0 } // MARK: Deletion diff --git a/QueryKit/SortDescriptor.swift b/Sources/QueryKit/SortDescriptor.swift similarity index 100% rename from QueryKit/SortDescriptor.swift rename to Sources/QueryKit/SortDescriptor.swift diff --git a/QueryKitTests/AttributeTests.swift b/Tests/QueryKitTests/AttributeTests.swift similarity index 100% rename from QueryKitTests/AttributeTests.swift rename to Tests/QueryKitTests/AttributeTests.swift diff --git a/QueryKitTests/ExpressionTests.swift b/Tests/QueryKitTests/ExpressionTests.swift similarity index 100% rename from QueryKitTests/ExpressionTests.swift rename to Tests/QueryKitTests/ExpressionTests.swift diff --git a/Tests/QueryKitTests/KeyPathTests.swift b/Tests/QueryKitTests/KeyPathTests.swift new file mode 100644 index 0000000..09d44b8 --- /dev/null +++ b/Tests/QueryKitTests/KeyPathTests.swift @@ -0,0 +1,127 @@ +import XCTest +@testable import QueryKit + +class KeyPathTests: XCTestCase { + func testEqualityOperator() { + let predicate: Predicate = \User.name == "kyle" + XCTAssertEqual(predicate.predicate, NSPredicate(format:"name == 'kyle'")) + } + + func testEqualityOperatorWithOptional() { + let predicate: Predicate = \User.name == nil + XCTAssertEqual(predicate.predicate, NSPredicate(format:"name == %@", NSNull())) + } + + func testOptionalEqualityOperatorWithOptional() { + let predicate: Predicate = \User.createdAt == nil + XCTAssertEqual(predicate.predicate, NSPredicate(format:"createdAt == %@", NSNull())) + } + + func testInequalityOperator() { + let predicate: Predicate = \User.name != "kyle" + XCTAssertEqual(predicate.predicate, NSPredicate(format:"name != 'kyle'")) + } + + func testInqqualityOperatorWithOptional() { + let predicate: Predicate = \User.name != nil + XCTAssertEqual(predicate.predicate, NSPredicate(format:"name != %@", NSNull())) + } + + func testGreaterThanOperator() { + let predicate: Predicate = \User.age > 17 + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age > 17")) + } + + func testGreaterThanOrEqualOperator() { + let predicate: Predicate = \User.age >= 18 + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age >= 18")) + } + + func testLessThanOperator() { + let predicate: Predicate = \User.age < 18 + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age < 18")) + } + + func testLessThanOrEqualOperator() { + let predicate: Predicate = \User.age <= 17 + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age <= 17")) + } + + func testLikeOperator() { + let predicate: Predicate = \User.name ~= "k*" + XCTAssertEqual(predicate.predicate, NSPredicate(format:"name LIKE 'k*'")) + } + + func testInOperator() { + let predicate: Predicate = \User.age << [5, 10] + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age IN %@", [5, 10])) + } + + func testBetweenRangeOperator() { + let predicate: Predicate = \User.age << (32 ..< 64) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age BETWEEN %@", [32, 64])) + } +} + +class KeyPathNSPredicateTests: XCTestCase { + func testEqualityOperator() { + let predicate: NSPredicate = \User.name == "kyle" + XCTAssertEqual(predicate, NSPredicate(format:"name == 'kyle'")) + } + + func testEqualityOperatorWithOptional() { + let predicate: NSPredicate = \User.name == nil + XCTAssertEqual(predicate, NSPredicate(format:"name == %@", NSNull())) + } + + func testInequalityOperator() { + let predicate: NSPredicate = \User.name != "kyle" + XCTAssertEqual(predicate, NSPredicate(format:"name != 'kyle'")) + } + + func testInqqualityOperatorWithOptional() { + let predicate: NSPredicate = \User.name != nil + XCTAssertEqual(predicate, NSPredicate(format:"name != %@", NSNull())) + } + + func testGreaterThanOperator() { + let predicate: NSPredicate = \User.age > 17 + XCTAssertEqual(predicate, NSPredicate(format:"age > 17")) + } + + func testGreaterThanOrEqualOperator() { + let predicate: NSPredicate = \User.age >= 18 + XCTAssertEqual(predicate, NSPredicate(format:"age >= 18")) + } + + func testLessThanOperator() { + let predicate: NSPredicate = \User.age < 18 + XCTAssertEqual(predicate, NSPredicate(format:"age < 18")) + } + + func testLessThanOrEqualOperator() { + let predicate: NSPredicate = \User.age <= 17 + XCTAssertEqual(predicate, NSPredicate(format:"age <= 17")) + } + + func testLikeOperator() { + let predicate: NSPredicate = \User.name ~= "k*" + XCTAssertEqual(predicate, NSPredicate(format:"name LIKE 'k*'")) + } + + func testInOperator() { + let predicate: NSPredicate = \User.age << [5, 10] + XCTAssertEqual(predicate, NSPredicate(format:"age IN %@", [5, 10])) + } + + func testBetweenRangeOperator() { + let predicate: NSPredicate = \User.age << (32 ..< 64) + XCTAssertEqual(predicate, NSPredicate(format:"age BETWEEN %@", [32, 64])) + } +} + +class User: NSManagedObject { + @objc var name: String? + @NSManaged var age: Int + @objc var createdAt: Date? +} diff --git a/QueryKitTests/PredicateTests.swift b/Tests/QueryKitTests/PredicateTests.swift similarity index 100% rename from QueryKitTests/PredicateTests.swift rename to Tests/QueryKitTests/PredicateTests.swift diff --git a/QueryKitTests/QueryKitTests.swift b/Tests/QueryKitTests/QueryKitTests.swift similarity index 100% rename from QueryKitTests/QueryKitTests.swift rename to Tests/QueryKitTests/QueryKitTests.swift diff --git a/QueryKitTests/QuerySetTests.swift b/Tests/QueryKitTests/QuerySetTests.swift similarity index 90% rename from QueryKitTests/QuerySetTests.swift rename to Tests/QueryKitTests/QuerySetTests.swift index d1dbc3b..5ecab25 100644 --- a/QueryKitTests/QuerySetTests.swift +++ b/Tests/QueryKitTests/QuerySetTests.swift @@ -44,6 +44,22 @@ class QuerySetTests: XCTestCase { // MARK: Sorting + func testOrderByKeyPathAscending() { + let qs = queryset.orderBy(\.name, ascending: true) + + XCTAssertEqual(qs.sortDescriptors, [ + NSSortDescriptor(key: "name", ascending: true), + ]) + } + + func testOrderByKeyPathDecending() { + let qs = queryset.orderBy(\.name, ascending: false) + + XCTAssertEqual(qs.sortDescriptors, [ + NSSortDescriptor(key: "name", ascending: false), + ]) + } + func testOrderBySortDescriptor() { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) let qs = queryset.orderBy(sortDescriptor) @@ -63,7 +79,7 @@ class QuerySetTests: XCTestCase { func testTypeSafeOrderBySortDescriptors() { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - let qs = queryset.orderBy { [$0.name.ascending()] } + let qs = queryset.orderBy { [$0.name.ascending() as SortDescriptor] } XCTAssertTrue(qs.sortDescriptors == [sortDescriptor]) } @@ -80,6 +96,11 @@ class QuerySetTests: XCTestCase { // MARK: Filtering + func testFilterKeyPath() { + let qs = queryset.filter(\.name == "Kyle") + XCTAssertEqual(qs.predicate?.description, "name == \"Kyle\"") + } + func testFilterPredicate() { let predicate = NSPredicate(format: "name == Kyle") let qs = queryset.filter(predicate) @@ -120,6 +141,11 @@ class QuerySetTests: XCTestCase { // MARK: Exclusion + func testExcludeKeyPath() { + let qs = queryset.exclude(\.name == "Kyle") + XCTAssertEqual(qs.predicate?.description, "NOT name == \"Kyle\"") + } + func testExcludePredicate() { let predicate = NSPredicate(format: "name == Kyle") let qs = queryset.exclude(predicate) diff --git a/QueryKitTests/SortDescriptorTests.swift b/Tests/QueryKitTests/SortDescriptorTests.swift similarity index 100% rename from QueryKitTests/SortDescriptorTests.swift rename to Tests/QueryKitTests/SortDescriptorTests.swift