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 168fe4b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: objective-c -before_install: - - gem install cocoapods --no-rdoc --no-ri --no-document --quiet -script: pod lib lint QueryKit.podspec - 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/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.png b/QueryKit.png new file mode 100644 index 0000000..a2ab70d Binary files /dev/null and b/QueryKit.png differ diff --git a/QueryKit.podspec b/QueryKit.podspec index f4f93f2..03c48fd 100644 --- a/QueryKit.podspec +++ b/QueryKit.podspec @@ -1,13 +1,19 @@ Pod::Spec.new do |spec| spec.name = 'QueryKit' - spec.version = '0.8.1' - spec.summary = 'A simple CoreData query language for Swift.' - spec.homepage = 'https://github.com/kylef/QueryKit' + spec.version = '0.14.1' + spec.summary = 'A simple type-safe Core Data query language.' + spec.homepage = 'https://github.com/QueryKit/QueryKit/' spec.license = { :type => 'BSD', :file => 'LICENSE' } - spec.author = { 'Kyle Fuller' => 'inbox@kylefuller.co.uk' } - spec.social_media_url = 'http://twitter.com/kylefuller' - spec.source = { :git => 'https://github.com/kylef/QueryKit.git', :tag => "#{spec.version}" } - spec.source_files = 'QueryKit/*.{h,swift}' + 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 = 'Sources/QueryKit/*.swift' end diff --git a/QueryKit.xcodeproj/project.pbxproj b/QueryKit.xcodeproj/project.pbxproj deleted file mode 100644 index b9ac982..0000000 --- a/QueryKit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,548 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 77A9B67F195374490016654E /* QueryKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 77A9B67E195374490016654E /* QueryKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 77A9B685195374490016654E /* QueryKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77A9B679195374490016654E /* QueryKit.framework */; }; - 77A9B68C195374490016654E /* QueryKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A9B68B195374490016654E /* QueryKitTests.swift */; }; - 77A9B698195374AA0016654E /* QueryKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A9B697195374AA0016654E /* QueryKit.swift */; }; - 77E3A05D1969C019009372A8 /* QuerySet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E3A05C1969C019009372A8 /* QuerySet.swift */; }; - 77E3A05F1969C047009372A8 /* QuerySetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E3A05E1969C047009372A8 /* QuerySetTests.swift */; }; - 77E3A0611969DDF5009372A8 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E3A0601969DDF5009372A8 /* Expression.swift */; }; - 77E3A0631969E003009372A8 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E3A0621969E003009372A8 /* ExpressionTests.swift */; }; - 77E8728119539C0900A6F13F /* Attribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E8728019539C0900A6F13F /* Attribute.swift */; }; - 77E8728319539C2A00A6F13F /* AttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E8728219539C2A00A6F13F /* AttributeTests.swift */; }; - 77E8728519539FC000A6F13F /* PredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E8728419539FC000A6F13F /* PredicateTests.swift */; }; - 77E8728719539FD200A6F13F /* Predicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E8728619539FD200A6F13F /* Predicate.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 772A282A196C8D7800992BAB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 77A9B66E1953742F0016654E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 77A9B678195374490016654E; - remoteInfo = QueryKit; - }; - 77A9B686195374490016654E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 77A9B66E1953742F0016654E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 77A9B678195374490016654E; - remoteInfo = QueryKit; - }; - 77A9B6931953744A0016654E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 77A9B66E1953742F0016654E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 77A9B678195374490016654E; - remoteInfo = QueryKit; - }; - 77A9B6951953744A0016654E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 77A9B66E1953742F0016654E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 77A9B678195374490016654E; - remoteInfo = QueryKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 77A9B679195374490016654E /* QueryKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QueryKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 77A9B67D195374490016654E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 77A9B67E195374490016654E /* QueryKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QueryKit.h; sourceTree = ""; }; - 77A9B684195374490016654E /* QueryKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QueryKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 77A9B68A195374490016654E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 77A9B68B195374490016654E /* QueryKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryKitTests.swift; sourceTree = ""; }; - 77A9B697195374AA0016654E /* QueryKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryKit.swift; sourceTree = ""; }; - 77E3A05C1969C019009372A8 /* QuerySet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuerySet.swift; sourceTree = ""; }; - 77E3A05E1969C047009372A8 /* QuerySetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuerySetTests.swift; sourceTree = ""; }; - 77E3A0601969DDF5009372A8 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; - 77E3A0621969E003009372A8 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - 77E8728019539C0900A6F13F /* Attribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attribute.swift; sourceTree = ""; }; - 77E8728219539C2A00A6F13F /* AttributeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributeTests.swift; sourceTree = ""; }; - 77E8728419539FC000A6F13F /* PredicateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredicateTests.swift; sourceTree = ""; }; - 77E8728619539FD200A6F13F /* Predicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Predicate.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 77A9B675195374490016654E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 77A9B681195374490016654E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 77A9B685195374490016654E /* QueryKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 77A9B66D1953742F0016654E = { - isa = PBXGroup; - children = ( - 77A9B67B195374490016654E /* QueryKit */, - 77A9B688195374490016654E /* QueryKitTests */, - 77A9B67A195374490016654E /* Products */, - ); - sourceTree = ""; - }; - 77A9B67A195374490016654E /* Products */ = { - isa = PBXGroup; - children = ( - 77A9B679195374490016654E /* QueryKit.framework */, - 77A9B684195374490016654E /* QueryKitTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 77A9B67B195374490016654E /* QueryKit */ = { - isa = PBXGroup; - children = ( - 77A9B67E195374490016654E /* QueryKit.h */, - 77A9B697195374AA0016654E /* QueryKit.swift */, - 77E3A05C1969C019009372A8 /* QuerySet.swift */, - 77E8728019539C0900A6F13F /* Attribute.swift */, - 77E3A0601969DDF5009372A8 /* Expression.swift */, - 77E8728619539FD200A6F13F /* Predicate.swift */, - 77A9B67C195374490016654E /* Supporting Files */, - ); - path = QueryKit; - sourceTree = ""; - }; - 77A9B67C195374490016654E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 77A9B67D195374490016654E /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 77A9B688195374490016654E /* QueryKitTests */ = { - isa = PBXGroup; - children = ( - 77A9B68B195374490016654E /* QueryKitTests.swift */, - 77E3A05E1969C047009372A8 /* QuerySetTests.swift */, - 77E3A0621969E003009372A8 /* ExpressionTests.swift */, - 77E8728219539C2A00A6F13F /* AttributeTests.swift */, - 77A9B689195374490016654E /* Supporting Files */, - ); - path = QueryKitTests; - sourceTree = ""; - }; - 77A9B689195374490016654E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 77E8728419539FC000A6F13F /* PredicateTests.swift */, - 77A9B68A195374490016654E /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 77A9B676195374490016654E /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 77A9B67F195374490016654E /* QueryKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 77A9B678195374490016654E /* QueryKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 77A9B68D195374490016654E /* Build configuration list for PBXNativeTarget "QueryKit" */; - buildPhases = ( - 77A9B674195374490016654E /* Sources */, - 77A9B675195374490016654E /* Frameworks */, - 77A9B676195374490016654E /* Headers */, - 77A9B677195374490016654E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = QueryKit; - productName = QueryKit; - productReference = 77A9B679195374490016654E /* QueryKit.framework */; - productType = "com.apple.product-type.framework"; - }; - 77A9B683195374490016654E /* QueryKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 77A9B690195374490016654E /* Build configuration list for PBXNativeTarget "QueryKitTests" */; - buildPhases = ( - 77A9B680195374490016654E /* Sources */, - 77A9B681195374490016654E /* Frameworks */, - 77A9B682195374490016654E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 77A9B687195374490016654E /* PBXTargetDependency */, - 77A9B6941953744A0016654E /* PBXTargetDependency */, - 77A9B6961953744A0016654E /* PBXTargetDependency */, - 772A282B196C8D7800992BAB /* PBXTargetDependency */, - ); - name = QueryKitTests; - productName = QueryKitTests; - productReference = 77A9B684195374490016654E /* QueryKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 77A9B66E1953742F0016654E /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0600; - TargetAttributes = { - 77A9B678195374490016654E = { - CreatedOnToolsVersion = 6.0; - }; - 77A9B683195374490016654E = { - CreatedOnToolsVersion = 6.0; - TestTargetID = 77A9B678195374490016654E; - }; - }; - }; - buildConfigurationList = 77A9B6711953742F0016654E /* Build configuration list for PBXProject "QueryKit" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 77A9B66D1953742F0016654E; - productRefGroup = 77A9B67A195374490016654E /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 77A9B678195374490016654E /* QueryKit */, - 77A9B683195374490016654E /* QueryKitTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 77A9B677195374490016654E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 77A9B682195374490016654E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 77A9B674195374490016654E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 77E8728119539C0900A6F13F /* Attribute.swift in Sources */, - 77E8728719539FD200A6F13F /* Predicate.swift in Sources */, - 77A9B698195374AA0016654E /* QueryKit.swift in Sources */, - 77E3A05D1969C019009372A8 /* QuerySet.swift in Sources */, - 77E3A0611969DDF5009372A8 /* Expression.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 77A9B680195374490016654E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 77E8728319539C2A00A6F13F /* AttributeTests.swift in Sources */, - 77A9B68C195374490016654E /* QueryKitTests.swift in Sources */, - 77E3A05F1969C047009372A8 /* QuerySetTests.swift in Sources */, - 77E8728519539FC000A6F13F /* PredicateTests.swift in Sources */, - 77E3A0631969E003009372A8 /* ExpressionTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 772A282B196C8D7800992BAB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 77A9B678195374490016654E /* QueryKit */; - targetProxy = 772A282A196C8D7800992BAB /* PBXContainerItemProxy */; - }; - 77A9B687195374490016654E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 77A9B678195374490016654E /* QueryKit */; - targetProxy = 77A9B686195374490016654E /* PBXContainerItemProxy */; - }; - 77A9B6941953744A0016654E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 77A9B678195374490016654E /* QueryKit */; - targetProxy = 77A9B6931953744A0016654E /* PBXContainerItemProxy */; - }; - 77A9B6961953744A0016654E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 77A9B678195374490016654E /* QueryKit */; - targetProxy = 77A9B6951953744A0016654E /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 77A9B6721953742F0016654E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - }; - name = Debug; - }; - 77A9B6731953742F0016654E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - }; - name = Release; - }; - 77A9B68E195374490016654E /* 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_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - 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; - INFOPLIST_FILE = QueryKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - METAL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 77A9B68F195374490016654E /* 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_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - 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; - INFOPLIST_FILE = QueryKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - METAL_ENABLE_DEBUG_INFO = NO; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 77A9B691195374490016654E /* 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_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - 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; - INFOPLIST_FILE = QueryKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - METAL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 77A9B692195374490016654E /* 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_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - GCC_C_LANGUAGE_STANDARD = gnu99; - 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; - INFOPLIST_FILE = QueryKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - METAL_ENABLE_DEBUG_INFO = NO; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 77A9B6711953742F0016654E /* Build configuration list for PBXProject "QueryKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 77A9B6721953742F0016654E /* Debug */, - 77A9B6731953742F0016654E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 77A9B68D195374490016654E /* Build configuration list for PBXNativeTarget "QueryKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 77A9B68E195374490016654E /* Debug */, - 77A9B68F195374490016654E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 77A9B690195374490016654E /* Build configuration list for PBXNativeTarget "QueryKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 77A9B691195374490016654E /* Debug */, - 77A9B692195374490016654E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 77A9B66E1953742F0016654E /* 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/Attribute.swift b/QueryKit/Attribute.swift deleted file mode 100644 index 4981db9..0000000 --- a/QueryKit/Attribute.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// Attribute.swift -// QueryKit -// -// Created by Kyle Fuller on 19/06/2014. -// -// - -import Foundation - -class Attribute : Equatable { - let name:String - - init(_ name:String) { - self.name = name - } - - /// Builds a compound attribute with other key paths - convenience init(attributes:Array) { - self.init(".".join(attributes)) - } - - // Sorting - - var expression:NSExpression { - return NSExpression(forKeyPath: name) - } - - func ascending() -> NSSortDescriptor { - return NSSortDescriptor(key: name, ascending: true) - } - - func descending() -> NSSortDescriptor { - return NSSortDescriptor(key: name, ascending: false) - } -} - -func == (lhs: Attribute, rhs: Attribute) -> Bool { - return lhs.name == rhs.name -} - -@infix func == (left: Attribute, right: T) -> NSPredicate { - return left.expression == NSExpression(forConstantValue: bridgeToObjectiveC(right)) -} - -@infix func != (left: Attribute, right: T) -> NSPredicate { - return left.expression != NSExpression(forConstantValue: bridgeToObjectiveC(right)) -} - -@infix func > (left: Attribute, right: T) -> NSPredicate { - return left.expression > NSExpression(forConstantValue: bridgeToObjectiveC(right)) -} - -@infix func >= (left: Attribute, right: T) -> NSPredicate { - return left.expression >= NSExpression(forConstantValue: bridgeToObjectiveC(right)) -} - -@infix func < (left: Attribute, right: T) -> NSPredicate { - return left.expression < NSExpression(forConstantValue: bridgeToObjectiveC(right)) -} - -@infix func <= (left: Attribute, right: T) -> NSPredicate { - return left.expression <= NSExpression(forConstantValue: bridgeToObjectiveC(right)) -} - -// Bool Attributes - -@prefix func ! (left: Attribute) -> NSPredicate { - return left == false -} - -extension QuerySet { - func filter(attribute:Attribute) -> QuerySet { - return filter(attribute == true) - } - - func exclude(attribute:Attribute) -> QuerySet { - return filter(attribute == false) - } -} - -// Collections - -func count(attribute:Attribute) -> Attribute { - return Attribute(attributes: [attribute.name, "@count"]) -} - -func count(attribute:Attribute) -> Attribute { - return Attribute(attributes: [attribute.name, "@count"]) -} diff --git a/QueryKit/Expression.swift b/QueryKit/Expression.swift deleted file mode 100644 index 78e080a..0000000 --- a/QueryKit/Expression.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Expression.swift -// QueryKit -// -// Created by Kyle Fuller on 06/07/2014. -// -// - -import Foundation - -@infix func == (left: NSExpression, right: NSExpression) -> NSPredicate { - return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.EqualToPredicateOperatorType, options: NSComparisonPredicateOptions(0)) -} - -@infix func != (left: NSExpression, right: NSExpression) -> NSPredicate { - return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.NotEqualToPredicateOperatorType, options: NSComparisonPredicateOptions(0)) -} - -@infix func > (left: NSExpression, right: NSExpression) -> NSPredicate { - return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.GreaterThanPredicateOperatorType, options: NSComparisonPredicateOptions(0)) -} - -@infix func >= (left: NSExpression, right: NSExpression) -> NSPredicate { - return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.GreaterThanOrEqualToPredicateOperatorType, options: NSComparisonPredicateOptions(0)) -} - -@infix func < (left: NSExpression, right: NSExpression) -> NSPredicate { - return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.LessThanPredicateOperatorType, options: NSComparisonPredicateOptions(0)) -} - -@infix func <= (left: NSExpression, right: NSExpression) -> NSPredicate { - return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.LessThanOrEqualToPredicateOperatorType, options: NSComparisonPredicateOptions(0)) -} diff --git a/QueryKit/Info.plist b/QueryKit/Info.plist deleted file mode 100644 index c8d5065..0000000 --- a/QueryKit/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - org.cocode.${PRODUCT_NAME:rfc1034identifier} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/QueryKit/Predicate.swift b/QueryKit/Predicate.swift deleted file mode 100644 index a3202b9..0000000 --- a/QueryKit/Predicate.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Predicate.swift -// QueryKit -// -// Created by Kyle Fuller on 19/06/2014. -// -// - -import Foundation - -@infix func && (left: NSPredicate, right: NSPredicate) -> NSPredicate { - return NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: [left, right]) -} - -@infix func || (left: NSPredicate, right: NSPredicate) -> NSPredicate { - return NSCompoundPredicate(type: NSCompoundPredicateType.OrPredicateType, subpredicates: [left, right]) -} - -@prefix func ! (left: NSPredicate) -> NSPredicate { - return NSCompoundPredicate(type: NSCompoundPredicateType.NotPredicateType, subpredicates: [left]) -} diff --git a/QueryKit/QueryKit.h b/QueryKit/QueryKit.h deleted file mode 100644 index a1e3b8c..0000000 --- a/QueryKit/QueryKit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// QueryKit.h -// QueryKit -// -// Created by Kyle Fuller on 19/06/2014. -// -// - -#import - -//! Project version number for QueryKit. -FOUNDATION_EXPORT double QueryKitVersionNumber; - -//! Project version string for QueryKit. -FOUNDATION_EXPORT const unsigned char QueryKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/QueryKit/QueryKit.swift b/QueryKit/QueryKit.swift deleted file mode 100644 index ba12018..0000000 --- a/QueryKit/QueryKit.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// QueryKit.swift -// QueryKit -// -// Created by Kyle Fuller on 19/06/2014. -// -// - diff --git a/QueryKit/QuerySet.swift b/QueryKit/QuerySet.swift deleted file mode 100644 index edd4c65..0000000 --- a/QueryKit/QuerySet.swift +++ /dev/null @@ -1,190 +0,0 @@ -// -// QuerySet.swift -// QueryKit -// -// Created by Kyle Fuller on 06/07/2014. -// -// - -import Foundation -import CoreData - - -class QuerySet : Sequence, Equatable { - let context:NSManagedObjectContext - let entityName:String - - let sortDescriptors = [NSSortDescriptor]() - let predicate:NSPredicate? - let range:Range? - - // Initialization - - init(_ context:NSManagedObjectContext, _ entityName:String) { - self.context = context - self.entityName = entityName - } - - init(queryset:QuerySet, sortDescriptors:[NSSortDescriptor]?, predicate:NSPredicate?, range:Range?) { - self.context = queryset.context - self.entityName = queryset.entityName - - if let sortDescriptors = sortDescriptors { - self.sortDescriptors = sortDescriptors - } - - self.predicate = predicate - self.range = range - } - - // Sorting - - func orderBy(sortDescriptor:NSSortDescriptor) -> QuerySet { - return orderBy([sortDescriptor]) - } - - func orderBy(sortDescriptors:[NSSortDescriptor]) -> QuerySet { - return QuerySet(queryset:self, sortDescriptors:sortDescriptors, predicate:predicate, range:range) - } - - // Filtering - - func filter(predicate:NSPredicate) -> QuerySet { - var futurePredicate = predicate - - if let existingPredicate = self.predicate { - futurePredicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: [existingPredicate, predicate]) - } - - return QuerySet(queryset:self, sortDescriptors:sortDescriptors, predicate:futurePredicate, range:range) - } - - func filter(predicates:[NSPredicate]) -> QuerySet { - let newPredicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: predicates) - return filter(newPredicate) - } - - func exclude(predicate:NSPredicate) -> QuerySet { - let excludePredicate = NSCompoundPredicate(type: NSCompoundPredicateType.NotPredicateType, subpredicates: [predicate]) - return filter(excludePredicate) - } - - func exclude(predicates:[NSPredicate]) -> QuerySet { - let excludePredicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: predicates) - return exclude(excludePredicate) - } - - // Subscripting - - subscript(index: Int) -> (object:T?, error:NSError?) { - get { - var request = fetchRequest - request.fetchOffset = index - request.fetchLimit = 1 - - var error:NSError? - let items = context.executeFetchRequest(request, error:&error) - - return (object:items[0] as? T, error:error) - } - } - - subscript(index: Int) -> T? { - get { - return self[index].object - } - } - - subscript(range:Range) -> QuerySet { - get { - var fullRange = range - - if let currentRange = self.range { - fullRange = Range(start: currentRange.startIndex + range.startIndex, end: range.endIndex) - } - - return QuerySet(queryset:self, sortDescriptors:sortDescriptors, predicate:predicate, range:fullRange) - } - } - - // Conversion - - var fetchRequest:NSFetchRequest { - var request = NSFetchRequest(entityName:entityName) - request.predicate = predicate - request.sortDescriptors = sortDescriptors - - if let range = range { - request.fetchOffset = range.startIndex - request.fetchLimit = range.endIndex - range.startIndex - } - - return request - } - - func array() -> (objects:([T]?), error:NSError?) { - var error:NSError? - var objects = context.executeFetchRequest(fetchRequest, error:&error) as? [T] - return (objects:objects, error:error) - } - - func array() -> [T]? { - return array().objects - } - - // Count - - func count() -> (count:Int?, error:NSError?) { - var error:NSError? - var count:Int? = context.countForFetchRequest(fetchRequest, error: &error) - - if count! == NSNotFound { - count = nil - } - - return (count:count, error:error) - } - - func count() -> Int? { - return count().count - } - - // Deletion - - func delete() -> (count:Int, error:NSError?) { - var result = array() as (objects:([T]?), error:NSError?) - var deletedCount = 0 - - if let objects = result.objects { - for object in objects { - context.deleteObject(object) - } - - deletedCount = objects.count - } - - return (count:deletedCount, error:result.error) - } - - // Sequence - - func generate() -> IndexingGenerator> { - var result = self.array() as (objects:([T]?), error:NSError?) - - if let objects = result.objects { - return objects.generate() - } - - return [].generate() - } -} - -func == (lhs: QuerySet, rhs: QuerySet) -> Bool { - let context = lhs.context == rhs.context - let entityName = lhs.entityName == rhs.entityName - let sortDescriptors = lhs.sortDescriptors == rhs.sortDescriptors - let predicate = lhs.predicate == rhs.predicate - let startIndex = lhs.range?.startIndex == rhs.range?.startIndex - let endIndex = lhs.range?.endIndex == rhs.range?.endIndex - return context && entityName && sortDescriptors && predicate && startIndex && endIndex -} diff --git a/QueryKitTests/AttributeTests.swift b/QueryKitTests/AttributeTests.swift deleted file mode 100644 index f40784f..0000000 --- a/QueryKitTests/AttributeTests.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// AttributeTests.swift -// QueryKit -// -// Created by Kyle Fuller on 19/06/2014. -// -// - -import XCTest -import QueryKit - -class AttributeTests: XCTestCase { - var attribute:Attribute! - - override func setUp() { - super.setUp() - - attribute = Attribute("age") - } - - func testAttributeHasName() { - XCTAssertEqual(attribute.name, "age") - } - - func testAttributeExpression() { - XCTAssertEqual(attribute.expression.keyPath!, "age") - } - - func testEqualAttributesAreEquatable() { - XCTAssertEqual(attribute, Attribute("age")) - } - - func testCompoundAttributeCreation() { - let personCompanyNameAttribute = Attribute(attributes:["company", "name"]) - - XCTAssertEqual(personCompanyNameAttribute.name, "company.name") - XCTAssertEqual(personCompanyNameAttribute.expression.keyPath!, "company.name") - } - - // Sorting - - func testAscendingSortDescriptor() { - XCTAssertEqual(attribute.ascending(), NSSortDescriptor(key: "age", ascending: true)) - } - - func testDescendingSortDescriptor() { - XCTAssertEqual(attribute.descending(), NSSortDescriptor(key: "age", ascending: false)) - } - - // Operators - - func testEqualityOperator() { - var predicate:NSPredicate = (attribute == 10) - XCTAssertEqualObjects(predicate, NSPredicate(format:"age == 10")) - } - - func testInequalityOperator() { - var predicate:NSPredicate = (attribute != 10) - XCTAssertEqualObjects(predicate, NSPredicate(format:"age != 10")) - } - - func testGreaterThanOperator() { - var predicate:NSPredicate = (attribute > 10) - XCTAssertEqualObjects(predicate, NSPredicate(format:"age > 10")) - } - - func testGreaterOrEqualThanOperator() { - var predicate:NSPredicate = (attribute >= 10) - XCTAssertEqualObjects(predicate, NSPredicate(format:"age >= 10")) - } - - func testLessThanOperator() { - var predicate:NSPredicate = (attribute < 10) - XCTAssertEqualObjects(predicate, NSPredicate(format:"age < 10")) - } - - func testLessOrEqualThanOperator() { - var predicate:NSPredicate = (attribute <= 10) - XCTAssertEqualObjects(predicate, NSPredicate(format:"age <= 10")) - } -} - -class CollectionAttributeTests: XCTestCase { - func testCountOfSet() { - let setAttribute = Attribute("names") - let countAttribute = count(setAttribute) - XCTAssertEqual(countAttribute, Attribute("names.@count")) - } - - func testCountOfOrderedSet() { - let setAttribute = Attribute("names") - let countAttribute = count(setAttribute) - XCTAssertEqual(countAttribute, Attribute("names.@count")) - } -} - -class BoolAttributeTests: XCTestCase { - var attribute:Attribute! - - override func setUp() { - super.setUp() - attribute = Attribute("hasName") - } - - func testNotAttributeReturnsPredicate() { - XCTAssertEqual(!attribute, NSPredicate(format:"hasName == NO")) - } -} diff --git a/QueryKitTests/ExpressionTests.swift b/QueryKitTests/ExpressionTests.swift deleted file mode 100644 index 761ea16..0000000 --- a/QueryKitTests/ExpressionTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// ExpressionTests.swift -// QueryKit -// -// Created by Kyle Fuller on 06/07/2014. -// -// - -import XCTest -import QueryKit - -class ExpressionTests: XCTestCase { - var leftHandSide:NSExpression! - var rightHandSide:NSExpression! - - override func setUp() { - super.setUp() - - leftHandSide = NSExpression(forKeyPath: "age") - rightHandSide = NSExpression(forConstantValue: 10) - } - - func testEqualityOperator() { - XCTAssertEqualObjects(leftHandSide == rightHandSide, NSPredicate(format:"age == 10")) - } - - func testInequalityOperator() { - XCTAssertEqualObjects(leftHandSide != rightHandSide, NSPredicate(format:"age != 10")) - } - - func testGreaterThanOperator() { - let predicate:NSPredicate = leftHandSide > rightHandSide - XCTAssertEqualObjects(predicate, NSPredicate(format:"age > 10")) - } - - func testGreaterOrEqualThanOperator() { - XCTAssertEqualObjects(leftHandSide >= rightHandSide, NSPredicate(format:"age >= 10")) - } - - func testLessThanOperator() { - XCTAssertEqualObjects(leftHandSide < rightHandSide, NSPredicate(format:"age < 10")) - } - - func testLessOrEqualThanOperator() { - XCTAssertEqualObjects(leftHandSide <= rightHandSide, NSPredicate(format:"age <= 10")) - } -} diff --git a/QueryKitTests/Info.plist b/QueryKitTests/Info.plist deleted file mode 100644 index 205ebc1..0000000 --- a/QueryKitTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - org.cocode.${PRODUCT_NAME:rfc1034identifier} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/QueryKitTests/Person.h b/QueryKitTests/Person.h deleted file mode 100644 index 884af31..0000000 --- a/QueryKitTests/Person.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Person.h -// QueryKit -// -// Created by Kyle Fuller on 06/07/2014. -// -// - -#import - -@interface Person : NSManagedObject - -@property (nonatomic, retain) NSString *name; - -@end diff --git a/QueryKitTests/PredicateTests.swift b/QueryKitTests/PredicateTests.swift deleted file mode 100644 index 6ca31b5..0000000 --- a/QueryKitTests/PredicateTests.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// PredicateTests.swift -// QueryKit -// -// Created by Kyle Fuller on 19/06/2014. -// -// - -import XCTest -import QueryKit - -class PredicateTests: XCTestCase { - var namePredicate = NSPredicate(format:"name == Kyle") - var agePredicate = NSPredicate(format:"age >= 21") - - func testAndPredicate() { - var predicate = namePredicate && agePredicate - XCTAssertEqual(predicate, NSPredicate(format:"name == Kyle AND age >= 21")) - } - - func testOrPredicate() { - var predicate = namePredicate || agePredicate - XCTAssertEqual(predicate, NSPredicate(format:"name == Kyle OR age >= 21")) - } - - func testNotPredicate() { - var predicate = !namePredicate - XCTAssertEqual(predicate, NSPredicate(format:"NOT name == Kyle")) - } -} diff --git a/QueryKitTests/QueryKitTests.swift b/QueryKitTests/QueryKitTests.swift deleted file mode 100644 index 48e89df..0000000 --- a/QueryKitTests/QueryKitTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// QueryKitTests.swift -// QueryKitTests -// -// Created by Kyle Fuller on 19/06/2014. -// -// - -import XCTest -import QueryKit - -@objc(Person) class Person : NSManagedObject { - @NSManaged var name:String -} - -extension Person { - class func create(context:NSManagedObjectContext) -> Person { - return NSEntityDescription.insertNewObjectForEntityForName(Person.className(), inManagedObjectContext: context) as Person - } -} - -func managedObjectModel() -> NSManagedObjectModel { - let personEntity = NSEntityDescription() - personEntity.name = Person.className() - personEntity.managedObjectClassName = Person.className() - - let personNameAttribute = NSAttributeDescription() - personNameAttribute.name = "name" - personNameAttribute.attributeType = NSAttributeType.StringAttributeType - personNameAttribute.optional = false - personEntity.properties = [personNameAttribute] - - let model = NSManagedObjectModel() - model.entities = [personEntity] - - return model -} - -func persistentStoreCoordinator() -> NSPersistentStoreCoordinator { - let model = managedObjectModel() - let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) - persistentStoreCoordinator.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, URL: nil, options: nil, error: nil) - return persistentStoreCoordinator -} diff --git a/QueryKitTests/QuerySetTests.swift b/QueryKitTests/QuerySetTests.swift deleted file mode 100644 index d44c24a..0000000 --- a/QueryKitTests/QuerySetTests.swift +++ /dev/null @@ -1,200 +0,0 @@ -// -// QuerySetTests.swift -// QueryKit -// -// Created by Kyle Fuller on 06/07/2014. -// -// - -import XCTest -import QueryKit - -class QuerySetTests: XCTestCase { - var context:NSManagedObjectContext! - var queryset:QuerySet! - - override func setUp() { - super.setUp() - - context = NSManagedObjectContext() - context.persistentStoreCoordinator = persistentStoreCoordinator() - - for name in ["Kyle", "Orta", "Ayaka", "Mark", "Scott"] { - let person = Person.create(context) - person.name = name - } - - context.save(nil) - - queryset = QuerySet(context, "Person") - } - - func testEqualQuerySetIsEquatable() { - XCTAssertEqual(queryset, QuerySet(context, "Person")) - } - - // Sorting - - func testOrderBySortDescriptor() { - let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - var qs = queryset.orderBy(sortDescriptor) - XCTAssertEqualObjects(qs.sortDescriptors, [sortDescriptor]) - } - - func testOrderBySortDescriptors() { - let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - var qs = queryset.orderBy([sortDescriptor]) - XCTAssertEqualObjects(qs.sortDescriptors, [sortDescriptor]) - } - - // Filtering - - func testFilterPredicate() { - let predicate = NSPredicate(format: "name == Kyle") - var qs = queryset.filter(predicate) - XCTAssertEqualObjects(qs.predicate, predicate) - } - - func testFilterPredicates() { - let predicateName = NSPredicate(format: "name == Kyle") - let predicateAge = NSPredicate(format: "age > 27") - - var qs = queryset.filter([predicateName, predicateAge]) - XCTAssertEqualObjects(qs.predicate, NSPredicate(format: "name == Kyle AND age > 27")) - } - - // Exclusion - - func testExcludePredicate() { - let predicate = NSPredicate(format: "name == Kyle") - var qs = queryset.exclude(predicate) - XCTAssertEqualObjects(qs.predicate, NSPredicate(format: "NOT name == Kyle")) - } - - func testExcludePredicates() { - let predicateName = NSPredicate(format: "name == Kyle") - let predicateAge = NSPredicate(format: "age > 27") - - var qs = queryset.exclude([predicateName, predicateAge]) - XCTAssertEqualObjects(qs.predicate, NSPredicate(format: "NOT (name == Kyle AND age > 27)")) - } - - // Boolean attribute filtering and exclusion - - func testFilterBooleanAttribute() { - let qs = queryset.filter(Attribute("isEmployed")) - XCTAssertEqualObjects(qs.predicate, NSPredicate(format: "isEmployed == YES")) - } - - func testExcludeBooleanAttribute() { - let qs = queryset.exclude(Attribute("isEmployed")) - XCTAssertEqualObjects(qs.predicate, NSPredicate(format: "isEmployed == NO")) - } - - // Fetch Request - - func testConversionToFetchRequest() { - let predicate = NSPredicate(format: "name == Kyle") - let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - let qs = queryset.filter(predicate).orderBy(sortDescriptor)[2..<4] - - let fetchRequest = qs.fetchRequest - - XCTAssertEqualObjects(fetchRequest.entityName, Person.className()) - XCTAssertEqualObjects(fetchRequest.predicate, predicate) - XCTAssertEqualObjects(fetchRequest.sortDescriptors, [sortDescriptor]) - XCTAssertEqual(fetchRequest.fetchOffset, 2) - XCTAssertEqual(fetchRequest.fetchLimit, 2) - } - - // Subscripting - - func testSubscriptingAtIndex() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) - - var ayaka = qs[0].object - var kyle = qs[1].object - var mark = qs[2].object - var orta:Person? = qs[3].object - var scott:Person? = qs[4] - - XCTAssertEqualObjects(ayaka!.name, "Ayaka") - XCTAssertEqualObjects(kyle!.name, "Kyle") - XCTAssertEqualObjects(mark!.name, "Mark") - XCTAssertEqualObjects(orta!.name, "Orta") - XCTAssertEqualObjects(scott!.name, "Scott") - } - - func testSubscriptingRange() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...2] - - XCTAssertEqualObjects(qs.range!.startIndex, 0) - XCTAssertEqualObjects(qs.range!.endIndex, 3) - } - - func testSubscriptingRangeSubscriptsCurrentRange() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) - qs = qs[2...5] - qs = qs[2...4] - - XCTAssertEqualObjects(qs.range!.startIndex, 4) - XCTAssertEqualObjects(qs.range!.endIndex, 5) - } - - // Conversion - - func testConversionToArray() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...1] - var people = qs.array().objects - - XCTAssertEqual(people!.count, 2) - } - - func testConversionToArrayWithoutError() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...1] - var people = qs.array() as? [Person] - - XCTAssertEqual(people!.count, 2) - } - - // Count - - func testCount() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...1] - var count = qs.count().count - - XCTAssertEqual(count, 2) - } - - func testCountWithoutError() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...1] - var count = qs.count() as Int - - XCTAssertEqual(count, 2) - } - - // Deletion - - func testDelete() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) - - var deletedCount = qs[0...1].delete().count - var count = qs.count() as Int - - XCTAssertEqual(deletedCount, 2) - XCTAssertEqual(count, 3) - } - - // Sequence - - func testSequence() { - var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) - var objects = [Person]() - - for object in qs { - objects.append(object) - } - - XCTAssertEqual(objects.count, 5) - } -} diff --git a/README.md b/README.md index 0191f70..2cc9993 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,199 @@ -QueryKit -======== +QueryKit Logo -[![Build Status](http://img.shields.io/travis/kylef/QueryKit/master.svg?style=flat)](https://travis-ci.org/kylef/QueryKit) +# QueryKit -QueryKit, a simple CoreData query language for Swift. +QueryKit, a simple type-safe Core Data query language. ## Usage ```swift -Person.queryset(context).filter(Person.name == "Kyle").delete() +QuerySet(context, "Person") + .orderedBy(.name, ascending: true) + .filter(\.age >= 18) ``` -### Querying +### QuerySet -To retrieve objects from CoreData with QueryKit, you can construct a QuerySet -for your model in a managed object context. +A QuerySet represents a collection of objects from your Core Data Store. +It may have zero, one or many filters. Filters narrow down the query +results based on the given parameters. -A queryset is an immutable object, any operation will return a new queryset -instead of modifying the queryset. +#### Retrieving all objects ```swift -var queryset = Person.queryset(context) +let queryset = QuerySet(context, "Person") ``` -#### Filtering +#### Retrieving specific objects with filters -You can filter a queryset using the `filter` and `exclude` methods, which -accept a predicate and return a new queryset. +You may filter a QuerySet using the `filter` and `exclude` methods, which +accept a predicate which can be constructed using KeyPath extensions. + +The `filter` and `exclude` methods return new QuerySet's including your filter. + +```swift +queryset.filter(\.name == "Kyle") +queryset.exclude(\.age > 25) +``` + +You may also use standard `NSPredicate` if you want to construct complicated +queries or do not wish to use the type-safe properties. + +```swift +queryset.filter(NSPredicate(format: "name == '%@'", "Kyle")) +queryset.exclude(NSPredicate(format: "age > 25")) +``` + +##### Chaining filters + +The result of refining a QuerySet is itself a QuerySet, so it’s possible +to chain refinements together. For example: ```swift -queryset.filter(NSPredicate(format:"name == %@", "Kyle")) -queryset.filter(Person.name == "Kyle") -queryset.exclude(Person.age < 21) -queryset.exclude(Person.isEmployed) +queryset.filter(\.name == "Kyle") + .exclude(\.age < 25) ``` +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 + +A QuerySet is lazy, creating a QuerySet doesn’t involve querying +Core Data. QueryKit won’t actually execute the query until the +QuerySet is *evaluated*. + #### Ordering -You can order a queryset's results by using the `orderBy` method which accepts -a collection of sort descriptors: +You may order a QuerySet's results by using the `orderBy` function which +accepts a KeyPath. + +```swift +queryset.orderBy(\.name, ascending: true) +``` + +You may also pass in an `NSSortDescriptor` if you would rather. ```swift queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) -queryset.orderBy(Person.name.ascending) -queryset.orderBy([Person.name.ascending, Person.age.descending]) ``` #### Slicing -You can use slicing to limit a queryset to a range. For example, to get the -first 5 items: +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] ``` -### Fetching +**NOTE**: *Remember, QuerySets are lazily evaluated. Slicing doesn’t evaluate the query.* + +#### Fetching -#### Single object +##### Multiple objects + +You may convert a QuerySet to an array using the `array()` function. For example: ```swift -var kyle = queryset.filter(Person.name == "Kyle").get() +for person in try! queryset.array() { + println("Hello \(person.name).") +} ``` -#### Object at index +##### First object ```swift -var orta = queryset[3] +let kyle = try? queryset.first() ``` -#### Count +##### Last object ```swift -queryset.count() +let kyle = try? queryset.last() ``` -#### Iteration +##### Object at index ```swift -for person in queryset { - println(person.name) -} +let katie = try? queryset.object(3) ``` -#### Conversion to an array +##### Count ```swift -queryset.array() +let numberOfPeople = try? queryset.count() ``` -### Deleting +##### Deleting This method immediately deletes the objects in your queryset and returns a -count and an error if the operation failed. +count or an error if the operation failed. ```swift -queryset.delete() +let deleted = try? queryset.delete() ``` -## Predicate extensions +##### Operators -We've extended NSPredicate to add support for the `!`, `&&` and `||` operators -for joining predicates together. +QueryKit provides KeyPath extensions providing operator functions allowing you +to create predicates. ```swift -NSPredicate(format:"name == Kyle") || NSPredicate(format:"name == Katie") -NSPredicate(format:"age >= 21") && !NSPredicate(format:"name == Kyle") +// Name is equal to Kyle +\Person.name == "Kyle" + +// Name is either equal to Kyle or Katie +\.Person.name << ["Kyle", "Katie"] + +// Age is equal to 27 +\.Person.age == 27 + +// Age is more than or equal to 25 +\Person.age >= 25 + +// Age is within the range 22 to 30. +\Person.age << (22...30) ``` +The following types of comparisons are supported using Attribute: + +| Comparison | Meaning | +| ------- |:--------:| +| == | x equals y | +| != | x is not equal to y | +| < | x is less than y | +| <= | x is less than or equal to y | +| > | x is more than y | +| >= | x is more than or equal to y | +| ~= | x is like y | +| ~= | x is like y | +| << | x IN y, where y is an array | +| << | x BETWEEN y, where y is a range | + +##### Predicate extensions + +QueryKit provides the `!`, `&&` and `||` operators for joining multiple predicates together. + ```swift -Person.name == "Kyle" || Person.name == "Katie" -Person.age >= 21 || Person.name != "Kyle" +// Persons name is Kyle or Katie +\Person.name == "Kyle" || \Person.name == "Katie" + +// Persons age is more than 25 and their name is Kyle +\Person.age >= 25 && \Person.name == "Kyle" + +// Persons name is not Kyle +!(\Person.name == "Kyle") +``` + +## Installation + +[CocoaPods](http://cocoapods.org) is the recommended way to add QueryKit to +your project, you may also use Carthage. + +```ruby +pod 'QueryKit' ``` ## License diff --git a/Sources/QueryKit/Attribute.swift b/Sources/QueryKit/Attribute.swift new file mode 100644 index 0000000..a917dec --- /dev/null +++ b/Sources/QueryKit/Attribute.swift @@ -0,0 +1,128 @@ +import Foundation + +/// An attribute, representing an attribute on a model +public struct Attribute : Equatable { + public let key:String + + public init(_ key:String) { + self.key = key + } + + /// Builds a compound attribute with other key paths + public init(attributes:[String]) { + self.init(attributes.joined(separator: ".")) + } + + /// Returns an expression for the attribute + public var expression:NSExpression { + return NSExpression(forKeyPath: key) + } + + // MARK: Sorting + + /// Returns an ascending sort descriptor for the attribute + public func ascending() -> NSSortDescriptor { + return NSSortDescriptor(key: key, ascending: true) + } + + /// Returns a descending sort descriptor for the attribute + public func descending() -> NSSortDescriptor { + return NSSortDescriptor(key: key, ascending: false) + } + + func expressionForValue(_ value:AttributeType?) -> NSExpression { + if let value = value { + if let value = value as? NSObject { + return NSExpression(forConstantValue: value as NSObject) + } + + if MemoryLayout.size == MemoryLayout.size { + let value = unsafeBitCast(value, to: Optional.self) + if let value = value { + return NSExpression(forConstantValue: value) + } + } + + return NSExpression(forConstantValue: value) + } + + return NSExpression(forConstantValue: NSNull()) + } + + /// Builds a compound attribute by the current attribute with the given attribute + public func attribute(_ attribute:Attribute) -> Attribute { + return Attribute(attributes: [key, attribute.key]) + } +} + + +/// Returns true if two attributes have the same name +public func == (lhs: Attribute, rhs: Attribute) -> Bool { + return lhs.key == rhs.key +} + +public func == (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression == left.expressionForValue(right) +} + +public func != (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression != left.expressionForValue(right) +} + +public func > (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression > left.expressionForValue(right) +} + +public func >= (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression >= left.expressionForValue(right) +} + +public func < (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression < left.expressionForValue(right) +} + +public func <= (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression <= left.expressionForValue(right) +} + +public func ~= (left: Attribute, right: AttributeType?) -> NSPredicate { + return left.expression ~= left.expressionForValue(right) +} + +public func << (left: Attribute, right: [AttributeType]) -> NSPredicate { + let value = right.map { value in return value as! NSObject } + return left.expression << NSExpression(forConstantValue: value) +} + +public func << (left: Attribute, right: Range) -> NSPredicate { + let value = [right.lowerBound as! NSObject, right.upperBound as! NSObject] as NSArray + let rightExpression = NSExpression(forConstantValue: value) + + return NSComparisonPredicate(leftExpression: left.expression, rightExpression: rightExpression, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.between, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +/// MARK: Bool Attributes + +prefix public func ! (left: Attribute) -> NSPredicate { + return left == false +} + +public extension QuerySet { + func filter(_ attribute:Attribute) -> QuerySet { + return filter((attribute == true) as NSPredicate) + } + + func exclude(_ attribute:Attribute) -> QuerySet { + return filter((attribute == false) as NSPredicate) + } +} + +// MARK: Collections + +public func count(_ attribute:Attribute) -> Attribute { + return Attribute(attributes: [attribute.key, "@count"]) +} + +public func count(_ attribute:Attribute) -> Attribute { + return Attribute(attributes: [attribute.key, "@count"]) +} diff --git a/Sources/QueryKit/Expression.swift b/Sources/QueryKit/Expression.swift new file mode 100644 index 0000000..546756a --- /dev/null +++ b/Sources/QueryKit/Expression.swift @@ -0,0 +1,35 @@ +import Foundation + +/// Returns an equality predicate for the two given expressions +public func == (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.equalTo, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +/// Returns an inequality predicate for the two given expressions +public func != (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.notEqualTo, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +public func > (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.greaterThan, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +public func >= (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.greaterThanOrEqualTo, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +public func < (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.lessThan, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +public func <= (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.lessThanOrEqualTo, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +public func ~= (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.like, options: NSComparisonPredicate.Options(rawValue: 0)) +} + +public func << (left: NSExpression, right: NSExpression) -> NSPredicate { + return NSComparisonPredicate(leftExpression: left, rightExpression: right, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.in, options: NSComparisonPredicate.Options(rawValue: 0)) +} 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/Sources/QueryKit/Predicate.swift b/Sources/QueryKit/Predicate.swift new file mode 100644 index 0000000..a13b9cf --- /dev/null +++ b/Sources/QueryKit/Predicate.swift @@ -0,0 +1,79 @@ +import Foundation +import CoreData + +/// Returns an and predicate from the given predicates +public func && (left: NSPredicate, right: NSPredicate) -> NSPredicate { + return NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.and, subpredicates: [left, right]) +} + +/// Returns an or predicate from the given predicates +public func || (left: NSPredicate, right: NSPredicate) -> NSPredicate { + return NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.or, subpredicates: [left, right]) +} + +/// Returns a predicate reversing the given predicate +prefix public func ! (left: NSPredicate) -> NSPredicate { + return NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.not, subpredicates: [left]) +} + +// MARK: Predicate Type + +/// Represents a predicate for a specific model +public struct Predicate { + let predicate:NSPredicate + + init(predicate:NSPredicate) { + self.predicate = predicate + } +} + +public func == (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left == right) +} + +public func != (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left != right) +} + +public func > (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left > right) +} + +public func >= (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left >= right) +} + +public func < (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left < right) +} + +public func <= (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left <= right) +} + +public func ~= (left: Attribute, right: AttributeType?) -> Predicate { + return Predicate(predicate: left ~= right) +} + +public func << (left: Attribute, right: [AttributeType]) -> Predicate { + return Predicate(predicate: left << right) +} + +public func << (left: Attribute, right: Range) -> Predicate { + return Predicate(predicate: left << right) +} + +/// Returns an and predicate from the given predicates +public func && (left: Predicate, right: Predicate) -> Predicate { + return Predicate(predicate: left.predicate && right.predicate) +} + +/// Returns an or predicate from the given predicates +public func || (left: Predicate, right: Predicate) -> Predicate { + return Predicate(predicate: left.predicate || right.predicate) +} + +/// Returns a predicate reversing the given predicate +prefix public func ! (predicate: Predicate) -> Predicate { + return Predicate(predicate: !predicate.predicate) +} diff --git a/Sources/QueryKit/QueryKit.h b/Sources/QueryKit/QueryKit.h new file mode 100644 index 0000000..80cf39f --- /dev/null +++ b/Sources/QueryKit/QueryKit.h @@ -0,0 +1,7 @@ +#import + +//! Project version number for QueryKit. +FOUNDATION_EXPORT double QueryKitVersionNumber; + +//! Project version string for QueryKit. +FOUNDATION_EXPORT const unsigned char QueryKitVersionString[]; \ No newline at end of file diff --git a/Sources/QueryKit/QuerySet.swift b/Sources/QueryKit/QuerySet.swift new file mode 100644 index 0000000..c63d7a8 --- /dev/null +++ b/Sources/QueryKit/QuerySet.swift @@ -0,0 +1,257 @@ +import Foundation +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. + public let context: NSManagedObjectContext + + /// Returns the name of the entity the request is configured to fetch. + public let entityName: String + + /// Returns the sort descriptors of the receiver. + public let sortDescriptors: [NSSortDescriptor] + + /// Returns the predicate of the receiver. + public let predicate: NSPredicate? + + /// The range of the query, allows you to offset and limit a query + public let range: Range? + + // MARK: Initialization + + public init(_ context:NSManagedObjectContext, _ entityName:String) { + self.context = context + self.entityName = entityName + self.sortDescriptors = [] + self.predicate = nil + self.range = nil + } + + /// Create a queryset from another queryset with a different sortdescriptor predicate and range + public init(queryset:QuerySet, sortDescriptors:[NSSortDescriptor]?, predicate:NSPredicate?, range: Range?) { + self.context = queryset.context + self.entityName = queryset.entityName + self.sortDescriptors = sortDescriptors ?? [] + self.predicate = predicate + self.range = range + } +} + +/// Methods which return a new queryset +extension QuerySet { + // MARK: Sorting + + /// Returns a new QuerySet containing objects ordered by the given sort descriptor. + public func orderBy(_ sortDescriptor:NSSortDescriptor) -> QuerySet { + return orderBy([sortDescriptor]) + } + + /// Returns a new QuerySet containing objects ordered by the given sort descriptors. + public func orderBy(_ sortDescriptors:[NSSortDescriptor]) -> QuerySet { + return QuerySet(queryset:self, sortDescriptors:sortDescriptors, predicate:predicate, range:range) + } + + /// Reverses the ordering of the QuerySet + public func reverse() -> QuerySet { + func reverseSortDescriptor(_ sortDescriptor:NSSortDescriptor) -> NSSortDescriptor { + return NSSortDescriptor(key: sortDescriptor.key!, ascending: !sortDescriptor.ascending) + } + + return QuerySet(queryset:self, sortDescriptors:sortDescriptors.map(reverseSortDescriptor), predicate:predicate, range:range) + } + + // 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) + } + + /// Returns a new QuerySet containing objects ordered by the given sort descriptors. + public func orderBy(_ closure:((ModelType.Type) -> ([SortDescriptor]))) -> QuerySet { + return orderBy(closure(ModelType.self).map { $0.sortDescriptor }) + } + + // 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 + + if let existingPredicate = self.predicate { + futurePredicate = NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.and, subpredicates: [existingPredicate, predicate]) + } + + return QuerySet(queryset:self, sortDescriptors:sortDescriptors, predicate:futurePredicate, range:range) + } + + /// Returns a new QuerySet containing objects that match the given predicates. + public func filter(_ predicates:[NSPredicate]) -> QuerySet { + let newPredicate = NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.and, subpredicates: predicates) + 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]) + return filter(excludePredicate) + } + + /// Returns a new QuerySet containing objects that exclude the given predicates. + public func exclude(_ predicates:[NSPredicate]) -> QuerySet { + let excludePredicate = NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.and, subpredicates: predicates) + return exclude(excludePredicate) + } + + // 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 }) + } +} + +/// Functions for evauluating a QuerySet +extension QuerySet { + // MARK: Subscripting + + /// Returns the object at the specified index. + public func object(_ index: Int) throws -> ModelType? { + let request = fetchRequest + request.fetchOffset = index + request.fetchLimit = 1 + let items = try context.fetch(request) + return items.first + } + + public subscript(range: ClosedRange) -> QuerySet { + get { + return self[Range(range)] + } + } + + public subscript(range: Range) -> QuerySet { + get { + var fullRange = range + + if let currentRange = self.range { + fullRange = ((currentRange.lowerBound + range.lowerBound) ..< range.upperBound) + } + + return QuerySet(queryset:self, sortDescriptors:sortDescriptors, predicate:predicate, range:fullRange) + } + } + + // Mark: Getters + + /// Returns the first object in the QuerySet + public func first() throws -> ModelType? { + return try self.object(0) + } + + /// Returns the last object in the QuerySet + public func last() throws -> ModelType? { + return try reverse().first() + } + + // MARK: Conversion + + /// Returns a fetch request equivilent to the QuerySet + public var fetchRequest: NSFetchRequest { + let request = NSFetchRequest(entityName: entityName) + request.predicate = predicate + request.sortDescriptors = sortDescriptors + + if let range = range { + request.fetchOffset = range.lowerBound + request.fetchLimit = range.upperBound - range.lowerBound + } + + return request + } + + /// Returns an array of all objects matching the QuerySet + public func array() throws -> [ModelType] { + return try context.fetch(fetchRequest) + } + + // MARK: Count + + /// Returns the count of objects matching the QuerySet. + public func count() throws -> Int { + return try context.count(for: fetchRequest) + } + + // MARK: Exists + + /** Returns true if the QuerySet contains any results, and false if not. + :note: Returns nil if the operation could not be completed. + */ + public func exists() throws -> Bool { + let fetchRequest = self.fetchRequest + fetchRequest.fetchLimit = 1 + + let result = try context.count(for: fetchRequest) + return result != 0 + } + + // MARK: Deletion + + /// Deletes all the objects matching the QuerySet. + public func delete() throws -> Int { + let objects = try array() + let deletedCount = objects.count + + for object in objects { + context.delete(object) + } + + return deletedCount + } +} + +/// Returns true if the two given querysets are equivilent +public func == (lhs: QuerySet, rhs: QuerySet) -> Bool { + let context = lhs.context == rhs.context + let entityName = lhs.entityName == rhs.entityName + let sortDescriptors = lhs.sortDescriptors == rhs.sortDescriptors + let predicate = lhs.predicate == rhs.predicate + let startIndex = lhs.range?.lowerBound == rhs.range?.lowerBound + let endIndex = lhs.range?.upperBound == rhs.range?.upperBound + return context && entityName && sortDescriptors && predicate && startIndex && endIndex +} diff --git a/Sources/QueryKit/SortDescriptor.swift b/Sources/QueryKit/SortDescriptor.swift new file mode 100644 index 0000000..96bfc25 --- /dev/null +++ b/Sources/QueryKit/SortDescriptor.swift @@ -0,0 +1,23 @@ +import Foundation +import CoreData + +/// Represents a sort descriptor for a specific model +public struct SortDescriptor { + let sortDescriptor:NSSortDescriptor + + init(sortDescriptor:NSSortDescriptor) { + self.sortDescriptor = sortDescriptor + } +} + +extension Attribute { + /// Returns an ascending sort descriptor for the attribute + public func ascending() -> SortDescriptor { + return SortDescriptor(sortDescriptor: ascending()) + } + + /// Returns a descending sort descriptor for the attribute + public func descending() -> SortDescriptor { + return SortDescriptor(sortDescriptor: descending()) + } +} diff --git a/Tests/QueryKitTests/AttributeTests.swift b/Tests/QueryKitTests/AttributeTests.swift new file mode 100644 index 0000000..e411fac --- /dev/null +++ b/Tests/QueryKitTests/AttributeTests.swift @@ -0,0 +1,140 @@ +// +// AttributeTests.swift +// QueryKit +// +// Created by Kyle Fuller on 19/06/2014. +// +// + +import XCTest +import QueryKit + +class AttributeTests: XCTestCase { + var attribute:Attribute! + + override func setUp() { + super.setUp() + attribute = Attribute("age") + } + + func testAttributeHasKey() { + XCTAssertEqual(attribute.key, "age") + } + + func testAttributeExpression() { + XCTAssertEqual(attribute.expression.keyPath, "age") + } + + func testEqualAttributesAreEquatable() { + XCTAssertEqual(attribute, Attribute("age")) + } + + func testCompoundAttributeCreation() { + let personCompanyNameAttribute = Attribute(attributes:["company", "name"]) + + XCTAssertEqual(personCompanyNameAttribute.key, "company.name") + XCTAssertEqual(personCompanyNameAttribute.expression.keyPath, "company.name") + } + + // Sorting + + func testAscendingSortDescriptor() { + XCTAssertEqual(attribute.ascending(), NSSortDescriptor(key: "age", ascending: true)) + } + + func testDescendingSortDescriptor() { + XCTAssertEqual(attribute.descending(), NSSortDescriptor(key: "age", ascending: false)) + } + + // Operators + + func testEqualityOperator() { + let predicate:NSPredicate = (attribute == 10) + XCTAssertEqual(predicate, NSPredicate(format:"age == 10")) + } + + func testInequalityOperator() { + let predicate:NSPredicate = (attribute != 10) + XCTAssertEqual(predicate, NSPredicate(format:"age != 10")) + } + + func testGreaterThanOperator() { + let predicate:NSPredicate = (attribute > 10) + XCTAssertEqual(predicate, NSPredicate(format:"age > 10")) + } + + func testGreaterOrEqualThanOperator() { + let predicate:NSPredicate = (attribute >= 10) + XCTAssertEqual(predicate, NSPredicate(format:"age >= 10")) + } + + func testLessThanOperator() { + let predicate:NSPredicate = (attribute < 10) + XCTAssertEqual(predicate, NSPredicate(format:"age < 10")) + } + + func testLessOrEqualThanOperator() { + let predicate:NSPredicate = (attribute <= 10) + XCTAssertEqual(predicate, NSPredicate(format:"age <= 10")) + } + + func testLikeOperator() { + let predicate:NSPredicate = (attribute ~= 10) + XCTAssertEqual(predicate, NSPredicate(format:"age LIKE 10")) + } + + func testInOperator() { + let predicate:NSPredicate = (attribute << [5, 10]) + XCTAssertEqual(predicate, NSPredicate(format:"age IN %@", [5, 10])) + } + + func testBetweenRangeOperator() { + let predicate:NSPredicate = attribute << (5..<10) + XCTAssertEqual(predicate, NSPredicate(format:"age BETWEEN %@", [5, 10])) + } + + func testOptionalEqualityOperator() { + let attribute = Attribute("name") + let predicate:NSPredicate = (attribute == "kyle") + XCTAssertEqual(predicate, NSPredicate(format:"name == 'kyle'")) + } + + func testOptionalNSObjectEqualityOperator() { + let attribute = Attribute("name") + let predicate:NSPredicate = (attribute == "kyle") + XCTAssertEqual(predicate, NSPredicate(format:"name == 'kyle'")) + } + + func testEqualityOperatorWithNilRHS() { + let attribute = Attribute("name") + let predicate: NSPredicate = attribute == nil + XCTAssertEqual(predicate.description, "name == ") + } +} + +class CollectionAttributeTests: XCTestCase { + func testCountOfSet() { + let setAttribute = Attribute("names") + let countAttribute = count(setAttribute) + XCTAssertEqual(countAttribute, Attribute("names.@count")) + } + + func testCountOfOrderedSet() { + let setAttribute = Attribute("names") + let countAttribute = count(setAttribute) + XCTAssertEqual(countAttribute, Attribute("names.@count")) + } +} + +class BoolAttributeTests: XCTestCase { + var attribute:Attribute! + + override func setUp() { + super.setUp() + attribute = Attribute("hasName") + } + + func testNotAttributeReturnsPredicate() { + XCTAssertEqual(!attribute, NSPredicate(format:"hasName == NO")) + } +} diff --git a/Tests/QueryKitTests/ExpressionTests.swift b/Tests/QueryKitTests/ExpressionTests.swift new file mode 100644 index 0000000..9d823a7 --- /dev/null +++ b/Tests/QueryKitTests/ExpressionTests.swift @@ -0,0 +1,55 @@ +// +// ExpressionTests.swift +// QueryKit +// +// Created by Kyle Fuller on 06/07/2014. +// +// + +import XCTest +import QueryKit + +class ExpressionTests: XCTestCase { + var leftHandSide:NSExpression! + var rightHandSide:NSExpression! + + override func setUp() { + super.setUp() + + leftHandSide = NSExpression(forKeyPath: "age") + rightHandSide = NSExpression(forConstantValue: 10) + } + + func testEqualityOperator() { + XCTAssertEqual(leftHandSide == rightHandSide, NSPredicate(format:"age == 10")) + } + + func testInequalityOperator() { + XCTAssertEqual(leftHandSide != rightHandSide, NSPredicate(format:"age != 10")) + } + + func testGreaterThanOperator() { + let predicate:NSPredicate = leftHandSide > rightHandSide + XCTAssertEqual(predicate, NSPredicate(format:"age > 10")) + } + + func testGreaterOrEqualThanOperator() { + XCTAssertEqual(leftHandSide >= rightHandSide, NSPredicate(format:"age >= 10")) + } + + func testLessThanOperator() { + XCTAssertEqual(leftHandSide < rightHandSide, NSPredicate(format:"age < 10")) + } + + func testLessOrEqualThanOperator() { + XCTAssertEqual(leftHandSide <= rightHandSide, NSPredicate(format:"age <= 10")) + } + + func testLikeOperator() { + XCTAssertEqual(leftHandSide ~= rightHandSide, NSPredicate(format:"age LIKE 10")) + } + + func testInOperator() { + XCTAssertEqual(leftHandSide << rightHandSide, NSPredicate(format:"age IN 10")) + } +} 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/Tests/QueryKitTests/PredicateTests.swift b/Tests/QueryKitTests/PredicateTests.swift new file mode 100644 index 0000000..1746ecf --- /dev/null +++ b/Tests/QueryKitTests/PredicateTests.swift @@ -0,0 +1,120 @@ +// +// PredicateTests.swift +// QueryKit +// +// Created by Kyle Fuller on 19/06/2014. +// +// + +import XCTest +@testable import QueryKit + + +class NSPredicateTests: XCTestCase { + var namePredicate = NSPredicate(format: "name == Kyle") + var agePredicate = NSPredicate(format: "age >= 21") + + func testAndPredicate() { + let predicate = namePredicate && agePredicate + XCTAssertEqual(predicate, NSPredicate(format: "name == Kyle AND age >= 21")) + } + + func testOrPredicate() { + let predicate = namePredicate || agePredicate + XCTAssertEqual(predicate, NSPredicate(format: "name == Kyle OR age >= 21")) + } + + func testNotPredicate() { + let predicate = !namePredicate + XCTAssertEqual(predicate, NSPredicate(format: "NOT name == Kyle")) + } +} + + +class PredicateTests: XCTestCase { + var attribute:Attribute! + + override func setUp() { + super.setUp() + attribute = Attribute("age") + } + + // MARK: Operators + + func testEqualityOperator() { + let predicate:Predicate = attribute == 10 + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age == 10")) + } + + func testEqualityOperatorWithNilRHS() { + let attribute = Attribute("name") + let predicate: Predicate = attribute == nil + XCTAssertEqual(predicate.predicate.description, "name == ") + } + + func testInequalityOperator() { + let predicate:Predicate = (attribute != 10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age != 10")) + } + + func testGreaterThanOperator() { + let predicate:Predicate = (attribute > 10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age > 10")) + } + + func testGreaterOrEqualThanOperator() { + let predicate:Predicate = (attribute >= 10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age >= 10")) + } + + func testLessThanOperator() { + let predicate:Predicate = (attribute < 10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age < 10")) + } + + func testLessOrEqualThanOperator() { + let predicate:Predicate = (attribute <= 10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age <= 10")) + } + + func testLikeOperator() { + let predicate:Predicate = (attribute ~= 10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age LIKE 10")) + } + + func testInOperator() { + let predicate:Predicate = (attribute << [5, 10]) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age IN %@", [5, 10])) + } + + func testBetweenRangeOperator() { + let predicate:Predicate = attribute << (5..<10) + XCTAssertEqual(predicate.predicate, NSPredicate(format:"age BETWEEN %@", [5, 10])) + } + + func testNSObjectEqualityOperator() { + let attribute = Attribute("name") + let predicate:Predicate = (attribute == "kyle") + XCTAssertEqual(predicate.predicate, NSPredicate(format:"name == 'kyle'")) + } + + // MARK: + + var namePredicate = Predicate(predicate: NSPredicate(format: "name == Kyle")) + var agePredicate = Predicate(predicate: NSPredicate(format: "age >= 21")) + + func testAndPredicate() { + let predicate = namePredicate && agePredicate + XCTAssertEqual(predicate.predicate, NSPredicate(format: "name == Kyle AND age >= 21")) + } + + func testOrPredicate() { + let predicate = namePredicate || agePredicate + XCTAssertEqual(predicate.predicate, NSPredicate(format: "name == Kyle OR age >= 21")) + } + + func testNotPredicate() { + let predicate = !namePredicate + XCTAssertEqual(predicate.predicate, NSPredicate(format: "NOT name == Kyle")) + } +} diff --git a/Tests/QueryKitTests/QueryKitTests.swift b/Tests/QueryKitTests/QueryKitTests.swift new file mode 100644 index 0000000..fc429ab --- /dev/null +++ b/Tests/QueryKitTests/QueryKitTests.swift @@ -0,0 +1,127 @@ +// +// QueryKitTests.swift +// QueryKitTests +// +// Created by Kyle Fuller on 19/06/2014. +// +// + +import XCTest +import QueryKit +import CoreData + +@objc(Person) class Person : NSManagedObject { + @NSManaged var name:String + @NSManaged var company:Company? + + class var entityName:String { + return "Person" + } + + class var name:Attribute { + return Attribute("name") + } + + class var company:Attribute { + return Attribute("company") + } +} + +@objc(Company) class Company : NSManagedObject { + @NSManaged var name:String + + class var entityName:String { + return "Company" + } + + class var name:Attribute { + return Attribute("name") + } + + class func create(_ context:NSManagedObjectContext) -> Company { + return NSEntityDescription.insertNewObject(forEntityName: Company.entityName, into: context) as! Company + } +} + +extension Attribute where AttributeType: Company { + var name:Attribute { + return attribute(AttributeType.name) + } +} + +extension Person { + class func create(_ context:NSManagedObjectContext) -> Person { + return NSEntityDescription.insertNewObject(forEntityName: Person.entityName, into: context) as! Person + } +} + +func managedObjectModel() -> NSManagedObjectModel { + let companyEntity = NSEntityDescription() + companyEntity.name = Company.entityName + companyEntity.managedObjectClassName = "Company" + + let personEntity = NSEntityDescription() + personEntity.name = Person.entityName + personEntity.managedObjectClassName = "Person" + + let companyNameAttribute = NSAttributeDescription() + companyNameAttribute.name = "name" + companyNameAttribute.attributeType = NSAttributeType.stringAttributeType + companyNameAttribute.isOptional = false + + let companyPeopleAttribute = NSRelationshipDescription() + companyPeopleAttribute.name = "members" + companyPeopleAttribute.maxCount = 0 + companyPeopleAttribute.destinationEntity = personEntity + + let personNameAttribute = NSAttributeDescription() + personNameAttribute.name = "name" + personNameAttribute.attributeType = NSAttributeType.stringAttributeType + personNameAttribute.isOptional = false + + let personCompanyRelation = NSRelationshipDescription() + personCompanyRelation.name = "company" + personCompanyRelation.destinationEntity = companyEntity + personCompanyRelation.maxCount = 1 + personCompanyRelation.isOptional = true + + companyPeopleAttribute.inverseRelationship = personCompanyRelation + personCompanyRelation.inverseRelationship = companyPeopleAttribute + + companyEntity.properties = [companyNameAttribute, companyPeopleAttribute] + personEntity.properties = [personNameAttribute, personCompanyRelation] + + let model = NSManagedObjectModel() + model.entities = [personEntity, companyEntity] + + return model +} + +func persistentStoreCoordinator() -> NSPersistentStoreCoordinator { + let model = managedObjectModel() + let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) + do { + try persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) + } catch { + print(error) + fatalError() + } + return persistentStoreCoordinator +} + +public func AssertNotThrow(_ closure: @autoclosure () throws -> R) -> R? { + var result: R? + AssertNotThrow() { + result = try closure() + } + return result +} + +public func AssertNotThrow(_ closure: () throws -> ()) { + do { + try closure() + } catch let error { + XCTFail("Catched error \(error), " + + "but did not expect any error.") + } +} diff --git a/Tests/QueryKitTests/QuerySetTests.swift b/Tests/QueryKitTests/QuerySetTests.swift new file mode 100644 index 0000000..5ecab25 --- /dev/null +++ b/Tests/QueryKitTests/QuerySetTests.swift @@ -0,0 +1,283 @@ +// +// QuerySetTests.swift +// QueryKit +// +// Created by Kyle Fuller on 06/07/2014. +// +// + +import XCTest +import CoreData +import QueryKit + + +class QuerySetTests: XCTestCase { + var context:NSManagedObjectContext! + var queryset:QuerySet! + + override func setUp() { + super.setUp() + + context = NSManagedObjectContext() + context.persistentStoreCoordinator = persistentStoreCoordinator() + + let company = Company.create(context) + company.name = "Cocode" + + for name in ["Kyle", "Orta", "Ayaka", "Mark", "Scott"] { + let person = Person.create(context) + person.name = name + + if name == "Kyle" { + person.company = company + } + } + + try! context.save() + + queryset = QuerySet(context, "Person") + } + + func testEqualQuerySetIsEquatable() { + XCTAssertEqual(queryset, QuerySet(context, "Person")) + } + + // 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) + XCTAssertTrue(qs.sortDescriptors == [sortDescriptor]) + } + + func testOrderBySortDescriptors() { + let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) + let qs = queryset.orderBy([sortDescriptor]) + XCTAssertTrue(qs.sortDescriptors == [sortDescriptor]) + } + + func testTypeSafeOrderBySortDescriptor() { + let qs = queryset.orderBy { $0.name.ascending() } + XCTAssertTrue(qs.sortDescriptors == [NSSortDescriptor(key: "name", ascending: true)]) + } + + func testTypeSafeOrderBySortDescriptors() { + let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) + let qs = queryset.orderBy { [$0.name.ascending() as SortDescriptor] } + XCTAssertTrue(qs.sortDescriptors == [sortDescriptor]) + } + + func testReverseOrdering() { + let nameSortDescriptor = NSSortDescriptor(key: "name", ascending: true) + let ageSortDescriptor = NSSortDescriptor(key: "age", ascending: true) + let qs = queryset.orderBy([nameSortDescriptor, ageSortDescriptor]).reverse() + + XCTAssertEqual(qs.sortDescriptors, [ + NSSortDescriptor(key: "name", ascending: false), + NSSortDescriptor(key: "age", ascending: false), + ]) + } + + // 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) + XCTAssertEqual(qs.predicate!, predicate) + } + + func testFilterPredicates() { + let predicateName = NSPredicate(format: "name == Kyle") + let predicateAge = NSPredicate(format: "age > 27") + + let qs = queryset.filter([predicateName, predicateAge]) + XCTAssertEqual(qs.predicate!, NSPredicate(format: "name == Kyle AND age > 27")) + } + + func testFilterBooleanAttribute() { + let qs = queryset.filter(Attribute("isEmployed")) + XCTAssertEqual(qs.predicate!, NSPredicate(format: "isEmployed == YES")) + } + + func testTypeSafeFilter() { + let qs = queryset.filter { $0.name == "Kyle" } + + XCTAssertEqual(qs.predicate?.description, "name == \"Kyle\"") + } + + func testTypeSafeFilerEqualWithNilRHS() { + let qs = queryset.filter { $0.name == nil } + XCTAssertEqual(qs.predicate?.description, "name == ") + } + + func testTypeSafeRelatedFilterPredicate() { + let at = Attribute("company") + XCTAssertEqual(at.name.key, "company.name") + let qs = queryset.filter { $0.company.name == "Cocode" } + + XCTAssertEqual(qs.predicate?.description, "company.name == \"Cocode\"") + } + + // 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) + XCTAssertEqual(qs.predicate!, NSPredicate(format: "NOT name == Kyle")) + } + + func testExcludePredicates() { + let predicateName = NSPredicate(format: "name == Kyle") + let predicateAge = NSPredicate(format: "age > 27") + + let qs = queryset.exclude([predicateName, predicateAge]) + XCTAssertEqual(qs.predicate!, NSPredicate(format: "NOT (name == Kyle AND age > 27)")) + } + + func testExcludeBooleanAttribute() { + let qs = queryset.exclude(Attribute("isEmployed")) + XCTAssertEqual(qs.predicate!, NSPredicate(format: "isEmployed == NO")) + } + + func testTypeSafeExclude() { + let qs = queryset.exclude { $0.name == "Kyle" } + + XCTAssertEqual(qs.predicate?.description, "NOT name == \"Kyle\"") + } + + // Fetch Request + + func testConversionToFetchRequest() { + let predicate = NSPredicate(format: "name == Kyle") + let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) + let qs = queryset.filter(predicate).orderBy(sortDescriptor)[2..<4] + + let fetchRequest = qs.fetchRequest + + XCTAssertEqual(fetchRequest.entityName!, "Person") + XCTAssertEqual(fetchRequest.predicate!, predicate) + // XCTAssertEqual(fetchRequest.sortDescriptors!, [sortDescriptor]) + XCTAssertEqual(fetchRequest.fetchOffset, 2) + XCTAssertEqual(fetchRequest.fetchLimit, 2) + } + + // MARK: Subscripting + + func testSubscriptingAtIndex() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) + + let ayaka = try! qs.object(0) + let kyle = try! qs.object(1) + let mark = try! qs.object(2) + let orta = try! qs.object(3) + let scott = try! qs.object(4) + + XCTAssertEqual(ayaka!.name, "Ayaka") + XCTAssertEqual(kyle!.name, "Kyle") + XCTAssertEqual(mark!.name, "Mark") + XCTAssertEqual(orta!.name, "Orta") + XCTAssertEqual(scott!.name, "Scott") + } + + func testSubscriptingRange() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...2] + + XCTAssertEqual(qs.range!.startIndex, 0) + XCTAssertEqual(qs.range!.endIndex, 3) + } + + func testSubscriptingRangeSubscriptsCurrentRange() { + var qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) + qs = qs[2...5] + qs = qs[2...4] + + XCTAssertEqual(qs.range!.startIndex, 4) + XCTAssertEqual(qs.range!.endIndex, 5) + } + + // MARK: Getters + + func testFirst() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) + let name = try! qs.first()?.name + XCTAssertEqual(name, "Ayaka") + } + + func testLast() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) + let name = try! qs.last()?.name + XCTAssertEqual(name, "Scott") + } + + // MARK: Conversion + + func testConversionToArray() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...1] + let people = AssertNotThrow(try qs.array()) ?? [] + + XCTAssertEqual(people.count, 2) + } + + // MARK: Count + + func testCount() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true))[0...1] + let count = AssertNotThrow(try qs.count()) + + XCTAssertEqual(count!, 2) + } + + // MARK: Exists + + func testExistsReturnsTrueWithMatchingObjects() { + let qs = queryset.filter(NSPredicate(format: "name == %@", "Kyle")) + let exists = AssertNotThrow(try qs.exists()) ?? false + + XCTAssertTrue(exists) + } + + func testExistsReturnsFalseWithNoMatchingObjects() { + let qs = queryset.filter(NSPredicate(format: "name == %@", "None")) + let exists = AssertNotThrow(try qs.exists()) ?? true + + XCTAssertFalse(exists) + } + + // MARK: Deletion + + func testDelete() { + let qs = queryset.orderBy(NSSortDescriptor(key: "name", ascending: true)) + + let deletedCount = AssertNotThrow(try qs[0...1].delete()) ?? 0 + let count = AssertNotThrow(try qs.count()) ?? 0 + + XCTAssertEqual(deletedCount, 2) + XCTAssertEqual(count, 3) + } +} diff --git a/Tests/QueryKitTests/SortDescriptorTests.swift b/Tests/QueryKitTests/SortDescriptorTests.swift new file mode 100644 index 0000000..34f43ff --- /dev/null +++ b/Tests/QueryKitTests/SortDescriptorTests.swift @@ -0,0 +1,21 @@ +import XCTest +@testable import QueryKit + +class SortDescriptorTests: XCTestCase { + var attribute:Attribute! + + override func setUp() { + super.setUp() + attribute = Attribute("age") + } + + func testAscendingSortDescriptor() { + let sortDescriptor:SortDescriptor = attribute.ascending() + XCTAssertEqual(sortDescriptor.sortDescriptor, NSSortDescriptor(key: "age", ascending: true)) + } + + func testDescendingSortDescriptor() { + let sortDescriptor:SortDescriptor = attribute.descending() + XCTAssertEqual(sortDescriptor.sortDescriptor, NSSortDescriptor(key: "age", ascending: false)) + } +}