Dactyloidae iOS initial commit

This commit is contained in:
wuggy 2026-06-26 21:04:09 -07:00
commit 7154a0497e
2123 changed files with 197052 additions and 0 deletions

View file

@ -0,0 +1,116 @@
/*
File: UIImage+ImageEffects.h
Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code youll want to look out to find out how to use vImage to efficiently calculate a blur.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2013 Apple Inc. All Rights Reserved.
Copyright © 2013 Apple Inc. All rights reserved.
WWDC 2013 License
NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013
Session. Please refer to the applicable WWDC 2013 Session for further
information.
IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and
your use, installation, modification or redistribution of this Apple
software constitutes acceptance of these terms. If you do not agree with
these terms, please do not use, install, modify or redistribute this
Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple
Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EA1002
5/3/2013
*/
typedef enum {
NOBLUR,
BOXFILTER,
TENTFILTER
} BlurType;
@import UIKit;
@interface UIImage (ImageEffects)
- (UIImage *) applyLightEffect;
- (UIImage *) applyExtraLightEffect;
- (UIImage *) applyDarkEffect;
- (UIImage *) applyDarkEffectWithTent: (CGFloat) radius;
- (UIImage *) applyTintEffectWithColor:(UIColor *)tintColor;
- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius blurType: (BlurType) blurType tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage;
@end

View file

@ -0,0 +1,288 @@
/*
File: UIImage+ImageEffects.m
Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code youll want to look out to find out how to use vImage to efficiently calculate a blur.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2013 Apple Inc. All Rights Reserved.
Copyright © 2013 Apple Inc. All rights reserved.
WWDC 2013 License
NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013
Session. Please refer to the applicable WWDC 2013 Session for further
information.
IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and
your use, installation, modification or redistribution of this Apple
software constitutes acceptance of these terms. If you do not agree with
these terms, please do not use, install, modify or redistribute this
Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple
Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EA1002
5/3/2013
*/
#import "UIImage+ImageEffects.h"
@import Accelerate;
#import <float.h>
@implementation UIImage (ImageEffects)
- (UIImage *)applyLightEffect
{
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3];
return [self applyBlurWithRadius:30 blurType: BOXFILTER tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *)applyExtraLightEffect
{
UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82];
return [self applyBlurWithRadius:20 blurType: BOXFILTER tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *)applyDarkEffect
{
UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73];
return [self applyBlurWithRadius:10 blurType: BOXFILTER tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *) applyDarkEffectWithTent: (CGFloat) radius
{
UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.5];
return [self applyBlurWithRadius:radius blurType: TENTFILTER tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor
{
const CGFloat EffectColorAlpha = 0.6;
UIColor *effectColor = tintColor;
size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
if (componentCount == 2) {
CGFloat b;
if ([tintColor getWhite:&b alpha:NULL]) {
effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
}
}
else {
CGFloat r, g, b;
if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
}
}
return [self applyBlurWithRadius:10 blurType: BOXFILTER tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil];
}
- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius blurType: (BlurType) blurType tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage
{
// Check pre-conditions.
if (self.size.width < 1 || self.size.height < 1) {
NSLog (@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog (@"*** error: image must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog (@"*** error: maskImage must be backed by a CGImage: %@", maskImage);
return nil;
}
CGRect imageRect = { CGPointZero, self.size };
UIImage *effectImage = self;
BOOL hasBlur = blurRadius > __FLT_EPSILON__ && blurType != NOBLUR;
BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
if (hasBlur || hasSaturationChange) {
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef effectInContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(effectInContext, 1.0, -1.0);
CGContextTranslateCTM(effectInContext, 0, -self.size.height);
CGContextDrawImage(effectInContext, imageRect, self.CGImage);
vImage_Buffer effectInBuffer;
effectInBuffer.data = CGBitmapContextGetData(effectInContext);
effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
vImage_Buffer effectOutBuffer;
effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
if (hasBlur) {
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
//
// For larger values of 's' (s >= 2.0), an approximation can be used: Three
// successive box-blurs build a piece-wise quadratic convolution kernel, which
// approximates the Gaussian kernel to within roughly 3%.
//
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
uint32_t radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
if (radius % 2 != 1) {
radius += 1; // force radius to be odd so that the three box-blur methodology works.
}
if (blurType == BOXFILTER)
{
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
}
else
{
vImageTentConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
}
}
BOOL effectImageBuffersAreSwapped = NO;
if (hasSaturationChange) {
CGFloat s = saturationDeltaFactor;
CGFloat floatingPointSaturationMatrix[] = {
0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
0, 0, 0, 1,
};
const int32_t divisor = 256;
NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]);
int16_t saturationMatrix[matrixSize];
for (NSUInteger i = 0; i < matrixSize; ++i) {
saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor);
}
if (hasBlur) {
vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
effectImageBuffersAreSwapped = YES;
}
else {
vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
}
}
if (!effectImageBuffersAreSwapped)
effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (effectImageBuffersAreSwapped)
effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
// Set up output context.
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef outputContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(outputContext, 1.0, -1.0);
CGContextTranslateCTM(outputContext, 0, -self.size.height);
// Draw effect image.
if (hasBlur) {
CGContextSaveGState(outputContext);
if (maskImage) {
CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
}
CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
CGContextRestoreGState(outputContext);
}
// Add in color tint.
if (tintColor) {
CGContextSaveGState(outputContext);
CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
CGContextFillRect(outputContext, imageRect);
CGContextRestoreGState(outputContext);
}
// Output image is ready.
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
@end

33
mobile/ios/ThirdParty/Box/Box.swift vendored Normal file
View file

@ -0,0 +1,33 @@
// Copyright (c) 2014 Rob Rix. All rights reserved.
/// Wraps a type `T` in a reference type.
///
/// Typically this is used to work around limitations of value types (for example, the lack of codegen for recursive value types and type-parameterized enums with >1 case). It is also useful for sharing a single (presumably large) value without copying it.
public final class Box<T>: BoxType, CustomStringConvertible {
/// Initializes a `Box` with the given value.
public init(_ value: T) {
self.value = value
}
/// Constructs a `Box` with the given `value`.
public class func unit(_ value: T) -> Box<T> {
return Box(value)
}
/// The (immutable) value wrapped by the receiver.
public let value: T
/// Constructs a new Box by transforming `value` by `f`.
public func map<U>(_ f: (T) -> U) -> Box<U> {
return Box<U>(f(value))
}
// MARK: Printable
public var description: String {
return String(describing: value)
}
}

46
mobile/ios/ThirdParty/Box/BoxType.swift vendored Normal file
View file

@ -0,0 +1,46 @@
// Copyright (c) 2014 Rob Rix. All rights reserved.
// MARK: BoxType
/// The type conformed to by all boxes.
public protocol BoxType {
/// The type of the wrapped value.
associatedtype Value
/// Initializes an intance of the type with a value.
init(_ value: Value)
/// The wrapped value.
var value: Value { get }
}
/// The type conformed to by mutable boxes.
public protocol MutableBoxType: BoxType {
/// The (mutable) wrapped value.
var value: Value { get set }
}
// MARK: Equality
/// Equality of `BoxType`s of `Equatable` types.
///
/// We cannot declare that e.g. `Box<T: Equatable>` conforms to `Equatable`, so this is a relatively ad hoc definition.
public func == <B: BoxType> (lhs: B, rhs: B) -> Bool where B.Value: Equatable {
return lhs.value == rhs.value
}
/// Inequality of `BoxType`s of `Equatable` types.
///
/// We cannot declare that e.g. `Box<T: Equatable>` conforms to `Equatable`, so this is a relatively ad hoc definition.
public func != <B: BoxType> (lhs: B, rhs: B) -> Bool where B.Value: Equatable {
return lhs.value != rhs.value
}
// MARK: Map
/// Maps the value of a box into a new box.
public func map<B: BoxType, C: BoxType>(_ v: B, f: (B.Value) -> C.Value) -> C {
return C(f(v.value))
}

21
mobile/ios/ThirdParty/Box/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Rob Rix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,27 @@
// Copyright (c) 2014 Rob Rix. All rights reserved.
/// Wraps a type `T` in a mutable reference type.
///
/// While this, like `Box<T>` could be used to work around limitations of value types, it is much more useful for sharing a single mutable value such that mutations are shared.
///
/// As with all mutable state, this should be used carefully, for example as an optimization, rather than a default design choice. Most of the time, `Box<T>` will suffice where any `BoxType` is needed.
public final class MutableBox<T>: MutableBoxType, CustomStringConvertible {
/// Initializes a `MutableBox` with the given value.
public init(_ value: T) {
self.value = value
}
/// The (mutable) value wrapped by the receiver.
public var value: T
/// Constructs a new MutableBox by transforming `value` by `f`.
public func map<U>(_ f: (T) -> U) -> MutableBox<U> {
return MutableBox<U>(f(value))
}
// MARK: Printable
public var description: String {
return String(describing: value)
}
}

58
mobile/ios/ThirdParty/Box/README.md vendored Normal file
View file

@ -0,0 +1,58 @@
# Box
This is a Swift microframework which implements `Box<T>` & `MutableBox<T>`, with implementations of `==`/`!=` where `T`: `Equatable`.
`Box` is typically used to work around limitations of value types:
- recursive `struct`s/`enum`s
- type-parameterized `enum`s where more than one `case` has a value
## Use
Wrapping & unwrapping a `Box`:
```swift
// Wrap:
let box = Box(1)
// Unwrap:
let value = box.value
```
Changing the value of a `MutableBox`:
```swift
// Mutation:
let mutableBox = MutableBox(1)
mutableBox.value = 2
```
Building a recursive value type:
```swift
struct BinaryTree {
let value: Int
let left: Box<BinaryTree>?
let right: Box<BinaryTree>?
}
```
Building a parameterized `enum`:
```swift
enum Result<T> {
case Success(Box<T>)
case Failure(NSError)
}
```
See the sources for more details.
## Integration
1. Add this repo as a submodule in e.g. `External/Box`:
git submodule add https://github.com/robrix/Box.git External/Box
2. Drag `Box.xcodeproj` into your `.xcworkspace`/`.xcodeproj`.
3. Add `Box.framework` to your targets `Link Binary With Libraries` build phase.
4. You may also want to add a `Copy Files` phase which copies `Box.framework` (and any other framework dependencies you need) into your bundles `Frameworks` directory. If your target is a framework, you may instead want the client app to include `Box.framework`.

View file

@ -0,0 +1,147 @@
// Copyright (c) 2015 Doe Pics Hit, Inc. All rights reserved.
#import <Foundation/Foundation.h>
#import <UIKit/UIApplication.h>
typedef NSString*(^BBReturnNSStringCallback)();
typedef BOOL (^BBReturnBooleanCallback)();
typedef void (^BBCallback)();
@interface BuddyBuildSDK : NSObject
// Deprecated
+ (void)setup:(id<UIApplicationDelegate>)bbAppDelegate;
/**
* Initialize the SDK
*
* This should be called at (or near) the start of the appdelegate
*/
+ (void)setup;
/*
* Associate arbitrary key/value pairs with your crash reports
* which will be visible from the buddybuild dashboard
*/
+ (void) setCrashMetadataObject:(id)object forKey:(NSString*)key;
/*
* Programatically trigger the screenshot feedback UI without pressing the screenshot buttons
* If you have screenshot feedback disabled through the buddybuild setting,
* you can still trigger it by calling this method
*/
+ (void)takeScreenshotAndShowFeedbackScreen;
/*
* If you distribute a build to someone with their email address, buddybuild can
* figure out who they are and attach their info to feedback and crash reports.
*
* However, if you send out a build to a mailing list, or through TestFlight or
* the App Store we are unable to infer who they are. If you see 'Unknown User'
* this is likely the cause.
* Often you'll know the identity of your user, for example, after they've
* logged in. You can provide buddybuild a callback to identify the current user.
*/
+ (void)setUserDisplayNameCallback:(BBReturnNSStringCallback)bbCallback;
/*
* You might have API keys and other secrets that your app needs to consume.
* However, you may not want to check these secrets into the source code.
*
* You can provide your secrets to buddybuild. Buddybuild can then expose them
* to you at build time through environment variables. These secrets can also be
* configured to be included into built app. We obfuscate the device keys to
* prevent unauthorized access.
*/
+ (NSString*)valueForDeviceKey:(NSString*)bbKey;
/*
* To temporarily disable screenshot interception you can provide a callback
* here.
*
* When screenshotting is turned on through a buddybuild setting, and no
* callback is provided then screenshotting is by default on.
*
* If screenshotting is disabled through the buddybuild setting, then this
* callback has no effect
*
*/
+ (void)setScreenshotAllowedCallback:(BBReturnBooleanCallback)bbCallback;
/*
* Once a piece of feedback is sent this callback will be called
* so you can take additional actions if necessary
*/
+ (void)setScreenshotFeedbackSentCallback:(BBCallback)bbCallback;
/*
* Once a crash report is sent this callback will be called
* so you can take additional actions if necessary
*/
+ (void)setCrashReportSentCallback:(BBCallback)bbCallback;
/*
* Buddybuild Build Number
*/
+ (NSString*)buildNumber;
/*
* Scheme
*/
+ (NSString*)scheme;
/*
* App ID
*/
+ (NSString*)appID;
/*
* Build ID
*/
+ (NSString*)buildID;
/*
* Build Configuration
*/
+ (NSString*)buildConfiguration;
/*
* Branch name for this build
*/
+ (NSString*)branchName;
/*
* Returns the user's email or more specifically, the email that was used to download and deploy the build.
* Returns "Unknown User" in cases where buddybuild is unable to identify the user.
* This is the same email seen in crash instances and feedbacks in the dashboard
* NOTE: To be called after [BuddyBuildSDK setup]
* this is different than the one returned in the user display name callback.
*/
+ (NSString*)userEmail;
/* Manually invoke the screenshot tutorial
* If you don't want it to appear on app launch, disable it in the
* dashboard by going to settings -> buddybuildSDK -> Feature Settings and turning off the screenshot tutorial
* You will be able to show it at any time from anywhere in your app
*/
+ (void) showScreenshotTutorial;
+(void) crash;
@end
@interface UIView (BuddyBuildSDK)
// Certain features of buddybuild involve capturing the screen (either through a static screenshot, or as a video for instant replays in crash reporting or video feedback.
// Your app may contain certain sensitive customer information that you do not want to be included in the video.
// If you set this property to be true, this view will be redacted from the screen capture and blacked out
@property (nonatomic, assign) BOOL buddybuildViewIsPrivate;
@end

View file

@ -0,0 +1 @@
d1039cf

View file

@ -0,0 +1,137 @@
// FilledPageControl
//
// Copyright (c) 2016 Kyle Zaragoza <popwarsweet@gmail.com>
// MIT License
import UIKit
@IBDesignable open class FilledPageControl: UIView {
// MARK: - PageControl
@IBInspectable open var pageCount: Int = 0 {
didSet {
updateNumberOfPages(pageCount)
}
}
@IBInspectable open var progress: CGFloat = 0 {
didSet {
updateActivePageIndicatorMasks(progress)
}
}
open var currentPage: Int {
return Int(round(progress))
}
// MARK: - Appearance
override open var tintColor: UIColor! {
didSet {
inactiveLayers.forEach() { $0.backgroundColor = tintColor.cgColor }
}
}
@IBInspectable open var inactiveRingWidth: CGFloat = 1 {
didSet {
updateActivePageIndicatorMasks(progress)
}
}
@IBInspectable open var indicatorPadding: CGFloat = 10 {
didSet {
layoutPageIndicators(inactiveLayers)
}
}
@IBInspectable open var indicatorRadius: CGFloat = 5 {
didSet {
layoutPageIndicators(inactiveLayers)
}
}
fileprivate var indicatorDiameter: CGFloat {
return indicatorRadius * 2
}
fileprivate var inactiveLayers = [CALayer]()
// MARK: - State Update
fileprivate func updateNumberOfPages(_ count: Int) {
// no need to update
guard count != inactiveLayers.count else { return }
// reset current layout
inactiveLayers.forEach() { $0.removeFromSuperlayer() }
inactiveLayers = [CALayer]()
// add layers for new page count
inactiveLayers = stride(from: 0, to:count, by:1).map() { _ in
let layer = CALayer()
layer.backgroundColor = self.tintColor.cgColor
self.layer.addSublayer(layer)
return layer
}
layoutPageIndicators(inactiveLayers)
updateActivePageIndicatorMasks(progress)
self.invalidateIntrinsicContentSize()
}
// MARK: - Layout
fileprivate func updateActivePageIndicatorMasks(_ progress: CGFloat) {
// ignore if progress is outside of page indicators' bounds
guard progress >= 0 && progress <= CGFloat(pageCount - 1) else { return }
// mask rect w/ default stroke width
let insetRect = CGRect(x: 0, y: 0, width: indicatorDiameter, height: indicatorDiameter).insetBy(dx: inactiveRingWidth, dy: inactiveRingWidth)
let leftPageFloat = trunc(progress)
let leftPageInt = Int(progress)
// inset right moving page indicator
let spaceToMove = insetRect.width / 2
let percentPastLeftIndicator = progress - leftPageFloat
let additionalSpaceToInsetRight = spaceToMove * percentPastLeftIndicator
let closestRightInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetRight, dy: additionalSpaceToInsetRight)
// inset left moving page indicator
let additionalSpaceToInsetLeft = (1 - percentPastLeftIndicator) * spaceToMove
let closestLeftInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetLeft, dy: additionalSpaceToInsetLeft)
// adjust masks
for (idx, layer) in inactiveLayers.enumerated() {
let maskLayer = CAShapeLayer()
maskLayer.fillRule = kCAFillRuleEvenOdd
let boundsPath = UIBezierPath(rect: layer.bounds)
let circlePath: UIBezierPath
if leftPageInt == idx {
circlePath = UIBezierPath(ovalIn: closestLeftInsetRect)
} else if leftPageInt + 1 == idx {
circlePath = UIBezierPath(ovalIn: closestRightInsetRect)
} else {
circlePath = UIBezierPath(ovalIn: insetRect)
}
boundsPath.append(circlePath)
maskLayer.path = boundsPath.cgPath
layer.mask = maskLayer
}
}
fileprivate func layoutPageIndicators(_ layers: [CALayer]) {
let layerDiameter = indicatorRadius * 2
var layerFrame = CGRect(x: 0, y: 0, width: layerDiameter, height: layerDiameter)
layers.forEach() { layer in
layer.cornerRadius = self.indicatorRadius
layer.frame = layerFrame
layerFrame.origin.x += layerDiameter + indicatorPadding
}
}
override open var intrinsicContentSize: CGSize {
return sizeThatFits(CGSize.zero)
}
override open func sizeThatFits(_ size: CGSize) -> CGSize {
let layerDiameter = indicatorRadius * 2
return CGSize(width: CGFloat(inactiveLayers.count) * layerDiameter + CGFloat(inactiveLayers.count - 1) * indicatorPadding,
height: layerDiameter)
}
}

View file

@ -0,0 +1,193 @@
//
// LPInbox.h
// Leanplum
//
// Created by Aleksandar Gyorev on 05/08/15.
// Copyright (c) 2015 Leanplum, Inc. All rights reserved.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#import <Foundation/Foundation.h>
#pragma mark - LPInboxMessage interface
@interface LPInboxMessage : NSObject <NSCoding>
#pragma mark - LPInboxMessage methods
/**
* Returns the message identifier of the inbox message.
*/
- (NSString *)messageId;
/**
* Returns the title of the inbox message.
*/
- (NSString *)title;
/**
* Returns the subtitle of the inbox message.
*/
- (NSString *)subtitle;
/**
* Returns the image path of the inbox message. Can be nil.
* Use with [UIImage contentsOfFile:].
*/
- (NSString *)imageFilePath;
/**
* Returns the image URL of the inbox message.
* You can safely use this with prefetching enabled.
* It will return the file URL path instead if the image is in cache.
*/
- (NSURL *)imageURL;
/**
* Returns the data of the inbox message. Advanced use only.
*/
- (NSDictionary *)data;
/**
* Returns the delivery timestamp of the inbox message.
*/
- (NSDate *)deliveryTimestamp;
/**
* Return the expiration timestamp of the inbox message.
*/
- (NSDate *)expirationTimestamp;
/**
* Returns YES if the inbox message is read.
*/
- (BOOL)isRead;
/**
* Read the inbox message, marking it as read and invoking its open action.
*/
- (void)read;
/**
* Remove the inbox message from the inbox.
*/
- (void)remove;
@end
#pragma mark - LPInbox interface
/**
* This block is used when you define a callback.
*/
typedef void (^LeanplumInboxChangedBlock)(void);
typedef void (^LeanplumInboxSyncedBlock)(BOOL success);
@interface LPInbox : NSObject
#pragma mark - LPInbox methods
/**
* Returns the number of all inbox messages on the device.
*/
- (NSUInteger)count;
/**
* Returns the number of the unread inbox messages on the device.
*/
- (NSUInteger)unreadCount;
/**
* Returns the identifiers of all inbox messages on the device sorted in ascending
* chronological order, i.e. the id of the oldest message is the first one, and the most
* recent one is the last one in the array.
*/
- (NSArray *)messagesIds;
/**
* Returns an array containing all of the inbox messages (as LPInboxMessage objects)
* on the device, sorted in ascending chronological order, i.e. the oldest message is the
* first one, and the most recent one is the last one in the array.
*/
- (NSArray *)allMessages;
/**
* Returns an array containing all of the unread inbox messages on the device, sorted
* in ascending chronological order, i.e. the oldest message is the first one, and the
* most recent one is the last one in the array.
*/
- (NSArray *)unreadMessages;
/**
* Returns the inbox messages associated with the given messageId identifier.
*/
- (LPInboxMessage *)messageForId:(NSString *)messageId;
/**
* Call this method if you don't want Inbox images to be prefetched.
* Useful if you only want to deal with image URL.
*/
- (void)disableImagePrefetching;
/**
* Block to call when the inbox receive new values from the server.
* This will be called on start, and also later on if the user is in an experiment
* that can update in realtime.
*/
- (void)onChanged:(LeanplumInboxChangedBlock)block;
/**
* Block to call when forceContentUpdate was called.
* Returns true if syncing was successful.
* Note: use onChanged: for UI.
*/
- (void)onForceContentUpdate:(LeanplumInboxSyncedBlock)block;
/**
@{
* Adds a responder to be executed when an event happens.
* Uses NSInvocation instead of blocks.
* @see [Leanplum onStartResponse:]
*/
- (void)addInboxChangedResponder:(id)responder withSelector:(SEL)selector;
- (void)removeInboxChangedResponder:(id)responder withSelector:(SEL)selector;
/**@}*/
@end
#pragma mark - LPNewsfeed for backwards compatibility
@interface LPNewsfeedMessage : LPInboxMessage
@end
typedef void (^LeanplumNewsfeedChangedBlock)(void);
@interface LPNewsfeed : NSObject
+ (LPNewsfeed *)sharedState;
- (NSUInteger)count;
- (NSUInteger)unreadCount;
- (NSArray *)messagesIds;
- (NSArray *)allMessages;
- (NSArray *)unreadMessages;
- (void)onChanged:(LeanplumNewsfeedChangedBlock)block;
- (LPNewsfeedMessage *)messageForId:(NSString *)messageId;
- (void)addNewsfeedChangedResponder:(id)responder withSelector:(SEL)selector __attribute__((deprecated));
- (void)removeNewsfeedChangedResponder:(id)responder withSelector:(SEL)selector __attribute__((deprecated));
@end

View file

@ -0,0 +1,929 @@
//
// Leanplum.h
// Leanplum iOS SDK Version 2.0.4
//
// Copyright (c) 2012 Leanplum, Inc. All rights reserved.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "LPInbox.h"
#ifndef LP_NOT_TV
#define LP_NOT_TV (!defined(TARGET_OS_TV) || !TARGET_OS_TV)
#endif
#define _LP_DEFINE_HELPER(name,val,type) LPVar* name; \
static void __attribute__((constructor)) initialize_##name() { \
@autoreleasepool { \
name = [LPVar define:[@#name stringByReplacingOccurrencesOfString:@"_" withString:@"."] with##type:val]; \
} \
}
/**
* @defgroup Macros Variable Macros
* Use these macros to define variables inside your app.
* Underscores within variable names will nest variables within groups.
* To define variables in a more custom way, copy and modify
* the template above in your own code.
* @see LPVar
* @{
*/
#define DEFINE_VAR_INT(name,val) _LP_DEFINE_HELPER(name, val, Int)
#define DEFINE_VAR_BOOL(name,val) _LP_DEFINE_HELPER(name, val, Bool)
#define DEFINE_VAR_STRING(name,val) _LP_DEFINE_HELPER(name, val, String)
#define DEFINE_VAR_NUMBER(name,val) _LP_DEFINE_HELPER(name, val, Number)
#define DEFINE_VAR_FLOAT(name,val) _LP_DEFINE_HELPER(name, val, Float)
#define DEFINE_VAR_CGFLOAT(name,val) _LP_DEFINE_HELPER(name, val, CGFloat)
#define DEFINE_VAR_DOUBLE(name,val) _LP_DEFINE_HELPER(name, val, Double)
#define DEFINE_VAR_SHORT(name,val) _LP_DEFINE_HELPER(name, val, Short)
#define DEFINE_VAR_LONG(name,val) _LP_DEFINE_HELPER(name, val, Long)
#define DEFINE_VAR_CHAR(name,val) _LP_DEFINE_HELPER(name, val, Char)
#define DEFINE_VAR_LONG_LONG(name,val) _LP_DEFINE_HELPER(name, val, LongLong)
#define DEFINE_VAR_INTEGER(name,val) _LP_DEFINE_HELPER(name, val, Integer)
#define DEFINE_VAR_UINT(name,val) _LP_DEFINE_HELPER(name, val, UnsignedInt)
#define DEFINE_VAR_UCHAR(name,val) _LP_DEFINE_HELPER(name, val, UnsignedChar)
#define DEFINE_VAR_ULONG(name,val) _LP_DEFINE_HELPER(name, val, UnsignedLong)
#define DEFINE_VAR_UINTEGER(name,val) _LP_DEFINE_HELPER(name, val, UnsignedInteger)
#define DEFINE_VAR_USHORT(name,val) _LP_DEFINE_HELPER(name, val, UnsignedShort)
#define DEFINE_VAR_ULONGLONG(name,val) _LP_DEFINE_HELPER(name, val, UnsignedLongLong)
#define DEFINE_VAR_UNSIGNED_INT(name,val) _LP_DEFINE_HELPER(name, val, UnsignedInt)
#define DEFINE_VAR_UNSIGNED_INTEGER(name,val) _LP_DEFINE_HELPER(name, val, UnsignedInteger)
#define DEFINE_VAR_UNSIGNED_CHAR(name,val) _LP_DEFINE_HELPER(name, val, UnsignedChar)
#define DEFINE_VAR_UNSIGNED_LONG(name,val) _LP_DEFINE_HELPER(name, val, UnsignedLong)
#define DEFINE_VAR_UNSIGNED_LONG_LONG(name,val) _LP_DEFINE_HELPER(name, val, UnsignedLongLong)
#define DEFINE_VAR_UNSIGNED_SHORT(name,val) _LP_DEFINE_HELPER(name, val, UnsignedShort)
#define DEFINE_VAR_FILE(name,filename) _LP_DEFINE_HELPER(name, filename, File)
#define DEFINE_VAR_DICTIONARY(name,dict) _LP_DEFINE_HELPER(name, dict, Dictionary)
#define DEFINE_VAR_ARRAY(name,array) _LP_DEFINE_HELPER(name, array, Array)
#define DEFINE_VAR_COLOR(name,val) _LP_DEFINE_HELPER(name, val, Color)
#define DEFINE_VAR_DICTIONARY_WITH_OBJECTS_AND_KEYS(name,...) LPVar* name; \
static void __attribute__((constructor)) initialize_##name() { \
@autoreleasepool { \
name = [LPVar define:[@#name stringByReplacingOccurrencesOfString:@"_" withString:@"."] withDictionary:[NSDictionary dictionaryWithObjectsAndKeys:__VA_ARGS__]]; \
} \
}
#define DEFINE_VAR_ARRAY_WITH_OBJECTS(name,...) LPVar* name; \
static void __attribute__((constructor)) initialize_##name() { \
@autoreleasepool { \
name = [LPVar define:[@#name stringByReplacingOccurrencesOfString:@"_" withString:@"."] withArray:[NSArray arrayWithObjects:__VA_ARGS__]]; \
} \
}
/**@}*/
/**
* Use this code in development mode (or in production), to use the advertising ID.
* It's useful in development mode so that we remember your device even if you reinstall your app.
* Since it's a MACRO, this won't get compiled into your app in production, and will be safe
* to submit to Apple.
*/
#define LEANPLUM_USE_ADVERTISING_ID \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
id LeanplumIdentifierManager = [NSClassFromString(@"ASIdentifierManager") \
performSelector:NSSelectorFromString(@"sharedManager")]; \
if (floor(NSFoundationVersionNumber) <= 1299 /* NSFoundationVersionNumber_iOS_9_x_Max */ || \
[LeanplumIdentifierManager performSelector: \
NSSelectorFromString(@"isAdvertisingTrackingEnabled")]) { \
/* < iOS10 || isAdvertisingTrackingEnabled */ \
[Leanplum setDeviceId:[[LeanplumIdentifierManager performSelector: \
NSSelectorFromString(@"advertisingIdentifier")] \
performSelector:NSSelectorFromString(@"UUIDString")]]; \
} \
_Pragma("clang diagnostic pop")
@class LPActionContext;
@class SKPaymentTransaction;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
@class NSExtensionContext;
#endif
/**
* @defgroup _ Callback Blocks
* Those blocks are used when you define callbacks.
* @{
*/
typedef void (^LeanplumStartBlock)(BOOL success);
typedef void (^LeanplumVariablesChangedBlock)(void);
typedef void (^LeanplumInterfaceChangedBlock)(void);
typedef void (^LeanplumSetLocationBlock)(BOOL success);
// Returns whether the action was handled.
typedef BOOL (^LeanplumActionBlock)(LPActionContext* context);
typedef void (^LeanplumHandleNotificationBlock)(void);
typedef void (^LeanplumShouldHandleNotificationBlock)(NSDictionary *userInfo, LeanplumHandleNotificationBlock response);
typedef NSUInteger LeanplumUIBackgroundFetchResult; // UIBackgroundFetchResult
typedef void (^LeanplumFetchCompletionBlock)(LeanplumUIBackgroundFetchResult result);
typedef void (^LeanplumPushSetupBlock)(void);
/**@}*/
/**
* Leanplum Action Kind Message types
* This is a bit-field. To choose both kinds, use
* kLeanplumActionKindMessage | kLeanplumActionKindAction
*/
typedef enum {
kLeanplumActionKindMessage = 0b1,
kLeanplumActionKindAction = 0b10,
} LeanplumActionKind;
#define LP_PURCHASE_EVENT @"Purchase"
@interface Leanplum : NSObject
/**
* Optional. Sets the API server. The API path is of the form http[s]://hostname/servletName
* @param hostName The name of the API host, such as api.leanplum.com
* @param servletName The name of the API servlet, such as api
* @param ssl Whether to use SSL
*/
+ (void)setApiHostName:(NSString *)hostName withServletName:(NSString *)servletName usingSsl:(BOOL)ssl;
/**
* Optional. Adjusts the network timeouts.
* The default timeout is 10 seconds for requests, and 15 seconds for file downloads.
* @{
*/
+ (void)setNetworkTimeoutSeconds:(int)seconds;
+ (void)setNetworkTimeoutSeconds:(int)seconds forDownloads:(int)downloadSeconds;
/**@}*/
/**
* Sets whether to show the network activity indicator in the status bar when making requests.
* Default: YES.
*/
+ (void)setNetworkActivityIndicatorEnabled:(BOOL)enabled;
/**
* Advanced: Whether new variables can be downloaded mid-session. By default, this is disabled.
* Currently, if this is enabled, new variables can only be downloaded if a push notification is sent
* while the app is running, and the notification's metadata hasn't be downloaded yet.
*/
+ (void)setCanDownloadContentMidSessionInProductionMode:(BOOL)value;
/**
* Modifies the file hashing setting in development mode.
* By default, Leanplum will hash file variables to determine if they're modified and need
* to be uploaded to the server if we're running in the simulator.
* Setting this to NO will reduce startup latency in development mode, but it's possible
* that Leanplum will not always have the most up-to-date versions of your resources.
*/
+ (void)setFileHashingEnabledInDevelopmentMode:(BOOL)enabled;
/**
* Sets whether to enable verbose logging in development mode. Default: NO.
*/
+ (void)setVerboseLoggingInDevelopmentMode:(BOOL)enabled;
/**
* Sets a custom event name for in-app purchase tracking. Default: Purchase.
*/
+ (void)setInAppPurchaseEventName:(NSString *)event;
/**
* @{
* Must call either this or {@link setAppId:withProductionKey:}
* before issuing any calls to the API, including start.
* @param appId Your app ID.
* @param accessKey Your development key.
*/
+ (void)setAppId:(NSString *)appId withDevelopmentKey:(NSString *)accessKey;
/**
* Must call either this or {@link Leanplum::setAppId:withDevelopmentKey:}
* before issuing any calls to the API, including start.
* @param appId Your app ID.
* @param accessKey Your production key.
*/
+ (void)setAppId:(NSString *)appId withProductionKey:(NSString *)accessKey;
/**@}*/
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
/**
* Apps running as extensions need to call this before start.
* @param context The current extensionContext. You can get this from UIViewController.
*/
+ (void)setExtensionContext:(NSExtensionContext *)context;
#endif
/**
* @{
* Call this before start to allow your interfaces to change on the fly.
* Needed in development mode to enable the interface editor, as well as in production to allow
* changes to be applied.
*/
+ (void)allowInterfaceEditing __attribute__((deprecated("Use LeanplumUIEditor pod instead.")));
/**
* Check if interface editing is enabled.
*/
+ (BOOL)interfaceEditingEnabled __attribute__((deprecated("Use LeanplumUIEditor pod instead.")));
/**@}*/
/**
* Sets a custom device ID. For example, you may want to pass the advertising ID to do attribution.
* By default, the device ID is the identifier for vendor.
*/
+ (void)setDeviceId:(NSString *)deviceId;
/**
* By default, Leanplum reports the version of your app using CFBundleVersion, which
* can be used for reporting and targeting on the Leanplum dashboard.
* If you wish to use CFBundleShortVersionString or any other string as the version,
* you can call this before your call to [Leanplum start]
*/
+ (void)setAppVersion:(NSString *)appVersion;
/**
* @{
* Syncs resources between Leanplum and the current app.
* You should only call this once, and before {@link start}.
* Deprecated. Use {@link syncResourcesAsync:} instead.
*/
+ (void)syncResources __attribute__((deprecated));
/**
* Syncs resources between Leanplum and the current app.
* You should only call this once, and before {@link start}.
* @param async Whether the call should be asynchronous. Resource syncing can take 1-2 seconds to
* index the app's resources. If async is set, resources may not be available immediately
* when the app starts.
*/
+ (void)syncResourcesAsync:(BOOL)async;
/**
* Syncs resources between Leanplum and the current app.
* You should only call this once, and before {@link start}.
* Deprecated. Use {@link syncResourcePaths:excluding:async} instead.
* @param async Whether the call should be asynchronous. Resource syncing can take 1-2 seconds to
* index the app's resources. If async is set, resources may not be available immediately
* when the app starts.
* @param patternsToIncludeOrNil Limit paths to only those matching at least one pattern in this
* list. Supply nil to indicate no inclusion patterns. Paths are relative to the app's bundle.
* @param patternsToExcludeOrNil Exclude paths matching at least one of these patterns.
* Supply nil to indicate no exclusion patterns.
*/
+ (void)syncResourcePaths:(NSArray *)patternsToIncludeOrNil
excluding:(NSArray *)patternsToExcludeOrNil __attribute__((deprecated));
/**
* Syncs resources between Leanplum and the current app.
* You should only call this once, and before {@link start}.
* @param async Whether the call should be asynchronous. Resource syncing can take 1-2 seconds to
* index the app's resources. If async is set, resources may not be available immediately
* when the app starts.
* @param patternsToIncludeOrNil Limit paths to only those matching at least one pattern in this
* list. Supply nil to indicate no inclusion patterns. Paths are relative to the app's bundle.
* @param patternsToExcludeOrNil Exclude paths matching at least one of these patterns.
* Supply nil to indicate no exclusion patterns.
* @param async Whether the call should be asynchronous. Resource syncing can take 1-2 seconds to
* index the app's resources. If async is set, resources may not be available immediately
* when the app starts.
*/
+ (void)syncResourcePaths:(NSArray *)patternsToIncludeOrNil
excluding:(NSArray *)patternsToExcludeOrNil
async:(BOOL)async;
/**@}*/
/**
* @{
* Call this when your application starts.
* This will initiate a call to Leanplum's servers to get the values
* of the variables used in your app.
*/
+ (void)start;
+ (void)startWithResponseHandler:(LeanplumStartBlock)response;
+ (void)startWithUserAttributes:(NSDictionary *)attributes;
+ (void)startWithUserId:(NSString *)userId;
+ (void)startWithUserId:(NSString *)userId responseHandler:(LeanplumStartBlock)response;
+ (void)startWithUserId:(NSString *)userId userAttributes:(NSDictionary *)attributes;
+ (void)startWithUserId:(NSString *)userId userAttributes:(NSDictionary *)attributes
responseHandler:(LeanplumStartBlock)startResponse;
/**@}*/
/**
* @{
* Returns whether or not Leanplum has finished starting.
*/
+ (BOOL)hasStarted;
/**
* Returns whether or not Leanplum has finished starting and the device is registered
* as a developer.
*/
+ (BOOL)hasStartedAndRegisteredAsDeveloper;
/**@}*/
/**
* Block to call when the start call finishes, and variables are returned
* back from the server. Calling this multiple times will call each block
* in succession.
*/
+ (void)onStartResponse:(LeanplumStartBlock)block;
/**
* Block to call when the variables receive new values from the server.
* This will be called on start, and also later on if the user is in an experiment
* that can update in realtime.
*/
+ (void)onVariablesChanged:(LeanplumVariablesChangedBlock)block;
/**
* Block to call when the interface receive new values from the server.
* This will be called on start, and also later on if the user is in an experiment
* that can update in realtime.
*/
+ (void)onInterfaceChanged:(LeanplumInterfaceChangedBlock)block;
/**
* Block to call when no more file downloads are pending (either when
* no files needed to be downloaded or all downloads have been completed).
*/
+ (void)onVariablesChangedAndNoDownloadsPending:(LeanplumVariablesChangedBlock)block;
/**
* Block to call ONCE when no more file downloads are pending (either when
* no files needed to be downloaded or all downloads have been completed).
*/
+ (void)onceVariablesChangedAndNoDownloadsPending:(LeanplumVariablesChangedBlock)block;
/**
* @{
* Defines new action and message types to be performed at points set up on the Leanplum dashboard.
*/
+ (void)defineAction:(NSString *)name ofKind:(LeanplumActionKind)kind withArguments:(NSArray *)args;
+ (void)defineAction:(NSString *)name ofKind:(LeanplumActionKind)kind withArguments:(NSArray *)args
withOptions:(NSDictionary *)options;
+ (void)defineAction:(NSString *)name ofKind:(LeanplumActionKind)kind withArguments:(NSArray *)args
withResponder:(LeanplumActionBlock)responder;
+ (void)defineAction:(NSString *)name ofKind:(LeanplumActionKind)kind withArguments:(NSArray *)args
withOptions:(NSDictionary *)options
withResponder:(LeanplumActionBlock)responder;
/**@}*/
/**
* Block to call when an action is received, such as to show a message to the user.
*/
+ (void)onAction:(NSString *)actionName invoke:(LeanplumActionBlock)block;
/**
* Handles a push notification for apps that use Background Notifications.
* Without background notifications, Leanplum handles them automatically.
* Deprecated. Leanplum calls handleNotification automatically now. If you
* implement application:didReceiveRemoteNotification:fetchCompletionHandler:
* in your app delegate, you should remove any calls to [Leanplum handleNotification]
* and call the completion handler yourself.
*/
+ (void)handleNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(LeanplumFetchCompletionBlock)completionHandler
__attribute__((deprecated("Leanplum calls handleNotification automatically now. If you "
"implement application:didReceiveRemoteNotification:fetchCompletionHandler: in your app "
"delegate, you should remove any calls to [Leanplum handleNotification] and call the "
"completion handler yourself.")));
#if LP_NOT_TV
/**
* Call this to handle custom actions for local notifications.
*/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma clang diagnostic ignored "-Wstrict-prototypes"
+ (void)handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
completionHandler:(void (^)())completionHandler;
#pragma clang diagnostic pop
#endif
/**
* Call this to handle custom actions for remote notifications.
*/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
+ (void)handleActionWithIdentifier:(NSString *)identifier
forRemoteNotification:(NSDictionary *)notification
completionHandler:(void (^)())completionHandler;
#pragma clang diagnostic pop
/*
* Block to call that decides whether a notification should be displayed when it is
* received while the app is running, and the notification is not muted.
* Overrides the default behavior of showing an alert view with the notification message.
*/
+ (void)setShouldOpenNotificationHandler:(LeanplumShouldHandleNotificationBlock)block;
/**
* @{
* Adds a responder to be executed when an event happens.
* Similar to the methods above but uses NSInvocations instead of blocks.
* @see onStartResponse:
*/
+ (void)addStartResponseResponder:(id)responder withSelector:(SEL)selector;
+ (void)addVariablesChangedResponder:(id)responder withSelector:(SEL)selector;
+ (void)addInterfaceChangedResponder:(id)responder withSelector:(SEL)selector;
+ (void)addVariablesChangedAndNoDownloadsPendingResponder:(id)responder withSelector:(SEL)selector;
+ (void)addResponder:(id)responder withSelector:(SEL)selector forActionNamed:(NSString *)actionName;
+ (void)removeStartResponseResponder:(id)responder withSelector:(SEL)selector;
+ (void)removeVariablesChangedResponder:(id)responder withSelector:(SEL)selector;
+ (void)removeInterfaceChangedResponder:(id)responder withSelector:(SEL)selector;
+ (void)removeVariablesChangedAndNoDownloadsPendingResponder:(id)responder withSelector:(SEL)selector;
+ (void)removeResponder:(id)responder withSelector:(SEL)selector forActionNamed:(NSString *)actionName;
/**@}*/
/**
* Sets additional user attributes after the session has started.
* Variables retrieved by start won't be targeted based on these attributes, but
* they will count for the current session for reporting purposes.
* Only those attributes given in the dictionary will be updated. All other
* attributes will be preserved.
*/
+ (void)setUserAttributes:(NSDictionary *)attributes;
/**
* Updates a user ID after session start.
*/
+ (void)setUserId:(NSString *)userId;
/**
* Updates a user ID after session start with a dictionary of user attributes.
*/
+ (void)setUserId:(NSString *)userId withUserAttributes:(NSDictionary *)attributes;
/**
* Sets the traffic source info for the current user.
* Keys in info must be one of: publisherId, publisherName, publisherSubPublisher,
* publisherSubSite, publisherSubCampaign, publisherSubAdGroup, publisherSubAd.
*/
+ (void)setTrafficSourceInfo:(NSDictionary *)info;
/**
* @{
* Advances to a particular state in your application. The string can be
* any value of your choosing, and will show up in the dashboard.
* A state is a section of your app that the user is currently in.
* @param state The name of the state.
*/
+ (void)advanceTo:(NSString *)state;
/**
* Advances to a particular state in your application. The string can be
* any value of your choosing, and will show up in the dashboard.
* A state is a section of your app that the user is currently in.
* @param state The name of the state.
* @param info Anything else you want to log with the state. For example, if the state
* is watchVideo, info could be the video ID.
*/
+ (void)advanceTo:(NSString *)state withInfo:(NSString *)info;
/**
* Advances to a particular state in your application. The string can be
* any value of your choosing, and will show up in the dashboard.
* A state is a section of your app that the user is currently in.
* You can specify up to 200 types of parameters per app across all events and state.
* The parameter keys must be strings, and values either strings or numbers.
* @param state The name of the state.
* @param params A dictionary with custom parameters.
*/
+ (void)advanceTo:(NSString *)state withParameters:(NSDictionary *)params;
/**
* Advances to a particular state in your application. The string can be
* any value of your choosing, and will show up in the dashboard.
* A state is a section of your app that the user is currently in.
* You can specify up to 200 types of parameters per app across all events and state.
* The parameter keys must be strings, and values either strings or numbers.
* @param state The name of the state. (nullable)
* @param info Anything else you want to log with the state. For example, if the state
* is watchVideo, info could be the video ID.
* @param params A dictionary with custom parameters.
*/
+ (void)advanceTo:(NSString *)state withInfo:(NSString *)info andParameters:(NSDictionary *)params;
/**
* Pauses the current state.
* You can use this if your game has a "pause" mode. You shouldn't call it
* when someone switches out of your app because that's done automatically.
*/
+ (void)pauseState;
/**
* Resumes the current state.
*/
+ (void)resumeState;
/**
* Automatically tracks all of the screens in the app as states.
* You should not use this in conjunction with advanceTo as the user can only be in
* 1 state at a time. This method requires LeanplumUIEditor module.
*/
+ (void)trackAllAppScreens;
/**
* LPTrackScreenMode enum.
* LPTrackScreenModeDefault mans that states are the full view controller type name.
* LPTrackScreenModeStripViewController will cause the string "ViewController" to be stripped from
* the end of the state.
*/
typedef NS_ENUM(NSUInteger, LPTrackScreenMode) {
LPTrackScreenModeDefault = 0,
LPTrackScreenModeStripViewController
};
/**
* Automatically tracks all of the screens in the app as states.
* You should not use this in conjunction with advanceTo as the user can only be in
* 1 state at a time. This method requires LeanplumUIEditor module.
* @param trackScreenMode Choose mode for display. Default is the view controller type name.
*/
+ (void)trackAllAppScreensWithMode:(LPTrackScreenMode)trackScreenMode;
/**
* Manually track purchase event with currency code in your application. It is advised to use
* trackInAppPurchases to automatically track IAPs.
*/
+ (void)trackPurchase:(NSString *)event withValue:(double)value
andCurrencyCode:(NSString *)currencyCode andParameters:(NSDictionary *)params;
/**
* Automatically tracks InApp purchase and does server side receipt validation.
*/
+ (void)trackInAppPurchases;
/**
* Manually tracks InApp purchase and does server side receipt validation.
*/
+ (void)trackInAppPurchase:(SKPaymentTransaction *)transaction;
/**@}*/
/**
* @{
* Logs a particular event in your application. The string can be
* any value of your choosing, and will show up in the dashboard.
* To track a purchase, use LP_PURCHASE_EVENT.
*/
+ (void)track:(NSString *)event;
+ (void)track:(NSString *)event withValue:(double)value;
+ (void)track:(NSString *)event withInfo:(NSString *)info;
+ (void)track:(NSString *)event withValue:(double)value andInfo:(NSString *)info;
// See above for the explanation of params.
+ (void)track:(NSString *)event withParameters:(NSDictionary *)params;
+ (void)track:(NSString *)event withValue:(double)value andParameters:(NSDictionary *)params;
+ (void)track:(NSString *)event withValue:(double)value andInfo:(NSString *)info andParameters:(NSDictionary *)params;
/**@}*/
/**
* @{
* Gets the path for a particular resource. The resource can be overridden by the server.
*/
+ (NSString *)pathForResource:(NSString *)name ofType:(NSString *)extension;
+ (id)objectForKeyPath:(id)firstComponent, ... NS_REQUIRES_NIL_TERMINATION;
+ (id)objectForKeyPathComponents:(NSArray *)pathComponents;
/**@}*/
/**
* Gets a list of variants that are currently active for this user.
* Each variant is a dictionary containing an id.
*/
+ (NSArray *)variants;
/**
* Returns metadata for all active in-app messages.
* Recommended only for debugging purposes and advanced use cases.
*/
+ (NSDictionary *)messageMetadata;
/**
* Forces content to update from the server. If variables have changed, the
* appropriate callbacks will fire. Use sparingly as if the app is updated,
* you'll have to deal with potentially inconsistent state or user experience.
*/
+ (void)forceContentUpdate;
/**
* Forces content to update from the server. If variables have changed, the
* appropriate callbacks will fire. Use sparingly as if the app is updated,
* you'll have to deal with potentially inconsistent state or user experience.
* The provided callback will always fire regardless
* of whether the variables have changed.
*/
+ (void)forceContentUpdate:(LeanplumVariablesChangedBlock)block;
/**
* This should be your first statement in a unit test. This prevents
* Leanplum from communicating with the server.
*/
+ (void)enableTestMode;
/**
* Used to enable or disable test mode. Test mode prevents Leanplum from
* communicating with the server. This is useful for unit tests.
*/
+ (void)setTestModeEnabled:(BOOL)isTestModeEnabled;
/**
* Customize push setup. If this API should be called before [Leanplum start]. If this API is not
* used the default push setup from the docs will be used for "Push Ask to Ask" and
* "Register For Push".
*/
+ (void)setPushSetup:(LeanplumPushSetupBlock)block;
/**
* Get the push setup block.
*/
+ (LeanplumPushSetupBlock)pushSetupBlock;
/**
* Returns YES if the app existed on the device more than a day previous to a version built with
* Leanplum was installed.
*/
+ (BOOL)isPreLeanplumInstall;
/**
* Returns the deviceId in the current Leanplum session. This should only be called after
* [Leanplum start].
*/
+ (NSString *)deviceId;
/**
* Returns the userId in the current Leanplum session. This should only be called after
* [Leanplum start].
*/
+ (NSString *)userId;
/**
* Returns an instance to the singleton LPInbox object.
*/
+ (LPInbox *)inbox;
/**
* Returns an instance to the singleton LPNewsfeed object.
* Deprecated. Use {@link inbox} instead.
*/
+ (LPNewsfeed *)newsfeed __attribute__((deprecated("Use inbox instead.")));
/**
* Types of location accuracy. Higher value implies better accuracy.
*/
typedef enum {
LPLocationAccuracyIP = 0,
LPLocationAccuracyCELL = 1,
LPLocationAccuracyGPS = 2
} LPLocationAccuracyType;
/**
* Set location manually. Calls setDeviceLocationWithLatitude:longitude:type: with cell type.
* Best if used in after calling setDeviceLocationWithLatitude:.
*/
+ (void)setDeviceLocationWithLatitude:(double)latitude
longitude:(double)longitude;
/**
* Set location manually. Best if used in after calling setDeviceLocationWithLatitude:.
* Useful if you want to apply additional logic before sending in the location.
*/
+ (void)setDeviceLocationWithLatitude:(double)latitude
longitude:(double)longitude
type:(LPLocationAccuracyType)type;
/**
* Set location manually. Best if used in after calling setDeviceLocationWithLatitude:.
* If you have the CLPlacemark info: city is locality, region is administrativeArea,
* and country is ISOcountryCode.
*/
+ (void)setDeviceLocationWithLatitude:(double)latitude
longitude:(double)longitude
city:(NSString *)city
region:(NSString *)region
country:(NSString *)country
type:(LPLocationAccuracyType)type;
/**
* Disables collecting location automatically. Will do nothing if Leanplum-Location is not used.
*/
+ (void)disableLocationCollection;
@end
@interface LeanplumCompatibility : NSObject
/**
* Used only for compatibility with Google Analytics.
*/
+ (void)gaTrack:(NSObject *)trackingObject;
@end
@class LPVar;
/**
* Receives callbacks for {@link LPVar}
*/
@protocol LPVarDelegate <NSObject>
@optional
/**
* For file variables, called when the file is ready.
*/
- (void)fileIsReady:(LPVar *)var;
/**
* Called when the value of the variable changes.
*/
- (void)valueDidChange:(LPVar *)var;
@end
/**
* A variable is any part of your application that can change from an experiment.
* Check out {@link Macros the macros} for defining variables more easily.
*/
@interface LPVar : NSObject
/**
* @{
* Defines a {@link LPVar}
*/
+ (LPVar *)define:(NSString *)name;
+ (LPVar *)define:(NSString *)name withInt:(int)defaultValue;
+ (LPVar *)define:(NSString *)name withFloat:(float)defaultValue;
+ (LPVar *)define:(NSString *)name withDouble:(double)defaultValue;
+ (LPVar *)define:(NSString *)name withCGFloat:(CGFloat)cgFloatValue;
+ (LPVar *)define:(NSString *)name withShort:(short)defaultValue;
+ (LPVar *)define:(NSString *)name withChar:(char)defaultValue;
+ (LPVar *)define:(NSString *)name withBool:(BOOL)defaultValue;
+ (LPVar *)define:(NSString *)name withString:(NSString *)defaultValue;
+ (LPVar *)define:(NSString *)name withNumber:(NSNumber *)defaultValue;
+ (LPVar *)define:(NSString *)name withInteger:(NSInteger)defaultValue;
+ (LPVar *)define:(NSString *)name withLong:(long)defaultValue;
+ (LPVar *)define:(NSString *)name withLongLong:(long long)defaultValue;
+ (LPVar *)define:(NSString *)name withUnsignedChar:(unsigned char)defaultValue;
+ (LPVar *)define:(NSString *)name withUnsignedInt:(unsigned int)defaultValue;
+ (LPVar *)define:(NSString *)name withUnsignedInteger:(NSUInteger)defaultValue;
+ (LPVar *)define:(NSString *)name withUnsignedLong:(unsigned long)defaultValue;
+ (LPVar *)define:(NSString *)name withUnsignedLongLong:(unsigned long long)defaultValue;
+ (LPVar *)define:(NSString *)name withUnsignedShort:(unsigned short)defaultValue;
+ (LPVar *)define:(NSString *)name withFile:(NSString *)defaultFilename;
+ (LPVar *)define:(NSString *)name withDictionary:(NSDictionary *)defaultValue;
+ (LPVar *)define:(NSString *)name withArray:(NSArray *)defaultValue;
+ (LPVar *)define:(NSString *)name withColor:(UIColor *)defaultValue;
/**@}*/
/**
* Returns the name of the variable.
*/
- (NSString *)name;
/**
* Returns the components of the variable's name.
*/
- (NSArray *)nameComponents;
/**
* Returns the default value of a variable.
*/
- (id)defaultValue;
/**
* Returns the kind of the variable.
*/
- (NSString *)kind;
/**
* Returns whether the variable has changed since the last time the app was run.
*/
- (BOOL)hasChanged;
/**
* For file variables, called when the file is ready.
*/
- (void)onFileReady:(LeanplumVariablesChangedBlock)block;
/**
* Called when the value of the variable changes.
*/
- (void)onValueChanged:(LeanplumVariablesChangedBlock)block;
/**
* Sets the delegate of the variable in order to use
* {@link LPVarDelegate::fileIsReady:} and {@link LPVarDelegate::valueDidChange:}
*/
- (void)setDelegate:(id <LPVarDelegate>)delegate;
/**
* @{
* Accessess the value(s) of the variable
*/
- (id)objectForKey:(NSString *)key;
- (id)objectAtIndex:(NSUInteger )index;
- (id)objectForKeyPath:(id)firstComponent, ... NS_REQUIRES_NIL_TERMINATION;
- (id)objectForKeyPathComponents:(NSArray *)pathComponents;
- (NSUInteger)count;
- (NSNumber *)numberValue;
- (NSString *)stringValue;
- (NSString *)fileValue;
- (UIImage *)imageValue;
- (int)intValue;
- (double)doubleValue;
- (CGFloat)cgFloatValue;
- (float)floatValue;
- (short)shortValue;
- (BOOL)boolValue;
- (char)charValue;
- (long)longValue;
- (long long)longLongValue;
- (NSInteger)integerValue;
- (unsigned char)unsignedCharValue;
- (unsigned short)unsignedShortValue;
- (unsigned int)unsignedIntValue;
- (NSUInteger)unsignedIntegerValue;
- (unsigned long)unsignedLongValue;
- (unsigned long long)unsignedLongLongValue;
- (UIColor *)colorValue;
/**@}*/
@end
@interface LPActionArg : NSObject
/**
* @{
* Defines a Leanplum Action Argument
*/
+ (LPActionArg *)argNamed:(NSString *)name withNumber:(NSNumber *)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withString:(NSString *)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withBool:(BOOL)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withFile:(NSString *)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withDict:(NSDictionary *)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withArray:(NSArray *)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withAction:(NSString *)defaultValue;
+ (LPActionArg *)argNamed:(NSString *)name withColor:(UIColor *)defaultValue;
/**@}*/
- (NSString *)name;
- (NSString *)kind;
- (id)defaultValue;
@end
@interface LPActionContext : NSObject
- (NSString *)actionName;
- (NSString *)stringNamed:(NSString *)name;
- (NSString *)fileNamed:(NSString *)name;
- (NSNumber *)numberNamed:(NSString *)name;
- (BOOL)boolNamed:(NSString *)name;
- (NSDictionary *)dictionaryNamed:(NSString *)name;
- (NSArray *)arrayNamed:(NSString *)name;
- (UIColor *)colorNamed:(NSString *)name;
- (NSString *)htmlWithTemplateNamed:(NSString *)templateName;
/**
* Runs the action given by the "name" key.
*/
- (void)runActionNamed:(NSString *)name;
/**
* Runs and tracks an event for the action given by the "name" key.
* This will track an event if no action is set.
*/
- (void)runTrackedActionNamed:(NSString *)name;
/**
* Tracks an event in the context of the current message.
*/
- (void)track:(NSString *)event withValue:(double)value andParameters:(NSDictionary *)params;
/**
* Tracks an event in the conext of the current message, with any parent actions prepended to the
* message event name.
*/
- (void)trackMessageEvent:(NSString *)event
withValue:(double)value
andInfo:(NSString *)info
andParameters:(NSDictionary *)params;
/**
* Prevents the currently active message from appearing again in the future.
*/
- (void)muteFutureMessagesOfSameKind;
/**
* Checks if the action context has any missing files that still need to be downloaded.
*/
- (BOOL)hasMissingFiles;
@end

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,6 @@
framework module Leanplum {
umbrella header "Leanplum.h"
export *
module * { export * }
}

115
mobile/ios/ThirdParty/Reachability.swift vendored Normal file
View file

@ -0,0 +1,115 @@
// The MIT License (MIT)
//
// Copyright (c) 2015 Isuru Nanayakkara
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import SystemConfiguration
let ReachabilityStatusChangedNotification = "ReachabilityStatusChangedNotification"
enum ReachabilityType: CustomStringConvertible {
case wwan
case wiFi
var description: String {
switch self {
case .wwan: return "WWAN"
case .wiFi: return "WiFi"
}
}
}
enum ReachabilityStatus: CustomStringConvertible {
case offline
case online(ReachabilityType)
case unknown
var description: String {
switch self {
case .offline: return "Offline"
case .online(let type): return "Online (\(type))"
case .unknown: return "Unknown"
}
}
}
open class Reach {
func connectionStatus() -> ReachabilityStatus {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, $0)
}
}) else {
return .unknown
}
var flags : SCNetworkReachabilityFlags = []
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
return .unknown
}
return ReachabilityStatus(reachabilityFlags: flags)
}
func monitorReachabilityChanges() {
let host = "google.com"
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
let reachability = SCNetworkReachabilityCreateWithName(nil, host)!
SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in
let status = ReachabilityStatus(reachabilityFlags: flags)
NotificationCenter.default.post(name: Notification.Name(rawValue: ReachabilityStatusChangedNotification),
object: nil,
userInfo: ["Status": status.description])
}, &context)
SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue)
}
}
extension ReachabilityStatus {
fileprivate init(reachabilityFlags flags: SCNetworkReachabilityFlags) {
let connectionRequired = flags.contains(.connectionRequired)
let isReachable = flags.contains(.reachable)
let isWWAN = flags.contains(.isWWAN)
if !connectionRequired && isReachable {
if isWWAN {
self = .online(.wwan)
} else {
self = .online(.wiFi)
}
} else {
self = .offline
}
}
}

View file

@ -0,0 +1,11 @@
//
// Error.swift
// Result
//
// Created by John Gallagher on 9/12/14.
// Copyright (c) 2014 Big Nerd Ranch. All rights reserved.
//
import Foundation
public typealias MaybeErrorType = Error & CustomStringConvertible

19
mobile/ios/ThirdParty/Result/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2014 John Gallagher <jgallagher@bignerdranch.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

24
mobile/ios/ThirdParty/Result/README.md vendored Normal file
View file

@ -0,0 +1,24 @@
# Result
`Result` is a Swift framework that includes the `Result` enum and an
`ErrorType` protocol.
Both types are extremely small. I look forward to two changes in Swift's future:
* A fix for the Swift compiler issue that requires the `Success` case of
`Result` to box its `T` type somehow. This repo uses
[Box](https://github.com/robrix/Box) as a workaround.
* (Hopefully) The inclusion of these types or their moral equivalents in the
Swift standard library, at which point this repo can be removed.
## Integration
Add this repository as a submodule, or use [Carthage](https://github.com/Carthage/Carthage/).
## Author
John Gallagher, jgallagher@bignerdranch.com
## License
Deferred is available under the MIT license. See the LICENSE file for more info.

View file

@ -0,0 +1,76 @@
//
// Result.swift
// Result
//
// Created by John Gallagher on 9/12/14.
// Copyright (c) 2014 Big Nerd Ranch. All rights reserved.
//
import Foundation
//import Box
public enum Maybe<T> {
case failure(MaybeErrorType)
// TODO: Get rid of Box hack at some point after 6.3
case success(Box<T>)
public init(failure: MaybeErrorType) {
self = .failure(failure)
}
public init(success: T) {
self = .success(Box(success))
}
public var successValue: T? {
switch self {
case let .success(success): return success.value
case .failure: return nil
}
}
public var failureValue: MaybeErrorType? {
switch self {
case .success: return nil
case let .failure(error): return error
}
}
public var isSuccess: Bool {
switch self {
case .success: return true
case .failure: return false
}
}
public var isFailure: Bool {
switch self {
case .success: return false
case .failure: return true
}
}
public func map<U>(_ f: (T) -> U) -> Maybe<U> {
switch self {
case let .failure(error): return .failure(error)
case let .success(value): return .success(Box(f(value.value)))
}
}
public func bind<U>(_ f: (T) -> Maybe<U>) -> Maybe<U> {
switch self {
case let .failure(error): return .failure(error)
case let .success(value): return f(value.value)
}
}
}
extension Maybe: CustomStringConvertible {
public var description: String {
switch self {
case let .failure(error): return "Result.Failure(\(error))"
case let .success(value): return "Result.Success(\(value.value))"
}
}
}

View file

@ -0,0 +1,2 @@
additional_guides:
- Documentation/Index.md

View file

@ -0,0 +1,27 @@
# OS X
.DS_Store
# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Carthage
/Carthage/
# Swift Package Manager
.build
Packages/

View file

View file

@ -0,0 +1 @@
3.0

View file

@ -0,0 +1,26 @@
language: objective-c
rvm: 2.2
osx_image: xcode8.2
env:
global:
- IOS_SIMULATOR="iPhone 6s"
matrix:
include:
- env: BUILD_SCHEME="SQLite iOS"
- env: BUILD_SCHEME="SQLite Mac"
- env: VALIDATOR_SUBSPEC="none"
- env: VALIDATOR_SUBSPEC="standard"
- env: VALIDATOR_SUBSPEC="standalone"
- env: VALIDATOR_SUBSPEC="SQLCipher"
- env: CARTHAGE_PLATFORM="iOS"
- env: CARTHAGE_PLATFORM="Mac"
- env: CARTHAGE_PLATFORM="watchOS"
- env: CARTHAGE_PLATFORM="tvOS"
- env: PACKAGE_MANAGER_COMMAND="test -Xlinker -lsqlite3"
before_install:
- gem update bundler
- gem install xcpretty --no-document
- brew update
- brew outdated carthage || brew upgrade carthage
script:
- ./run-tests.sh

View file

@ -0,0 +1,34 @@
0.11.2 (25-12-2016), [diff][diff-0.11.2]
========================================
* Fixed SQLCipher integration with read-only databases ([#559][])
* Preliminary Swift Package Manager support ([#548][], [#560][])
* Fixed null pointer when fetching an empty BLOB ([#561][])
* Allow `where` as alias for `filter` ([#571][])
0.11.1 (06-12-2016), [diff][diff-0.11.1]
========================================
* Integrate SQLCipher via CocoaPods ([#546][], [#553][])
* Made lastInsertRowid consistent with other SQLite wrappers ([#532][])
* Fix for ~= operator used with Double ranges
* Various documentation updates
0.11.0 (19-10-2016)
===================
* Swift3 migration ([diff][diff-0.11.0])
[diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0
[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1
[diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2
[#532]: https://github.com/stephencelis/SQLite.swift/issues/532
[#546]: https://github.com/stephencelis/SQLite.swift/issues/546
[#548]: https://github.com/stephencelis/SQLite.swift/pull/548
[#553]: https://github.com/stephencelis/SQLite.swift/pull/553
[#559]: https://github.com/stephencelis/SQLite.swift/pull/559
[#560]: https://github.com/stephencelis/SQLite.swift/pull/560
[#561]: https://github.com/stephencelis/SQLite.swift/issues/561
[#571]: https://github.com/stephencelis/SQLite.swift/issues/571

View file

@ -0,0 +1,108 @@
# Contributing
The where and when to open an [issue](#issues) or [pull
request](#pull-requests).
## Issues
Issues are used to track **bugs** and **feature requests**. Need **help** or
have a **general question**? [Ask on Stack Overflow][] (tag `sqlite.swift`).
Before reporting a bug or requesting a feature, [run a few searches][Search] to
see if a similar issue has already been opened and ensure youre not submitting
a duplicate.
If you find a similar issue, read the existing conversation and see if it
addresses everything. If it doesnt, continue the conversation there.
If your searches return empty, see the [bug](#bugs) or [feature
request](#feature-requests) guidelines below.
[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift
[Search]: https://github.com/stephencelis/SQLite.swift/search?type=Issues
### Bugs
Think youve discovered a new **bug**? Lets try troubleshooting a few things
first.
- **Is it an installation issue?** <a name='bugs-1'/>
If this is your first time building SQLite.swift in your project, you may
encounter a build error, _e.g._:
No such module 'SQLite'
Please carefully re-read the [installation instructions][] to make sure
everything is in order.
- **Have you read the documentation?** <a name='bugs-2'/>
If you cant seem to get something working, check
[the documentation][See Documentation] to see if the solution is there.
- **Are you up-to-date?** <a name='bugs-3'/>
If youre perusing [the documentation][See Documentation] online and find
that an example is just not working, please upgrade to the latest version
of SQLite.swift and try again before continuing.
- **Is it an unhelpful build error?** <a name='bugs-4'/>
While Swift error messaging is improving with each release, complex
expressions still lend themselves to misleading errors. If you encounter an
error on a complex line, breaking it down into smaller pieces generally
yields a more understandable error.
- **Is it an _even more_ unhelpful build error?** <a name='bugs-5'/>
Have you updated Xcode recently? Did your project stop building out of the
blue?
Hold down the **option** key and select **Clean Build Folder…** from the
**Product** menu (⌥⇧⌘K).
Made it through everything above and still having trouble? Sorry!
[Open an issue][]! And _please_:
- Be as descriptive as possible.
- Provide as much information needed to _reliably reproduce_ the issue.
- Attach screenshots if possible.
- Better yet: attach GIFs or link to video.
- Even better: link to a sample project exhibiting the issue.
- Include the SQLite.swift commit or branch experiencing the issue.
- Include devices and operating systems affected.
- Include build information: the Xcode and OS X versions affected.
[installation instructions]: Documentation/Index.md#installation
[See Documentation]: Documentation/Index.md#sqliteswift-documentation
[Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new
### Feature Requests
Have an innovative **feature request**? [Open an issue][]! Be thorough! Provide
context and examples. Be open to discussion.
## Pull Requests
Interested in contributing but dont know where to start? Try the [`help
wanted`][help wanted] label.
Ready to submit a fix or a feature? [Submit a pull request][]! And _please_:
- If code changes, run the tests and make sure everything still works.
- Write new tests for new functionality.
- Update documentation comments where applicable.
- Maintain the existing style.
- Dont forget to have fun.
While we cannot guarantee a merge to every pull request, we do read each one
and love your input.
[help wanted]: https://github.com/stephencelis/SQLite.swift/labels/help%20wanted
[Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h"
export *
}

View file

@ -0,0 +1,4 @@
module CSQLite [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/usr/include/sqlite3.h"
export *
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
# SQLite.swift Planning
This document captures both near term steps (aka Roadmap) and feature requests.
The goal is to add some visibility and guidance for future additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible.
## Roadmap
_Lists agreed upon next steps in approximate priority order._
## Feature Requests
_A gathering point for ideas for new features. In general, the corresponding issue will be closed once it is added here, with the assumption that it will be referred to when it comes time to add the corresponding feature._
### Features
* encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per [#30](https://github.com/stephencelis/SQLite.swift/issues/30)
* provide separate threads for update vs read, so updates don't block reads, per [#236](https://github.com/stephencelis/SQLite.swift/issues/236)
* expose triggers, per [#164](https://github.com/stephencelis/SQLite.swift/issues/164)
## Suspended Feature Requests
_Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._
* provide a mechanism for INSERT INTO multiple values, per [#168](https://github.com/stephencelis/SQLite.swift/issues/168)

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View file

@ -0,0 +1,21 @@
(The MIT License)
Copyright (c) 2014-2015 Stephen Celis (<stephen@stephencelis.com>)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,60 @@
BUILD_TOOL = xcodebuild
BUILD_SCHEME = SQLite Mac
IOS_SIMULATOR = iPhone 6s
IOS_VERSION = 9.3
ifeq ($(BUILD_SCHEME),SQLite iOS)
BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)"
else
BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)"
endif
XCPRETTY := $(shell command -v xcpretty)
SWIFTCOV := $(shell command -v swiftcov)
GCOVR := $(shell command -v gcovr)
TEST_ACTIONS := clean build build-for-testing test-without-building
default: test
build:
$(BUILD_TOOL) $(BUILD_ARGUMENTS)
test:
ifdef XCPRETTY
@set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) | $(XCPRETTY) -c
else
$(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS)
endif
coverage:
ifdef SWIFTCOV
$(SWIFTCOV) generate --output coverage \
$(BUILD_TOOL) $(BUILD_ARGUMENTS) -configuration Release test \
-- ./SQLite/*.swift
ifdef GCOVR
$(GCOVR) \
--root . \
--use-gcov-files \
--html \
--html-details \
--output coverage/index.html \
--keep
else
@echo gcovr must be installed for HTML output: https://github.com/gcovr/gcovr
endif
else
@echo swiftcov must be installed for coverage: https://github.com/realm/SwiftCov
@exit 1
endif
clean:
$(BUILD_TOOL) $(BUILD_ARGUMENTS) clean
rm -r coverage
repl:
@$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \
swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug'
sloc:
@zsh -c "grep -vE '^ *//|^$$' SQLite/*/*.{swift,h,m} | wc -l"
.PHONY: test coverage clean repl sloc

View file

@ -0,0 +1,17 @@
import PackageDescription
let package = Package(
name: "SQLite",
targets: [
Target(
name: "SQLite",
dependencies: [
.Target(name: "SQLiteObjc")
]),
Target(name: "SQLiteObjc")
],
dependencies: [
.Package(url: "https://github.com/stephencelis/CSQLite.git", majorVersion: 0)
],
exclude: ["Tests/CocoaPods", "Tests/Carthage"]
)

View file

@ -0,0 +1,263 @@
# SQLite.swift
[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-3-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift)
A type-safe, [Swift][]-language layer over [SQLite3][].
[SQLite.swift][] provides compile-time confidence in SQL statement
syntax _and_ intent.
[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat
[Travis]: https://travis-ci.org/stephencelis/SQLite.swift
[Swift]: https://developer.apple.com/swift/
[SQLite3]: http://www.sqlite.org
[SQLite.swift]: https://github.com/stephencelis/SQLite.swift
## Features
- A pure-Swift interface
- A type-safe, optional-aware SQL expression builder
- A flexible, chainable, lazy-executing query layer
- Automatically-typed data access
- A lightweight, uncomplicated query and parameter binding interface
- Developer-friendly error handling and debugging
- [Full-text search][] support
- [Well-documented][See Documentation]
- Extensively tested
- SQLCipher support via CocoaPods
- Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_)
[Full-text search]: Documentation/Index.md#full-text-search
[See Documentation]: Documentation/Index.md#sqliteswift-documentation
## Usage
``` swift
import SQLite
let db = try Connection("path/to/db.sqlite3")
let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")
try db.run(users.create { t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email, unique: true)
})
// CREATE TABLE "users" (
// "id" INTEGER PRIMARY KEY NOT NULL,
// "name" TEXT,
// "email" TEXT NOT NULL UNIQUE
// )
let insert = users.insert(name <- "Alice", email <- "alice@mac.com")
let rowid = try db.run(insert)
// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com')
for user in try db.prepare(users) {
print("id: \(user[id]), name: \(user[name]), email: \(user[email])")
// id: 1, name: Optional("Alice"), email: alice@mac.com
}
// SELECT * FROM "users"
let alice = users.filter(id == rowid)
try db.run(alice.update(email <- email.replace("mac.com", with: "me.com")))
// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com')
// WHERE ("id" = 1)
try db.run(alice.delete())
// DELETE FROM "users" WHERE ("id" = 1)
db.scalar(users.count) // 0
// SELECT count(*) FROM "users"
```
SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C
API.
``` swift
let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)")
for email in ["betty@icloud.com", "cathy@icloud.com"] {
try stmt.run(email)
}
db.totalChanges // 3
db.changes // 1
db.lastInsertRowid // 3
for row in try db.prepare("SELECT id, email FROM users") {
print("id: \(row[0]), email: \(row[1])")
// id: Optional(2), email: Optional("betty@icloud.com")
// id: Optional(3), email: Optional("cathy@icloud.com")
}
db.scalar("SELECT count(*) FROM users") // 2
```
[Read the documentation][See Documentation] or explore more,
interactively, from the Xcode projects playground.
![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png)
For a more comprehensive example, see [this article](http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html) and the [companion repository](https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master).
## Installation
> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8) or greater. If you absolutely
> need compatibility with Swift 2.3 you can use the [swift-2.3][] branch or older
> released versions. New development will happen exclusively on the master/Swift 3 branch.
### Carthage
[Carthage][] is a simple, decentralized dependency manager for Cocoa. To
install SQLite.swift with Carthage:
1. Make sure Carthage is [installed][Carthage Installation].
2. Update your Cartfile to include the following:
```
github "stephencelis/SQLite.swift" ~> 0.11.2
```
3. Run `carthage update` and [add the appropriate framework][Carthage Usage].
[Carthage]: https://github.com/Carthage/Carthage
[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage
[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application
### CocoaPods
[CocoaPods][] is a dependency manager for Cocoa projects. To install
SQLite.swift with CocoaPods:
1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`).
```sh
sudo xcode-select --switch /Applications/Xcode.app
```
2. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.)
``` sh
# Using the default Ruby install will require you to use sudo when
# installing and updating gems.
[sudo] gem install cocoapods
```
3. Update your Podfile to include the following:
``` ruby
use_frameworks!
target 'YourAppTargetName' do
pod 'SQLite.swift', '~> 0.11.2'
end
```
4. Run `pod install --repo-update`.
[CocoaPods]: https://cocoapods.org
[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started
### Swift Package Manager
The [Swift Package Manager][] is a tool for managing the distribution of Swift code.
1. Add the following to your `Package.swift` file:
```swift
dependencies: [
.Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11)
]
```
[Swift Package Manager]: https://swift.org/package-manager
### Manual
To install SQLite.swift as an Xcode sub-project:
1. Drag the **SQLite.xcodeproj** file into your own project.
([Submodule][], clone, or [download][] the project first.)
![Installation Screen Shot](Documentation/Resources/installation@2x.png)
2. In your targets **General** tab, click the **+** button under **Linked
Frameworks and Libraries**.
3. Select the appropriate **SQLite.framework** for your platform.
4. **Add**.
Some additional steps are required to install the application on an actual device:
5. In the **General** tab, click the **+** button under **Embedded Binaries**.
6. Select the appropriate **SQLite.framework** for your platform.
7. **Add**.
[Xcode]: https://developer.apple.com/xcode/downloads/
[Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules
[download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip
## Communication
[See the planning document] for a roadmap and existing feature requests.
[Read the contributing guidelines][]. The _TL;DR_ (but please; _R_):
- Need **help** or have a **general question**? [Ask on Stack
Overflow][] (tag `sqlite.swift`).
- Found a **bug** or have a **feature request**? [Open an issue][].
- Want to **contribute**? [Submit a pull request][].
[See the planning document]: /Documentation/Planning.md
[Read the contributing guidelines]: ./CONTRIBUTING.md#contributing
[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift
[Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new
[Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork
## Author
- [Stephen Celis](mailto:stephen@stephencelis.com)
([@stephencelis](https://twitter.com/stephencelis))
## License
SQLite.swift is available under the MIT license. See [the LICENSE
file](./LICENSE.txt) for more information.
## Related
These projects enhance or use SQLite.swift:
- [SQLiteMigrationManager.swift](https://github.com/garriguv/SQLiteMigrationManager.swift) (inspired by [FMDBMigrationManager](https://github.com/layerhq/FMDBMigrationManager))
## Alternatives
Looking for something else? Try another Swift wrapper (or [FMDB][]):
- [Camembert](https://github.com/remirobert/Camembert)
- [GRDB](https://github.com/groue/GRDB.swift)
- [SQLiteDB](https://github.com/FahimF/SQLiteDB)
- [Squeal](https://github.com/nerdyc/Squeal)
- [SwiftData](https://github.com/ryanfowler/SwiftData)
- [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite)
[FMDB]: https://github.com/ccgus/fmdb
[swift-2.3]: https://github.com/stephencelis/SQLite.swift/tree/swift-2.3

View file

@ -0,0 +1,43 @@
import SQLite
let db = try! Connection()
db.trace { print($0) }
let users = Table("users")
let id = Expression<Int64>("id")
let email = Expression<String>("email")
let name = Expression<String?>("name")
try! db.run(users.create { t in
t.column(id, primaryKey: true)
t.column(email, unique: true, check: email.like("%@%"))
t.column(name)
})
let rowid = try! db.run(users.insert(email <- "alice@mac.com"))
let alice = users.filter(id == rowid)
for user in try! db.prepare(users) {
print("id: \(user[id]), email: \(user[email])")
}
let emails = VirtualTable("emails")
let subject = Expression<String?>("subject")
let body = Expression<String?>("body")
try! db.run(emails.create(.FTS4(subject, body)))
try! db.run(emails.insert(
subject <- "Hello, world!",
body <- "This is a hello world message."
))
let row = try! db.pluck(emails.match("hello"))
let query = try! db.prepare(emails.match("hello"))
for row in query {
print(row[subject])
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='osx' display-mode='raw'>
<timeline fileName='timeline.xctimeline'/>
</playground>

View file

@ -0,0 +1,70 @@
Pod::Spec.new do |s|
s.name = "SQLite.swift"
s.version = "0.11.2"
s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X."
s.description = <<-DESC
SQLite.swift provides compile-time confidence in SQL statement syntax and
intent.
DESC
s.homepage = "https://github.com/stephencelis/SQLite.swift"
s.license = 'MIT'
s.author = { "Stephen Celis" => "stephen@stephencelis.com" }
s.source = { :git => "https://github.com/stephencelis/SQLite.swift.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/stephencelis'
s.module_name = 'SQLite'
s.ios.deployment_target = "8.0"
s.tvos.deployment_target = "9.0"
s.osx.deployment_target = "10.9"
s.watchos.deployment_target = "2.0"
s.default_subspec = 'standard'
s.pod_target_xcconfig = {
'SWIFT_VERSION' => '3.0',
}
s.subspec 'standard' do |ss|
ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}'
ss.exclude_files = 'Sources/**/Cipher.swift'
ss.private_header_files = 'Sources/SQLiteObjc/*.h'
ss.library = 'sqlite3'
ss.preserve_paths = 'CocoaPods/**/*'
ss.pod_target_xcconfig = {
'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx',
'SWIFT_INCLUDE_PATHS[sdk=macosx10.11]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.11',
'SWIFT_INCLUDE_PATHS[sdk=macosx10.12]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.12',
'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos',
'SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos-10.0',
'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator',
'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator-10.0',
'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos',
'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator',
'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos',
'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator'
}
end
s.subspec 'standalone' do |ss|
ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}'
ss.exclude_files = 'Sources/**/Cipher.swift'
ss.private_header_files = 'Sources/SQLiteObjc/*.h'
ss.xcconfig = {
'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE'
}
ss.dependency 'sqlite3', '>= 3.14.0'
end
s.subspec 'SQLCipher' do |ss|
ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}'
ss.private_header_files = 'Sources/SQLiteObjc/*.h'
ss.xcconfig = {
'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER',
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1'
}
ss.dependency 'SQLCipher', '>= 3.4.0'
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:SQLite.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,60 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public struct Blob {
public let bytes: [UInt8]
public init(bytes: [UInt8]) {
self.bytes = bytes
}
public init(bytes: UnsafeRawPointer, length: Int) {
let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length)
self.init(bytes: [UInt8](i8bufptr))
}
public func toHex() -> String {
return bytes.map {
($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false)
}.joined(separator: "")
}
}
extension Blob : CustomStringConvertible {
public var description: String {
return "x'\(toHex())'"
}
}
extension Blob : Equatable {
}
public func ==(lhs: Blob, rhs: Blob) -> Bool {
return lhs.bytes == rhs.bytes
}

View file

@ -0,0 +1,756 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation.NSUUID
import Dispatch
#if SQLITE_SWIFT_STANDALONE
import sqlite3
#elseif SQLITE_SWIFT_SQLCIPHER
import SQLCipher
#else
import SQLite3
#endif
/// A connection to SQLite.
public final class Connection {
/// The location of a SQLite database.
public enum Location {
/// An in-memory database (equivalent to `.URI(":memory:")`).
///
/// See: <https://www.sqlite.org/inmemorydb.html#sharedmemdb>
case inMemory
/// A temporary, file-backed database (equivalent to `.URI("")`).
///
/// See: <https://www.sqlite.org/inmemorydb.html#temp_db>
case temporary
/// A database located at the given URI filename (or path).
///
/// See: <https://www.sqlite.org/uri.html>
///
/// - Parameter filename: A URI filename
case uri(String)
}
/// An SQL operation passed to update callbacks.
public enum Operation {
/// An INSERT operation.
case insert
/// An UPDATE operation.
case update
/// A DELETE operation.
case delete
fileprivate init(rawValue:Int32) {
switch rawValue {
case SQLITE_INSERT:
self = .insert
case SQLITE_UPDATE:
self = .update
case SQLITE_DELETE:
self = .delete
default:
fatalError("unhandled operation code: \(rawValue)")
}
}
}
public var handle: OpaquePointer { return _handle! }
fileprivate var _handle: OpaquePointer? = nil
/// Initializes a new SQLite connection.
///
/// - Parameters:
///
/// - location: The location of the database. Creates a new database if it
/// doesnt already exist (unless in read-only mode).
///
/// Default: `.InMemory`.
///
/// - readonly: Whether or not to open the database in a read-only state.
///
/// Default: `false`.
///
/// - Returns: A new database connection.
public init(_ location: Location = .inMemory, readonly: Bool = false) throws {
let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil))
queue.setSpecific(key: Connection.queueKey, value: queueContext)
}
/// Initializes a new connection to a database.
///
/// - Parameters:
///
/// - filename: The location of the database. Creates a new database if
/// it doesnt already exist (unless in read-only mode).
///
/// - readonly: Whether or not to open the database in a read-only state.
///
/// Default: `false`.
///
/// - Throws: `Result.Error` iff a connection cannot be established.
///
/// - Returns: A new database connection.
public convenience init(_ filename: String, readonly: Bool = false) throws {
try self.init(.uri(filename), readonly: readonly)
}
deinit {
sqlite3_close(handle)
}
// MARK: -
/// Whether or not the database was opened in a read-only state.
public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 }
/// The last rowid inserted into the database via this connection.
public var lastInsertRowid: Int64 {
return sqlite3_last_insert_rowid(handle)
}
/// The last number of changes (inserts, updates, or deletes) made to the
/// database via this connection.
public var changes: Int {
return Int(sqlite3_changes(handle))
}
/// The total number of changes (inserts, updates, or deletes) made to the
/// database via this connection.
public var totalChanges: Int {
return Int(sqlite3_total_changes(handle))
}
// MARK: - Execute
/// Executes a batch of SQL statements.
///
/// - Parameter SQL: A batch of zero or more semicolon-separated SQL
/// statements.
///
/// - Throws: `Result.Error` if query execution fails.
public func execute(_ SQL: String) throws {
_ = try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) }
}
// MARK: - Prepare
/// Prepares a single SQL statement (with optional parameter bindings).
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A list of parameters to bind to the statement.
///
/// - Returns: A prepared statement.
public func prepare(_ statement: String, _ bindings: Binding?...) throws -> Statement {
if !bindings.isEmpty { return try prepare(statement, bindings) }
return try Statement(self, statement)
}
/// Prepares a single SQL statement and binds parameters to it.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A list of parameters to bind to the statement.
///
/// - Returns: A prepared statement.
public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement {
return try prepare(statement).bind(bindings)
}
/// Prepares a single SQL statement and binds parameters to it.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A dictionary of named parameters to bind to the statement.
///
/// - Returns: A prepared statement.
public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement {
return try prepare(statement).bind(bindings)
}
// MARK: - Run
/// Runs a single SQL statement (with optional parameter bindings).
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A list of parameters to bind to the statement.
///
/// - Throws: `Result.Error` if query execution fails.
///
/// - Returns: The statement.
@discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement {
return try run(statement, bindings)
}
/// Prepares, binds, and runs a single SQL statement.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A list of parameters to bind to the statement.
///
/// - Throws: `Result.Error` if query execution fails.
///
/// - Returns: The statement.
@discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement {
return try prepare(statement).run(bindings)
}
/// Prepares, binds, and runs a single SQL statement.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A dictionary of named parameters to bind to the statement.
///
/// - Throws: `Result.Error` if query execution fails.
///
/// - Returns: The statement.
@discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement {
return try prepare(statement).run(bindings)
}
// MARK: - Scalar
/// Runs a single SQL statement (with optional parameter bindings),
/// returning the first value of the first row.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A list of parameters to bind to the statement.
///
/// - Returns: The first value of the first row returned.
public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? {
return try scalar(statement, bindings)
}
/// Runs a single SQL statement (with optional parameter bindings),
/// returning the first value of the first row.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A list of parameters to bind to the statement.
///
/// - Returns: The first value of the first row returned.
public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? {
return try prepare(statement).scalar(bindings)
}
/// Runs a single SQL statement (with optional parameter bindings),
/// returning the first value of the first row.
///
/// - Parameters:
///
/// - statement: A single SQL statement.
///
/// - bindings: A dictionary of named parameters to bind to the statement.
///
/// - Returns: The first value of the first row returned.
public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? {
return try prepare(statement).scalar(bindings)
}
// MARK: - Transactions
/// The mode in which a transaction acquires a lock.
public enum TransactionMode : String {
/// Defers locking the database till the first read/write executes.
case deferred = "DEFERRED"
/// Immediately acquires a reserved lock on the database.
case immediate = "IMMEDIATE"
/// Immediately acquires an exclusive lock on all databases.
case exclusive = "EXCLUSIVE"
}
// TODO: Consider not requiring a throw to roll back?
/// Runs a transaction with the given mode.
///
/// - Note: Transactions cannot be nested. To nest transactions, see
/// `savepoint()`, instead.
///
/// - Parameters:
///
/// - mode: The mode in which a transaction acquires a lock.
///
/// Default: `.Deferred`
///
/// - block: A closure to run SQL statements within the transaction.
/// The transaction will be committed when the block returns. The block
/// must throw to roll the transaction back.
///
/// - Throws: `Result.Error`, and rethrows.
public func transaction(_ mode: TransactionMode = .deferred, block: @escaping () throws -> Void) throws {
try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION")
}
// TODO: Consider not requiring a throw to roll back?
// TODO: Consider removing ability to set a name?
/// Runs a transaction with the given savepoint name (if omitted, it will
/// generate a UUID).
///
/// - SeeAlso: `transaction()`.
///
/// - Parameters:
///
/// - savepointName: A unique identifier for the savepoint (optional).
///
/// - block: A closure to run SQL statements within the transaction.
/// The savepoint will be released (committed) when the block returns.
/// The block must throw to roll the savepoint back.
///
/// - Throws: `SQLite.Result.Error`, and rethrows.
public func savepoint(_ name: String = UUID().uuidString, block: @escaping () throws -> Void) throws {
let name = name.quote("'")
let savepoint = "SAVEPOINT \(name)"
try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)")
}
fileprivate func transaction(_ begin: String, _ block: @escaping () throws -> Void, _ commit: String, or rollback: String) throws {
return try sync {
try self.run(begin)
do {
try block()
} catch {
try self.run(rollback)
throw error
}
try self.run(commit)
}
}
/// Interrupts any long-running queries.
public func interrupt() {
sqlite3_interrupt(handle)
}
// MARK: - Handlers
/// The number of seconds a connection will attempt to retry a statement
/// after encountering a busy signal (lock).
public var busyTimeout: Double = 0 {
didSet {
sqlite3_busy_timeout(handle, Int32(busyTimeout * 1_000))
}
}
/// Sets a handler to call after encountering a busy signal (lock).
///
/// - Parameter callback: This block is executed during a lock in which a
/// busy error would otherwise be returned. Its passed the number of
/// times its been called for this lock. If it returns `true`, it will
/// try again. If it returns `false`, no further attempts will be made.
public func busyHandler(_ callback: ((_ tries: Int) -> Bool)?) {
guard let callback = callback else {
sqlite3_busy_handler(handle, nil, nil)
busyHandler = nil
return
}
let box: BusyHandler = { callback(Int($0)) ? 1 : 0 }
sqlite3_busy_handler(handle, { callback, tries in
unsafeBitCast(callback, to: BusyHandler.self)(tries)
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
busyHandler = box
}
fileprivate typealias BusyHandler = @convention(block) (Int32) -> Int32
fileprivate var busyHandler: BusyHandler?
/// Sets a handler to call when a statement is executed with the compiled
/// SQL.
///
/// - Parameter callback: This block is invoked when a statement is executed
/// with the compiled SQL as its argument.
///
/// db.trace { SQL in print(SQL) }
public func trace(_ callback: ((String) -> Void)?) {
#if SQLITE_SWIFT_SQLCIPHER
trace_v1(callback)
#else
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
trace_v2(callback)
} else {
trace_v1(callback)
}
#endif
}
fileprivate func trace_v1(_ callback: ((String) -> Void)?) {
guard let callback = callback else {
sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */)
trace = nil
return
}
let box: Trace = { (pointer: UnsafeRawPointer) in
callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self)))
}
sqlite3_trace(handle,
{
(C: UnsafeMutableRawPointer?, SQL: UnsafePointer<Int8>?) in
if let C = C, let SQL = SQL {
unsafeBitCast(C, to: Trace.self)(SQL)
}
},
unsafeBitCast(box, to: UnsafeMutableRawPointer.self)
)
trace = box
}
fileprivate typealias Trace = @convention(block) (UnsafeRawPointer) -> Void
fileprivate var trace: Trace?
/// Registers a callback to be invoked whenever a row is inserted, updated,
/// or deleted in a rowid table.
///
/// - Parameter callback: A callback invoked with the `Operation` (one of
/// `.Insert`, `.Update`, or `.Delete`), database name, table name, and
/// rowid.
public func updateHook(_ callback: ((_ operation: Operation, _ db: String, _ table: String, _ rowid: Int64) -> Void)?) {
guard let callback = callback else {
sqlite3_update_hook(handle, nil, nil)
updateHook = nil
return
}
let box: UpdateHook = {
callback(
Operation(rawValue: $0),
String(cString: $1),
String(cString: $2),
$3
)
}
sqlite3_update_hook(handle, { callback, operation, db, table, rowid in
unsafeBitCast(callback, to: UpdateHook.self)(operation, db!, table!, rowid)
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
updateHook = box
}
fileprivate typealias UpdateHook = @convention(block) (Int32, UnsafePointer<Int8>, UnsafePointer<Int8>, Int64) -> Void
fileprivate var updateHook: UpdateHook?
/// Registers a callback to be invoked whenever a transaction is committed.
///
/// - Parameter callback: A callback invoked whenever a transaction is
/// committed. If this callback throws, the transaction will be rolled
/// back.
public func commitHook(_ callback: (() throws -> Void)?) {
guard let callback = callback else {
sqlite3_commit_hook(handle, nil, nil)
commitHook = nil
return
}
let box: CommitHook = {
do {
try callback()
} catch {
return 1
}
return 0
}
sqlite3_commit_hook(handle, { callback in
unsafeBitCast(callback, to: CommitHook.self)()
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
commitHook = box
}
fileprivate typealias CommitHook = @convention(block) () -> Int32
fileprivate var commitHook: CommitHook?
/// Registers a callback to be invoked whenever a transaction rolls back.
///
/// - Parameter callback: A callback invoked when a transaction is rolled
/// back.
public func rollbackHook(_ callback: (() -> Void)?) {
guard let callback = callback else {
sqlite3_rollback_hook(handle, nil, nil)
rollbackHook = nil
return
}
let box: RollbackHook = { callback() }
sqlite3_rollback_hook(handle, { callback in
unsafeBitCast(callback, to: RollbackHook.self)()
}, unsafeBitCast(box, to: UnsafeMutableRawPointer.self))
rollbackHook = box
}
fileprivate typealias RollbackHook = @convention(block) () -> Void
fileprivate var rollbackHook: RollbackHook?
/// Creates or redefines a custom SQL function.
///
/// - Parameters:
///
/// - function: The name of the function to create or redefine.
///
/// - argumentCount: The number of arguments that the function takes. If
/// `nil`, the function may take any number of arguments.
///
/// Default: `nil`
///
/// - deterministic: Whether or not the function is deterministic (_i.e._
/// the function always returns the same result for a given input).
///
/// Default: `false`
///
/// - block: A block of code to run when the function is called. The block
/// is called with an array of raw SQL values mapped to the functions
/// parameters and should return a raw SQL value (or nil).
public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) {
let argc = argumentCount.map { Int($0) } ?? -1
let box: Function = { context, argc, argv in
let arguments: [Binding?] = (0..<Int(argc)).map { idx in
let value = argv![idx]
switch sqlite3_value_type(value) {
case SQLITE_BLOB:
return Blob(bytes: sqlite3_value_blob(value), length: Int(sqlite3_value_bytes(value)))
case SQLITE_FLOAT:
return sqlite3_value_double(value)
case SQLITE_INTEGER:
return sqlite3_value_int64(value)
case SQLITE_NULL:
return nil
case SQLITE_TEXT:
return String(cString: UnsafePointer(sqlite3_value_text(value)))
case let type:
fatalError("unsupported value type: \(type)")
}
}
let result = block(arguments)
if let result = result as? Blob {
sqlite3_result_blob(context, result.bytes, Int32(result.bytes.count), nil)
} else if let result = result as? Double {
sqlite3_result_double(context, result)
} else if let result = result as? Int64 {
sqlite3_result_int64(context, result)
} else if let result = result as? String {
sqlite3_result_text(context, result, Int32(result.characters.count), SQLITE_TRANSIENT)
} else if result == nil {
sqlite3_result_null(context)
} else {
fatalError("unsupported result type: \(result)")
}
}
var flags = SQLITE_UTF8
if deterministic {
flags |= SQLITE_DETERMINISTIC
}
sqlite3_create_function_v2(handle, function, Int32(argc), flags, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in
let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self)
function(context, argc, value)
}, nil, nil, nil)
if functions[function] == nil { self.functions[function] = [:] }
functions[function]?[argc] = box
}
fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer<OpaquePointer?>?) -> Void
fileprivate var functions = [String: [Int: Function]]()
/// Defines a new collating sequence.
///
/// - Parameters:
///
/// - collation: The name of the collation added.
///
/// - block: A collation function that takes two strings and returns the
/// comparison result.
public func createCollation(_ collation: String, _ block: @escaping (_ lhs: String, _ rhs: String) -> ComparisonResult) throws {
let box: Collation = { (lhs: UnsafeRawPointer, rhs: UnsafeRawPointer) in
let lstr = String(cString: lhs.assumingMemoryBound(to: UInt8.self))
let rstr = String(cString: rhs.assumingMemoryBound(to: UInt8.self))
return Int32(block(lstr, rstr).rawValue)
}
try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8,
unsafeBitCast(box, to: UnsafeMutableRawPointer.self),
{ (callback: UnsafeMutableRawPointer?, _, lhs: UnsafeRawPointer?, _, rhs: UnsafeRawPointer?) in /* xCompare */
if let lhs = lhs, let rhs = rhs {
return unsafeBitCast(callback, to: Collation.self)(lhs, rhs)
} else {
fatalError("sqlite3_create_collation_v2 callback called with NULL pointer")
}
}, nil /* xDestroy */))
collations[collation] = box
}
fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32
fileprivate var collations = [String: Collation]()
// MARK: - Error Handling
func sync<T>(_ block: @escaping () throws -> T) rethrows -> T {
var success: T?
var failure: Error?
let box: () -> Void = {
do {
success = try block()
} catch {
failure = error
}
}
if DispatchQueue.getSpecific(key: Connection.queueKey) == queueContext {
box()
} else {
queue.sync(execute: box) // FIXME: rdar://problem/21389236
}
if let failure = failure {
try { () -> Void in throw failure }()
}
return success!
}
@discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 {
guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else {
return resultCode
}
throw error
}
fileprivate var queue = DispatchQueue(label: "SQLite.Database", attributes: [])
fileprivate static let queueKey = DispatchSpecificKey<Int>()
fileprivate lazy var queueContext: Int = unsafeBitCast(self, to: Int.self)
}
extension Connection : CustomStringConvertible {
public var description: String {
return String(cString: sqlite3_db_filename(handle, nil))
}
}
extension Connection.Location : CustomStringConvertible {
public var description: String {
switch self {
case .inMemory:
return ":memory:"
case .temporary:
return ""
case .uri(let URI):
return URI
}
}
}
public enum Result : Error {
fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE]
case error(message: String, code: Int32, statement: Statement?)
init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) {
guard !Result.successCodes.contains(errorCode) else { return nil }
let message = String(cString: sqlite3_errmsg(connection.handle))
self = .error(message: message, code: errorCode, statement: statement)
}
}
extension Result : CustomStringConvertible {
public var description: String {
switch self {
case let .error(message, errorCode, statement):
if let statement = statement {
return "\(message) (\(statement)) (code: \(errorCode))"
} else {
return "\(message) (code: \(errorCode))"
}
}
}
}
#if !SQLITE_SWIFT_SQLCIPHER
@available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *)
extension Connection {
fileprivate func trace_v2(_ callback: ((String) -> Void)?) {
guard let callback = callback else {
// If the X callback is NULL or if the M mask is zero, then tracing is disabled.
sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */)
trace = nil
return
}
let box: Trace = { (pointer: UnsafeRawPointer) in
callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self)))
}
sqlite3_trace_v2(handle,
UInt32(SQLITE_TRACE_STMT) /* mask */,
{
// A trace callback is invoked with four arguments: callback(T,C,P,X).
// The T argument is one of the SQLITE_TRACE constants to indicate why the
// callback was invoked. The C argument is a copy of the context pointer.
// The P and X arguments are pointers whose meanings depend on T.
(T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in
if let P = P,
let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) {
unsafeBitCast(C, to: Trace.self)(expandedSQL)
sqlite3_free(expandedSQL)
}
return Int32(0) // currently ignored
},
unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */
)
trace = box
}
}
#endif

View file

@ -0,0 +1,297 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#if SQLITE_SWIFT_STANDALONE
import sqlite3
#elseif SQLITE_SWIFT_SQLCIPHER
import SQLCipher
#else
import SQLite3
#endif
/// A single SQL statement.
public final class Statement {
fileprivate var handle: OpaquePointer? = nil
fileprivate let connection: Connection
init(_ connection: Connection, _ SQL: String) throws {
self.connection = connection
try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil))
}
deinit {
sqlite3_finalize(handle)
}
public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle))
public lazy var columnNames: [String] = (0..<Int32(self.columnCount)).map {
String(cString: sqlite3_column_name(self.handle, $0))
}
/// A cursor pointing to the current row.
public lazy var row: Cursor = Cursor(self)
/// Binds a list of parameters to a statement.
///
/// - Parameter values: A list of parameters to bind to the statement.
///
/// - Returns: The statement object (useful for chaining).
public func bind(_ values: Binding?...) -> Statement {
return bind(values)
}
/// Binds a list of parameters to a statement.
///
/// - Parameter values: A list of parameters to bind to the statement.
///
/// - Returns: The statement object (useful for chaining).
public func bind(_ values: [Binding?]) -> Statement {
if values.isEmpty { return self }
reset()
guard values.count == Int(sqlite3_bind_parameter_count(handle)) else {
fatalError("\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed")
}
for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) }
return self
}
/// Binds a dictionary of named parameters to a statement.
///
/// - Parameter values: A dictionary of named parameters to bind to the
/// statement.
///
/// - Returns: The statement object (useful for chaining).
public func bind(_ values: [String: Binding?]) -> Statement {
reset()
for (name, value) in values {
let idx = sqlite3_bind_parameter_index(handle, name)
guard idx > 0 else {
fatalError("parameter not found: \(name)")
}
bind(value, atIndex: Int(idx))
}
return self
}
fileprivate func bind(_ value: Binding?, atIndex idx: Int) {
if value == nil {
sqlite3_bind_null(handle, Int32(idx))
} else if let value = value as? Blob {
sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT)
} else if let value = value as? Double {
sqlite3_bind_double(handle, Int32(idx), value)
} else if let value = value as? Int64 {
sqlite3_bind_int64(handle, Int32(idx), value)
} else if let value = value as? String {
sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT)
} else if let value = value as? Int {
self.bind(value.datatypeValue, atIndex: idx)
} else if let value = value as? Bool {
self.bind(value.datatypeValue, atIndex: idx)
} else if let value = value {
fatalError("tried to bind unexpected value \(value)")
}
}
/// - Parameter bindings: A list of parameters to bind to the statement.
///
/// - Throws: `Result.Error` if query execution fails.
///
/// - Returns: The statement object (useful for chaining).
@discardableResult public func run(_ bindings: Binding?...) throws -> Statement {
guard bindings.isEmpty else {
return try run(bindings)
}
reset(clearBindings: false)
repeat {} while try step()
return self
}
/// - Parameter bindings: A list of parameters to bind to the statement.
///
/// - Throws: `Result.Error` if query execution fails.
///
/// - Returns: The statement object (useful for chaining).
@discardableResult public func run(_ bindings: [Binding?]) throws -> Statement {
return try bind(bindings).run()
}
/// - Parameter bindings: A dictionary of named parameters to bind to the
/// statement.
///
/// - Throws: `Result.Error` if query execution fails.
///
/// - Returns: The statement object (useful for chaining).
@discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement {
return try bind(bindings).run()
}
/// - Parameter bindings: A list of parameters to bind to the statement.
///
/// - Returns: The first value of the first row returned.
public func scalar(_ bindings: Binding?...) throws -> Binding? {
guard bindings.isEmpty else {
return try scalar(bindings)
}
reset(clearBindings: false)
_ = try step()
return row[0]
}
/// - Parameter bindings: A list of parameters to bind to the statement.
///
/// - Returns: The first value of the first row returned.
public func scalar(_ bindings: [Binding?]) throws -> Binding? {
return try bind(bindings).scalar()
}
/// - Parameter bindings: A dictionary of named parameters to bind to the
/// statement.
///
/// - Returns: The first value of the first row returned.
public func scalar(_ bindings: [String: Binding?]) throws -> Binding? {
return try bind(bindings).scalar()
}
public func step() throws -> Bool {
return try connection.sync { try self.connection.check(sqlite3_step(self.handle)) == SQLITE_ROW }
}
fileprivate func reset(clearBindings shouldClear: Bool = true) {
sqlite3_reset(handle)
if (shouldClear) { sqlite3_clear_bindings(handle) }
}
}
extension Statement : Sequence {
public func makeIterator() -> Statement {
reset(clearBindings: false)
return self
}
}
extension Statement : IteratorProtocol {
public func next() -> [Binding?]? {
return try! step() ? Array(row) : nil
}
}
extension Statement : CustomStringConvertible {
public var description: String {
return String(cString: sqlite3_sql(handle))
}
}
public struct Cursor {
fileprivate let handle: OpaquePointer
fileprivate let columnCount: Int
fileprivate init(_ statement: Statement) {
handle = statement.handle!
columnCount = statement.columnCount
}
public subscript(idx: Int) -> Double {
return sqlite3_column_double(handle, Int32(idx))
}
public subscript(idx: Int) -> Int64 {
return sqlite3_column_int64(handle, Int32(idx))
}
public subscript(idx: Int) -> String {
return String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx))))
}
public subscript(idx: Int) -> Blob {
if let pointer = sqlite3_column_blob(handle, Int32(idx)) {
let length = Int(sqlite3_column_bytes(handle, Int32(idx)))
return Blob(bytes: pointer, length: length)
} else {
// The return value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
// https://www.sqlite.org/c3ref/column_blob.html
return Blob(bytes: [])
}
}
// MARK: -
public subscript(idx: Int) -> Bool {
return Bool.fromDatatypeValue(self[idx])
}
public subscript(idx: Int) -> Int {
return Int.fromDatatypeValue(self[idx])
}
}
/// Cursors provide direct access to a statements current row.
extension Cursor : Sequence {
public subscript(idx: Int) -> Binding? {
switch sqlite3_column_type(handle, Int32(idx)) {
case SQLITE_BLOB:
return self[idx] as Blob
case SQLITE_FLOAT:
return self[idx] as Double
case SQLITE_INTEGER:
return self[idx] as Int64
case SQLITE_NULL:
return nil
case SQLITE_TEXT:
return self[idx] as String
case let type:
fatalError("unsupported column type: \(type)")
}
}
public func makeIterator() -> AnyIterator<Binding?> {
var idx = 0
return AnyIterator {
if idx >= self.columnCount {
return Optional<Binding?>.none
} else {
idx += 1
return self[idx - 1]
}
}
}
}

View file

@ -0,0 +1,132 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/// - Warning: `Binding` is a protocol that SQLite.swift uses internally to
/// directly map SQLite types to Swift types.
///
/// Do not conform custom types to the Binding protocol. See the `Value`
/// protocol, instead.
public protocol Binding {}
public protocol Number : Binding {}
public protocol Value : Expressible { // extensions cannot have inheritance clauses
associatedtype ValueType = Self
associatedtype Datatype : Binding
static var declaredDatatype: String { get }
static func fromDatatypeValue(_ datatypeValue: Datatype) -> ValueType
var datatypeValue: Datatype { get }
}
extension Double : Number, Value {
public static let declaredDatatype = "REAL"
public static func fromDatatypeValue(_ datatypeValue: Double) -> Double {
return datatypeValue
}
public var datatypeValue: Double {
return self
}
}
extension Int64 : Number, Value {
public static let declaredDatatype = "INTEGER"
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int64 {
return datatypeValue
}
public var datatypeValue: Int64 {
return self
}
}
extension String : Binding, Value {
public static let declaredDatatype = "TEXT"
public static func fromDatatypeValue(_ datatypeValue: String) -> String {
return datatypeValue
}
public var datatypeValue: String {
return self
}
}
extension Blob : Binding, Value {
public static let declaredDatatype = "BLOB"
public static func fromDatatypeValue(_ datatypeValue: Blob) -> Blob {
return datatypeValue
}
public var datatypeValue: Blob {
return self
}
}
// MARK: -
extension Bool : Binding, Value {
public static var declaredDatatype = Int64.declaredDatatype
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Bool {
return datatypeValue != 0
}
public var datatypeValue: Int64 {
return self ? 1 : 0
}
}
extension Int : Number, Value {
public static var declaredDatatype = Int64.declaredDatatype
public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int {
return Int(datatypeValue)
}
public var datatypeValue: Int64 {
return Int64(self)
}
}

View file

@ -0,0 +1,61 @@
#if SQLITE_SWIFT_SQLCIPHER
import SQLCipher
/// Extension methods for [SQLCipher](https://www.zetetic.net/sqlcipher/).
/// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/)
extension Connection {
/// Specify the key for an encrypted database. This routine should be
/// called right after sqlite3_open().
///
/// @param key The key to use.The key itself can be a passphrase, which is converted to a key
/// using [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) key derivation. The result
/// is used as the encryption key for the database.
///
/// Alternatively, it is possible to specify an exact byte sequence using a blob literal.
/// With this method, it is the calling application's responsibility to ensure that the data
/// provided is a 64 character hex string, which will be converted directly to 32 bytes (256 bits)
/// of key data.
/// e.g. x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'
/// @param db name of the database, defaults to 'main'
public func key(_ key: String, db: String = "main") throws {
try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count)
}
public func key(_ key: Blob, db: String = "main") throws {
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
}
/// Change the key on an open database. If the current database is not encrypted, this routine
/// will encrypt it.
/// To change the key on an existing encrypted database, it must first be unlocked with the
/// current encryption key. Once the database is readable and writeable, rekey can be used
/// to re-encrypt every page in the database with a new key.
public func rekey(_ key: String, db: String = "main") throws {
try _rekey_v2(db: db, keyPointer: key, keySize: key.utf8.count)
}
public func rekey(_ key: Blob, db: String = "main") throws {
try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
}
// MARK: - private
private func _key_v2(db: String, keyPointer: UnsafePointer<UInt8>, keySize: Int) throws {
try check(sqlite3_key_v2(handle, db, keyPointer, Int32(keySize)))
try cipher_key_check()
}
private func _rekey_v2(db: String, keyPointer: UnsafePointer<UInt8>, keySize: Int) throws {
try check(sqlite3_rekey_v2(handle, db, keyPointer, Int32(keySize)))
}
// When opening an existing database, sqlite3_key_v2 will not immediately throw an error if
// the key provided is incorrect. To test that the database can be successfully opened with the
// provided key, it is necessary to perform some operation on the database (i.e. read from it).
private func cipher_key_check() throws {
try scalar("SELECT count(*) FROM sqlite_master;")
}
}
#endif

View file

@ -0,0 +1,346 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#if SWIFT_PACKAGE
import SQLiteObjc
#endif
extension Module {
public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module {
return FTS4([column] + more)
}
public static func FTS4(_ columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module {
return FTS4(FTS4Config().columns(columns).tokenizer(tokenizer))
}
public static func FTS4(_ config: FTS4Config) -> Module {
return Module(name: "fts4", arguments: config.arguments())
}
}
extension VirtualTable {
/// Builds an expression appended with a `MATCH` query against the given
/// pattern.
///
/// let emails = VirtualTable("emails")
///
/// emails.filter(emails.match("Hello"))
/// // SELECT * FROM "emails" WHERE "emails" MATCH 'Hello'
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: An expression appended with a `MATCH` query against the given
/// pattern.
public func match(_ pattern: String) -> Expression<Bool> {
return "MATCH".infix(tableName(), pattern)
}
public func match(_ pattern: Expression<String>) -> Expression<Bool> {
return "MATCH".infix(tableName(), pattern)
}
public func match(_ pattern: Expression<String?>) -> Expression<Bool?> {
return "MATCH".infix(tableName(), pattern)
}
/// Builds a copy of the query with a `WHERE MATCH` clause.
///
/// let emails = VirtualTable("emails")
///
/// emails.match("Hello")
/// // SELECT * FROM "emails" WHERE "emails" MATCH 'Hello'
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A query with the given `WHERE MATCH` clause applied.
public func match(_ pattern: String) -> QueryType {
return filter(match(pattern))
}
public func match(_ pattern: Expression<String>) -> QueryType {
return filter(match(pattern))
}
public func match(_ pattern: Expression<String?>) -> QueryType {
return filter(match(pattern))
}
}
public struct Tokenizer {
public static let Simple = Tokenizer("simple")
public static let Porter = Tokenizer("porter")
public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set<Character> = [], separators: Set<Character> = []) -> Tokenizer {
var arguments = [String]()
if let removeDiacritics = removeDiacritics {
arguments.append("removeDiacritics=\(removeDiacritics ? 1 : 0)".quote())
}
if !tokenchars.isEmpty {
let joined = tokenchars.map { String($0) }.joined(separator: "")
arguments.append("tokenchars=\(joined)".quote())
}
if !separators.isEmpty {
let joined = separators.map { String($0) }.joined(separator: "")
arguments.append("separators=\(joined)".quote())
}
return Tokenizer("unicode61", arguments)
}
public static func Custom(_ name: String) -> Tokenizer {
return Tokenizer(Tokenizer.moduleName.quote(), [name.quote()])
}
public let name: String
public let arguments: [String]
fileprivate init(_ name: String, _ arguments: [String] = []) {
self.name = name
self.arguments = arguments
}
fileprivate static let moduleName = "SQLite.swift"
}
extension Tokenizer : CustomStringConvertible {
public var description: String {
return ([name] + arguments).joined(separator: " ")
}
}
extension Connection {
public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range<String.Index>)?) throws {
try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in
let string = String(cString: input)
guard let (token, range) = next(string) else { return nil }
let view = string.utf8
offset.pointee += string.substring(to: range.lowerBound).utf8.count
length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.upperBound.samePosition(in: view)))
return token
})
}
}
/// Configuration options shared between the [FTS4](https://www.sqlite.org/fts3.html) and
/// [FTS5](https://www.sqlite.org/fts5.html) extensions.
open class FTSConfig {
public enum ColumnOption {
/// [The notindexed= option](https://www.sqlite.org/fts3.html#section_6_5)
case unindexed
}
typealias ColumnDefinition = (Expressible, options: [ColumnOption])
var columnDefinitions = [ColumnDefinition]()
var tokenizer: Tokenizer?
var prefixes = [Int]()
var externalContentSchema: SchemaType?
var isContentless: Bool = false
/// Adds a column definition
@discardableResult open func column(_ column: Expressible, _ options: [ColumnOption] = []) -> Self {
self.columnDefinitions.append((column, options))
return self
}
@discardableResult open func columns(_ columns: [Expressible]) -> Self {
for column in columns {
self.column(column)
}
return self
}
/// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer)
open func tokenizer(_ tokenizer: Tokenizer?) -> Self {
self.tokenizer = tokenizer
return self
}
/// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6)
open func prefix(_ prefix: [Int]) -> Self {
self.prefixes += prefix
return self
}
/// [The content= option](https://www.sqlite.org/fts3.html#section_6_2)
open func externalContent(_ schema: SchemaType) -> Self {
self.externalContentSchema = schema
return self
}
/// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1)
open func contentless() -> Self {
self.isContentless = true
return self
}
func formatColumnDefinitions() -> [Expressible] {
return columnDefinitions.map { $0.0 }
}
func arguments() -> [Expressible] {
return options().arguments
}
func options() -> Options {
var options = Options()
options.append(formatColumnDefinitions())
if let tokenizer = tokenizer {
options.append("tokenize", value: Expression<Void>(literal: tokenizer.description))
}
options.appendCommaSeparated("prefix", values:prefixes.sorted().map { String($0) })
if isContentless {
options.append("content", value: "")
} else if let externalContentSchema = externalContentSchema {
options.append("content", value: externalContentSchema.tableName())
}
return options
}
struct Options {
var arguments = [Expressible]()
@discardableResult mutating func append(_ columns: [Expressible]) -> Options {
arguments.append(contentsOf: columns)
return self
}
@discardableResult mutating func appendCommaSeparated(_ key: String, values: [String]) -> Options {
if values.isEmpty {
return self
} else {
return append(key, value: values.joined(separator: ","))
}
}
@discardableResult mutating func append(_ key: String, value: CustomStringConvertible?) -> Options {
return append(key, value: value?.description)
}
@discardableResult mutating func append(_ key: String, value: String?) -> Options {
return append(key, value: value.map { Expression<String>($0) })
}
@discardableResult mutating func append(_ key: String, value: Expressible?) -> Options {
if let value = value {
arguments.append("=".join([Expression<Void>(literal: key), value]))
}
return self
}
}
}
/// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension.
open class FTS4Config : FTSConfig {
/// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4)
public enum MatchInfo : CustomStringConvertible {
case fts3
public var description: String {
return "fts3"
}
}
/// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options)
public enum Order : CustomStringConvertible {
/// Data structures are optimized for returning results in ascending order by docid (default)
case asc
/// FTS4 stores its data in such a way as to optimize returning results in descending order by docid.
case desc
public var description: String {
switch self {
case .asc: return "asc"
case .desc: return "desc"
}
}
}
var compressFunction: String?
var uncompressFunction: String?
var languageId: String?
var matchInfo: MatchInfo?
var order: Order?
override public init() {
}
/// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1)
open func compress(_ functionName: String) -> Self {
self.compressFunction = functionName
return self
}
/// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1)
open func uncompress(_ functionName: String) -> Self {
self.uncompressFunction = functionName
return self
}
/// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3)
open func languageId(_ columnName: String) -> Self {
self.languageId = columnName
return self
}
/// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4)
open func matchInfo(_ matchInfo: MatchInfo) -> Self {
self.matchInfo = matchInfo
return self
}
/// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options)
open func order(_ order: Order) -> Self {
self.order = order
return self
}
override func options() -> Options {
var options = super.options()
for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) {
options.append("notindexed", value: column)
}
options.append("languageid", value: languageId)
options.append("compress", value: compressFunction)
options.append("uncompress", value: uncompressFunction)
options.append("matchinfo", value: matchInfo)
options.append("order", value: order)
return options
}
}

View file

@ -0,0 +1,97 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
extension Module {
public static func FTS5(_ config: FTS5Config) -> Module {
return Module(name: "fts5", arguments: config.arguments())
}
}
/// Configuration for the [FTS5](https://www.sqlite.org/fts5.html) extension.
///
/// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version
/// of SQLite.
open class FTS5Config : FTSConfig {
public enum Detail : CustomStringConvertible {
/// store rowid, column number, term offset
case full
/// store rowid, column number
case column
/// store rowid
case none
public var description: String {
switch self {
case .full: return "full"
case .column: return "column"
case .none: return "none"
}
}
}
var detail: Detail?
var contentRowId: Expressible?
var columnSize: Int?
override public init() {
}
/// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2)
open func contentRowId(_ column: Expressible) -> Self {
self.contentRowId = column
return self
}
/// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5)
open func columnSize(_ size: Int) -> Self {
self.columnSize = size
return self
}
/// [The Detail Option](https://www.sqlite.org/fts5.html#section_4_6)
open func detail(_ detail: Detail) -> Self {
self.detail = detail
return self
}
override func options() -> Options {
var options = super.options()
options.append("content_rowid", value: contentRowId)
if let columnSize = columnSize {
options.append("columnsize", value: Expression<Int>(value: columnSize))
}
options.append("detail", value: detail)
return options
}
override func formatColumnDefinitions() -> [Expressible] {
return columnDefinitions.map { definition in
if definition.options.contains(.unindexed) {
return " ".join([definition.0, Expression<Void>(literal: "UNINDEXED")])
} else {
return definition.0
}
}
}
}

View file

@ -0,0 +1,37 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
extension Module {
public static func RTree<T : Value, U : Value>(_ primaryKey: Expression<T>, _ pairs: (Expression<U>, Expression<U>)...) -> Module where T.Datatype == Int64, U.Datatype == Double {
var arguments: [Expressible] = [primaryKey]
for pair in pairs {
arguments.append(contentsOf: [pair.0, pair.1] as [Expressible])
}
return Module(name: "rtree", arguments: arguments)
}
}

View file

@ -0,0 +1,108 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension Data : Value {
public static var declaredDatatype: String {
return Blob.declaredDatatype
}
public static func fromDatatypeValue(_ dataValue: Blob) -> Data {
return Data(bytes: dataValue.bytes)
}
public var datatypeValue: Blob {
return withUnsafeBytes { (pointer: UnsafePointer<UInt8>) -> Blob in
return Blob(bytes: pointer, length: count)
}
}
}
extension Date : Value {
public static var declaredDatatype: String {
return String.declaredDatatype
}
public static func fromDatatypeValue(_ stringValue: String) -> Date {
return dateFormatter.date(from: stringValue)!
}
public var datatypeValue: String {
return dateFormatter.string(from: self)
}
}
/// A global date formatter used to serialize and deserialize `NSDate` objects.
/// If multiple date formats are used in an applications database(s), use a
/// custom `Value` type per additional format.
public var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
// FIXME: rdar://problem/18673897 // subscript<T>
extension QueryType {
public subscript(column: Expression<Data>) -> Expression<Data> {
return namespace(column)
}
public subscript(column: Expression<Data?>) -> Expression<Data?> {
return namespace(column)
}
public subscript(column: Expression<Date>) -> Expression<Date> {
return namespace(column)
}
public subscript(column: Expression<Date?>) -> Expression<Date?> {
return namespace(column)
}
}
extension Row {
public subscript(column: Expression<Data>) -> Data {
return get(column)
}
public subscript(column: Expression<Data?>) -> Data? {
return get(column)
}
public subscript(column: Expression<Date>) -> Date {
return get(column)
}
public subscript(column: Expression<Date?>) -> Date? {
return get(column)
}
}

View file

@ -0,0 +1,130 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#if SQLITE_SWIFT_STANDALONE
import sqlite3
#elseif SQLITE_SWIFT_SQLCIPHER
import SQLCipher
#else
import SQLite3
#endif
public typealias Star = (Expression<Binding>?, Expression<Binding>?) -> Expression<Void>
public func *(_: Expression<Binding>?, _: Expression<Binding>?) -> Expression<Void> {
return Expression(literal: "*")
}
public protocol _OptionalType {
associatedtype WrappedType
}
extension Optional : _OptionalType {
public typealias WrappedType = Wrapped
}
// let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self)
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
extension String {
func quote(_ mark: Character = "\"") -> String {
let escaped = characters.reduce("") { string, character in
string + (character == mark ? "\(mark)\(mark)" : "\(character)")
}
return "\(mark)\(escaped)\(mark)"
}
func join(_ expressions: [Expressible]) -> Expressible {
var (template, bindings) = ([String](), [Binding?]())
for expressible in expressions {
let expression = expressible.expression
template.append(expression.template)
bindings.append(contentsOf: expression.bindings)
}
return Expression<Void>(template.joined(separator: self), bindings)
}
func infix<T>(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression<T> {
let expression = Expression<T>(" \(self) ".join([lhs, rhs]).expression)
guard wrap else {
return expression
}
return "".wrap(expression)
}
func prefix(_ expressions: Expressible) -> Expressible {
return "\(self) ".wrap(expressions) as Expression<Void>
}
func prefix(_ expressions: [Expressible]) -> Expressible {
return "\(self) ".wrap(expressions) as Expression<Void>
}
func wrap<T>(_ expression: Expressible) -> Expression<T> {
return Expression("\(self)(\(expression.expression.template))", expression.expression.bindings)
}
func wrap<T>(_ expressions: [Expressible]) -> Expression<T> {
return wrap(", ".join(expressions))
}
}
func infix<T>(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = #function) -> Expression<T> {
return function.infix(lhs, rhs, wrap: wrap)
}
func wrap<T>(_ expression: Expressible, function: String = #function) -> Expression<T> {
return function.wrap(expression)
}
func wrap<T>(_ expressions: [Expressible], function: String = #function) -> Expression<T> {
return function.wrap(", ".join(expressions))
}
func transcode(_ literal: Binding?) -> String {
guard let literal = literal else { return "NULL" }
switch literal {
case let blob as Blob:
return blob.description
case let string as String:
return string.quote("'")
case let binding:
return "\(binding)"
}
}
func value<A: Value>(_ v: Binding) -> A {
return A.fromDatatypeValue(v as! A.Datatype) as! A
}
func value<A: Value>(_ v: Binding?) -> A {
return value(v!)
}

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.11.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View file

@ -0,0 +1,6 @@
@import Foundation;
FOUNDATION_EXPORT double SQLiteVersionNumber;
FOUNDATION_EXPORT const unsigned char SQLiteVersionString[];
#import <SQLite/SQLite-Bridging.h>

View file

@ -0,0 +1,251 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
extension ExpressionType where UnderlyingType : Value {
/// Builds a copy of the expression prefixed with the `DISTINCT` keyword.
///
/// let name = Expression<String>("name")
/// name.distinct
/// // DISTINCT "name"
///
/// - Returns: A copy of the expression prefixed with the `DISTINCT`
/// keyword.
public var distinct: Expression<UnderlyingType> {
return Expression("DISTINCT \(template)", bindings)
}
/// Builds a copy of the expression wrapped with the `count` aggregate
/// function.
///
/// let name = Expression<String?>("name")
/// name.count
/// // count("name")
/// name.distinct.count
/// // count(DISTINCT "name")
///
/// - Returns: A copy of the expression wrapped with the `count` aggregate
/// function.
public var count: Expression<Int> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value {
/// Builds a copy of the expression prefixed with the `DISTINCT` keyword.
///
/// let name = Expression<String?>("name")
/// name.distinct
/// // DISTINCT "name"
///
/// - Returns: A copy of the expression prefixed with the `DISTINCT`
/// keyword.
public var distinct: Expression<UnderlyingType> {
return Expression("DISTINCT \(template)", bindings)
}
/// Builds a copy of the expression wrapped with the `count` aggregate
/// function.
///
/// let name = Expression<String?>("name")
/// name.count
/// // count("name")
/// name.distinct.count
/// // count(DISTINCT "name")
///
/// - Returns: A copy of the expression wrapped with the `count` aggregate
/// function.
public var count: Expression<Int> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Comparable {
/// Builds a copy of the expression wrapped with the `max` aggregate
/// function.
///
/// let age = Expression<Int>("age")
/// age.max
/// // max("age")
///
/// - Returns: A copy of the expression wrapped with the `max` aggregate
/// function.
public var max: Expression<UnderlyingType?> {
return wrap(self)
}
/// Builds a copy of the expression wrapped with the `min` aggregate
/// function.
///
/// let age = Expression<Int>("age")
/// age.min
/// // min("age")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var min: Expression<UnderlyingType?> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Comparable {
/// Builds a copy of the expression wrapped with the `max` aggregate
/// function.
///
/// let age = Expression<Int?>("age")
/// age.max
/// // max("age")
///
/// - Returns: A copy of the expression wrapped with the `max` aggregate
/// function.
public var max: Expression<UnderlyingType> {
return wrap(self)
}
/// Builds a copy of the expression wrapped with the `min` aggregate
/// function.
///
/// let age = Expression<Int?>("age")
/// age.min
/// // min("age")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var min: Expression<UnderlyingType> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Number {
/// Builds a copy of the expression wrapped with the `avg` aggregate
/// function.
///
/// let salary = Expression<Double>("salary")
/// salary.average
/// // avg("salary")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var average: Expression<Double?> {
return "avg".wrap(self)
}
/// Builds a copy of the expression wrapped with the `sum` aggregate
/// function.
///
/// let salary = Expression<Double>("salary")
/// salary.sum
/// // sum("salary")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var sum: Expression<UnderlyingType?> {
return wrap(self)
}
/// Builds a copy of the expression wrapped with the `total` aggregate
/// function.
///
/// let salary = Expression<Double>("salary")
/// salary.total
/// // total("salary")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var total: Expression<Double> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Number {
/// Builds a copy of the expression wrapped with the `avg` aggregate
/// function.
///
/// let salary = Expression<Double?>("salary")
/// salary.average
/// // avg("salary")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var average: Expression<Double?> {
return "avg".wrap(self)
}
/// Builds a copy of the expression wrapped with the `sum` aggregate
/// function.
///
/// let salary = Expression<Double?>("salary")
/// salary.sum
/// // sum("salary")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var sum: Expression<UnderlyingType> {
return wrap(self)
}
/// Builds a copy of the expression wrapped with the `total` aggregate
/// function.
///
/// let salary = Expression<Double?>("salary")
/// salary.total
/// // total("salary")
///
/// - Returns: A copy of the expression wrapped with the `min` aggregate
/// function.
public var total: Expression<Double> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType == Int {
static func count(_ star: Star) -> Expression<UnderlyingType> {
return wrap(star(nil, nil))
}
}
/// Builds an expression representing `count(*)` (when called with the `*`
/// function literal).
///
/// count(*)
/// // count(*)
///
/// - Returns: An expression returning `count(*)` (when called with the `*`
/// function literal).
public func count(_ star: Star) -> Expression<Int> {
return Expression.count(star)
}

View file

@ -0,0 +1,69 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/// A collating function used to compare to strings.
///
/// - SeeAlso: <https://www.sqlite.org/datatype3.html#collation>
public enum Collation {
/// Compares string by raw data.
case binary
/// Like binary, but folds uppercase ASCII letters into their lowercase
/// equivalents.
case nocase
/// Like binary, but strips trailing space.
case rtrim
/// A custom collating sequence identified by the given string, registered
/// using `Database.create(collation:)`
case custom(String)
}
extension Collation : Expressible {
public var expression: Expression<Void> {
return Expression(literal: description)
}
}
extension Collation : CustomStringConvertible {
public var description : String {
switch self {
case .binary:
return "BINARY"
case .nocase:
return "NOCASE"
case .rtrim:
return "RTRIM"
case .custom(let collation):
return collation.quote()
}
}
}

View file

@ -0,0 +1,683 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation.NSData
extension ExpressionType where UnderlyingType : Number {
/// Builds a copy of the expression wrapped with the `abs` function.
///
/// let x = Expression<Int>("x")
/// x.absoluteValue
/// // abs("x")
///
/// - Returns: A copy of the expression wrapped with the `abs` function.
public var absoluteValue : Expression<UnderlyingType> {
return "abs".wrap(self)
}
}
extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Number {
/// Builds a copy of the expression wrapped with the `abs` function.
///
/// let x = Expression<Int?>("x")
/// x.absoluteValue
/// // abs("x")
///
/// - Returns: A copy of the expression wrapped with the `abs` function.
public var absoluteValue : Expression<UnderlyingType> {
return "abs".wrap(self)
}
}
extension ExpressionType where UnderlyingType == Double {
/// Builds a copy of the expression wrapped with the `round` function.
///
/// let salary = Expression<Double>("salary")
/// salary.round()
/// // round("salary")
/// salary.round(2)
/// // round("salary", 2)
///
/// - Returns: A copy of the expression wrapped with the `round` function.
public func round(_ precision: Int? = nil) -> Expression<UnderlyingType> {
guard let precision = precision else {
return wrap([self])
}
return wrap([self, Int(precision)])
}
}
extension ExpressionType where UnderlyingType == Double? {
/// Builds a copy of the expression wrapped with the `round` function.
///
/// let salary = Expression<Double>("salary")
/// salary.round()
/// // round("salary")
/// salary.round(2)
/// // round("salary", 2)
///
/// - Returns: A copy of the expression wrapped with the `round` function.
public func round(_ precision: Int? = nil) -> Expression<UnderlyingType> {
guard let precision = precision else {
return wrap(self)
}
return wrap([self, Int(precision)])
}
}
extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype == Int64 {
/// Builds an expression representing the `random` function.
///
/// Expression<Int>.random()
/// // random()
///
/// - Returns: An expression calling the `random` function.
public static func random() -> Expression<UnderlyingType> {
return "random".wrap([])
}
}
extension ExpressionType where UnderlyingType == Data {
/// Builds an expression representing the `randomblob` function.
///
/// Expression<Int>.random(16)
/// // randomblob(16)
///
/// - Parameter length: Length in bytes.
///
/// - Returns: An expression calling the `randomblob` function.
public static func random(_ length: Int) -> Expression<UnderlyingType> {
return "randomblob".wrap([])
}
/// Builds an expression representing the `zeroblob` function.
///
/// Expression<Int>.allZeros(16)
/// // zeroblob(16)
///
/// - Parameter length: Length in bytes.
///
/// - Returns: An expression calling the `zeroblob` function.
public static func allZeros(_ length: Int) -> Expression<UnderlyingType> {
return "zeroblob".wrap([])
}
/// Builds a copy of the expression wrapped with the `length` function.
///
/// let data = Expression<NSData>("data")
/// data.length
/// // length("data")
///
/// - Returns: A copy of the expression wrapped with the `length` function.
public var length: Expression<Int> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType == Data? {
/// Builds a copy of the expression wrapped with the `length` function.
///
/// let data = Expression<NSData?>("data")
/// data.length
/// // length("data")
///
/// - Returns: A copy of the expression wrapped with the `length` function.
public var length: Expression<Int?> {
return wrap(self)
}
}
extension ExpressionType where UnderlyingType == String {
/// Builds a copy of the expression wrapped with the `length` function.
///
/// let name = Expression<String>("name")
/// name.length
/// // length("name")
///
/// - Returns: A copy of the expression wrapped with the `length` function.
public var length: Expression<Int> {
return wrap(self)
}
/// Builds a copy of the expression wrapped with the `lower` function.
///
/// let name = Expression<String>("name")
/// name.lowercaseString
/// // lower("name")
///
/// - Returns: A copy of the expression wrapped with the `lower` function.
public var lowercaseString: Expression<UnderlyingType> {
return "lower".wrap(self)
}
/// Builds a copy of the expression wrapped with the `upper` function.
///
/// let name = Expression<String>("name")
/// name.uppercaseString
/// // lower("name")
///
/// - Returns: A copy of the expression wrapped with the `upper` function.
public var uppercaseString: Expression<UnderlyingType> {
return "upper".wrap(self)
}
/// Builds a copy of the expression appended with a `LIKE` query against the
/// given pattern.
///
/// let email = Expression<String>("email")
/// email.like("%@example.com")
/// // "email" LIKE '%@example.com'
/// email.like("99\\%@%", escape: "\\")
/// // "email" LIKE '99\%@%' ESCAPE '\'
///
/// - Parameters:
///
/// - pattern: A pattern to match.
///
/// - escape: An (optional) character designated for escaping
/// pattern-matching characters (*i.e.*, the `%` and `_` characters).
///
/// - Returns: A copy of the expression appended with a `LIKE` query against
/// the given pattern.
public func like(_ pattern: String, escape character: Character? = nil) -> Expression<Bool> {
guard let character = character else {
return "LIKE".infix(self, pattern)
}
return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)])
}
/// Builds a copy of the expression appended with a `GLOB` query against the
/// given pattern.
///
/// let path = Expression<String>("path")
/// path.glob("*.png")
/// // "path" GLOB '*.png'
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression appended with a `GLOB` query against
/// the given pattern.
public func glob(_ pattern: String) -> Expression<Bool> {
return "GLOB".infix(self, pattern)
}
/// Builds a copy of the expression appended with a `MATCH` query against
/// the given pattern.
///
/// let title = Expression<String>("title")
/// title.match("swift AND programming")
/// // "title" MATCH 'swift AND programming'
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression appended with a `MATCH` query
/// against the given pattern.
public func match(_ pattern: String) -> Expression<Bool> {
return "MATCH".infix(self, pattern)
}
/// Builds a copy of the expression appended with a `REGEXP` query against
/// the given pattern.
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression appended with a `REGEXP` query
/// against the given pattern.
public func regexp(_ pattern: String) -> Expression<Bool> {
return "REGEXP".infix(self, pattern)
}
/// Builds a copy of the expression appended with a `COLLATE` clause with
/// the given sequence.
///
/// let name = Expression<String>("name")
/// name.collate(.Nocase)
/// // "name" COLLATE NOCASE
///
/// - Parameter collation: A collating sequence.
///
/// - Returns: A copy of the expression appended with a `COLLATE` clause
/// with the given sequence.
public func collate(_ collation: Collation) -> Expression<UnderlyingType> {
return "COLLATE".infix(self, collation)
}
/// Builds a copy of the expression wrapped with the `ltrim` function.
///
/// let name = Expression<String>("name")
/// name.ltrim()
/// // ltrim("name")
/// name.ltrim([" ", "\t"])
/// // ltrim("name", ' \t')
///
/// - Parameter characters: A set of characters to trim.
///
/// - Returns: A copy of the expression wrapped with the `ltrim` function.
public func ltrim(_ characters: Set<Character>? = nil) -> Expression<UnderlyingType> {
guard let characters = characters else {
return wrap(self)
}
return wrap([self, String(characters)])
}
/// Builds a copy of the expression wrapped with the `rtrim` function.
///
/// let name = Expression<String>("name")
/// name.rtrim()
/// // rtrim("name")
/// name.rtrim([" ", "\t"])
/// // rtrim("name", ' \t')
///
/// - Parameter characters: A set of characters to trim.
///
/// - Returns: A copy of the expression wrapped with the `rtrim` function.
public func rtrim(_ characters: Set<Character>? = nil) -> Expression<UnderlyingType> {
guard let characters = characters else {
return wrap(self)
}
return wrap([self, String(characters)])
}
/// Builds a copy of the expression wrapped with the `trim` function.
///
/// let name = Expression<String>("name")
/// name.trim()
/// // trim("name")
/// name.trim([" ", "\t"])
/// // trim("name", ' \t')
///
/// - Parameter characters: A set of characters to trim.
///
/// - Returns: A copy of the expression wrapped with the `trim` function.
public func trim(_ characters: Set<Character>? = nil) -> Expression<UnderlyingType> {
guard let characters = characters else {
return wrap([self])
}
return wrap([self, String(characters)])
}
/// Builds a copy of the expression wrapped with the `replace` function.
///
/// let email = Expression<String>("email")
/// email.replace("@mac.com", with: "@icloud.com")
/// // replace("email", '@mac.com', '@icloud.com')
///
/// - Parameters:
///
/// - pattern: A pattern to match.
///
/// - replacement: The replacement string.
///
/// - Returns: A copy of the expression wrapped with the `replace` function.
public func replace(_ pattern: String, with replacement: String) -> Expression<UnderlyingType> {
return "replace".wrap([self, pattern, replacement])
}
public func substring(_ location: Int, length: Int? = nil) -> Expression<UnderlyingType> {
guard let length = length else {
return "substr".wrap([self, location])
}
return "substr".wrap([self, location, length])
}
public subscript(range: Range<Int>) -> Expression<UnderlyingType> {
return substring(range.lowerBound, length: range.upperBound - range.lowerBound)
}
}
extension ExpressionType where UnderlyingType == String? {
/// Builds a copy of the expression wrapped with the `length` function.
///
/// let name = Expression<String?>("name")
/// name.length
/// // length("name")
///
/// - Returns: A copy of the expression wrapped with the `length` function.
public var length: Expression<Int?> {
return wrap(self)
}
/// Builds a copy of the expression wrapped with the `lower` function.
///
/// let name = Expression<String?>("name")
/// name.lowercaseString
/// // lower("name")
///
/// - Returns: A copy of the expression wrapped with the `lower` function.
public var lowercaseString: Expression<UnderlyingType> {
return "lower".wrap(self)
}
/// Builds a copy of the expression wrapped with the `upper` function.
///
/// let name = Expression<String?>("name")
/// name.uppercaseString
/// // lower("name")
///
/// - Returns: A copy of the expression wrapped with the `upper` function.
public var uppercaseString: Expression<UnderlyingType> {
return "upper".wrap(self)
}
/// Builds a copy of the expression appended with a `LIKE` query against the
/// given pattern.
///
/// let email = Expression<String?>("email")
/// email.like("%@example.com")
/// // "email" LIKE '%@example.com'
/// email.like("99\\%@%", escape: "\\")
/// // "email" LIKE '99\%@%' ESCAPE '\'
///
/// - Parameters:
///
/// - pattern: A pattern to match.
///
/// - escape: An (optional) character designated for escaping
/// pattern-matching characters (*i.e.*, the `%` and `_` characters).
///
/// - Returns: A copy of the expression appended with a `LIKE` query against
/// the given pattern.
public func like(_ pattern: String, escape character: Character? = nil) -> Expression<Bool?> {
guard let character = character else {
return "LIKE".infix(self, pattern)
}
return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)])
}
/// Builds a copy of the expression appended with a `GLOB` query against the
/// given pattern.
///
/// let path = Expression<String?>("path")
/// path.glob("*.png")
/// // "path" GLOB '*.png'
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression appended with a `GLOB` query against
/// the given pattern.
public func glob(_ pattern: String) -> Expression<Bool?> {
return "GLOB".infix(self, pattern)
}
/// Builds a copy of the expression appended with a `MATCH` query against
/// the given pattern.
///
/// let title = Expression<String?>("title")
/// title.match("swift AND programming")
/// // "title" MATCH 'swift AND programming'
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression appended with a `MATCH` query
/// against the given pattern.
public func match(_ pattern: String) -> Expression<Bool> {
return "MATCH".infix(self, pattern)
}
/// Builds a copy of the expression appended with a `REGEXP` query against
/// the given pattern.
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression appended with a `REGEXP` query
/// against the given pattern.
public func regexp(_ pattern: String) -> Expression<Bool?> {
return "REGEXP".infix(self, pattern)
}
/// Builds a copy of the expression appended with a `COLLATE` clause with
/// the given sequence.
///
/// let name = Expression<String?>("name")
/// name.collate(.Nocase)
/// // "name" COLLATE NOCASE
///
/// - Parameter collation: A collating sequence.
///
/// - Returns: A copy of the expression appended with a `COLLATE` clause
/// with the given sequence.
public func collate(_ collation: Collation) -> Expression<UnderlyingType> {
return "COLLATE".infix(self, collation)
}
/// Builds a copy of the expression wrapped with the `ltrim` function.
///
/// let name = Expression<String?>("name")
/// name.ltrim()
/// // ltrim("name")
/// name.ltrim([" ", "\t"])
/// // ltrim("name", ' \t')
///
/// - Parameter characters: A set of characters to trim.
///
/// - Returns: A copy of the expression wrapped with the `ltrim` function.
public func ltrim(_ characters: Set<Character>? = nil) -> Expression<UnderlyingType> {
guard let characters = characters else {
return wrap(self)
}
return wrap([self, String(characters)])
}
/// Builds a copy of the expression wrapped with the `rtrim` function.
///
/// let name = Expression<String?>("name")
/// name.rtrim()
/// // rtrim("name")
/// name.rtrim([" ", "\t"])
/// // rtrim("name", ' \t')
///
/// - Parameter characters: A set of characters to trim.
///
/// - Returns: A copy of the expression wrapped with the `rtrim` function.
public func rtrim(_ characters: Set<Character>? = nil) -> Expression<UnderlyingType> {
guard let characters = characters else {
return wrap(self)
}
return wrap([self, String(characters)])
}
/// Builds a copy of the expression wrapped with the `trim` function.
///
/// let name = Expression<String?>("name")
/// name.trim()
/// // trim("name")
/// name.trim([" ", "\t"])
/// // trim("name", ' \t')
///
/// - Parameter characters: A set of characters to trim.
///
/// - Returns: A copy of the expression wrapped with the `trim` function.
public func trim(_ characters: Set<Character>? = nil) -> Expression<UnderlyingType> {
guard let characters = characters else {
return wrap(self)
}
return wrap([self, String(characters)])
}
/// Builds a copy of the expression wrapped with the `replace` function.
///
/// let email = Expression<String?>("email")
/// email.replace("@mac.com", with: "@icloud.com")
/// // replace("email", '@mac.com', '@icloud.com')
///
/// - Parameters:
///
/// - pattern: A pattern to match.
///
/// - replacement: The replacement string.
///
/// - Returns: A copy of the expression wrapped with the `replace` function.
public func replace(_ pattern: String, with replacement: String) -> Expression<UnderlyingType> {
return "replace".wrap([self, pattern, replacement])
}
/// Builds a copy of the expression wrapped with the `substr` function.
///
/// let title = Expression<String?>("title")
/// title.substr(-100)
/// // substr("title", -100)
/// title.substr(0, length: 100)
/// // substr("title", 0, 100)
///
/// - Parameters:
///
/// - location: The substrings start index.
///
/// - length: An optional substring length.
///
/// - Returns: A copy of the expression wrapped with the `substr` function.
public func substring(_ location: Int, length: Int? = nil) -> Expression<UnderlyingType> {
guard let length = length else {
return "substr".wrap([self, location])
}
return "substr".wrap([self, location, length])
}
/// Builds a copy of the expression wrapped with the `substr` function.
///
/// let title = Expression<String?>("title")
/// title[0..<100]
/// // substr("title", 0, 100)
///
/// - Parameter range: The character index range of the substring.
///
/// - Returns: A copy of the expression wrapped with the `substr` function.
public subscript(range: Range<Int>) -> Expression<UnderlyingType> {
return substring(range.lowerBound, length: range.upperBound - range.lowerBound)
}
}
extension Collection where Iterator.Element : Value, IndexDistance == Int {
/// Builds a copy of the expression prepended with an `IN` check against the
/// collection.
///
/// let name = Expression<String>("name")
/// ["alice", "betty"].contains(name)
/// // "name" IN ('alice', 'betty')
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression prepended with an `IN` check against
/// the collection.
public func contains(_ expression: Expression<Iterator.Element>) -> Expression<Bool> {
let templates = [String](repeating: "?", count: count).joined(separator: ", ")
return "IN".infix(expression, Expression<Void>("(\(templates))", map { $0.datatypeValue }))
}
/// Builds a copy of the expression prepended with an `IN` check against the
/// collection.
///
/// let name = Expression<String?>("name")
/// ["alice", "betty"].contains(name)
/// // "name" IN ('alice', 'betty')
///
/// - Parameter pattern: A pattern to match.
///
/// - Returns: A copy of the expression prepended with an `IN` check against
/// the collection.
public func contains(_ expression: Expression<Iterator.Element?>) -> Expression<Bool?> {
let templates = [String](repeating: "?", count: count).joined(separator: ", ")
return "IN".infix(expression, Expression<Void>("(\(templates))", map { $0.datatypeValue }))
}
}
/// Builds a copy of the given expressions wrapped with the `ifnull` function.
///
/// let name = Expression<String?>("name")
/// name ?? "An Anonymous Coward"
/// // ifnull("name", 'An Anonymous Coward')
///
/// - Parameters:
///
/// - optional: An optional expression.
///
/// - defaultValue: A fallback value for when the optional expression is
/// `nil`.
///
/// - Returns: A copy of the given expressions wrapped with the `ifnull`
/// function.
public func ??<V : Value>(optional: Expression<V?>, defaultValue: V) -> Expression<V> {
return "ifnull".wrap([optional, defaultValue])
}
/// Builds a copy of the given expressions wrapped with the `ifnull` function.
///
/// let nick = Expression<String?>("nick")
/// let name = Expression<String>("name")
/// nick ?? name
/// // ifnull("nick", "name")
///
/// - Parameters:
///
/// - optional: An optional expression.
///
/// - defaultValue: A fallback expression for when the optional expression is
/// `nil`.
///
/// - Returns: A copy of the given expressions wrapped with the `ifnull`
/// function.
public func ??<V : Value>(optional: Expression<V?>, defaultValue: Expression<V>) -> Expression<V> {
return "ifnull".wrap([optional, defaultValue])
}
/// Builds a copy of the given expressions wrapped with the `ifnull` function.
///
/// let nick = Expression<String?>("nick")
/// let name = Expression<String?>("name")
/// nick ?? name
/// // ifnull("nick", "name")
///
/// - Parameters:
///
/// - optional: An optional expression.
///
/// - defaultValue: A fallback expression for when the optional expression is
/// `nil`.
///
/// - Returns: A copy of the given expressions wrapped with the `ifnull`
/// function.
public func ??<V : Value>(optional: Expression<V?>, defaultValue: Expression<V?>) -> Expression<V> {
return "ifnull".wrap([optional, defaultValue])
}

View file

@ -0,0 +1,136 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public extension Connection {
/// Creates or redefines a custom SQL function.
///
/// - Parameters:
///
/// - function: The name of the function to create or redefine.
///
/// - deterministic: Whether or not the function is deterministic (_i.e._
/// the function always returns the same result for a given input).
///
/// Default: `false`
///
/// - block: A block of code to run when the function is called.
/// The assigned types must be explicit.
///
/// - Returns: A closure returning an SQL expression to call the function.
public func createFunction<Z : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression<Z>) {
let fn = try createFunction(function, 0, deterministic) { _ in block() }
return { fn([]) }
}
public func createFunction<Z : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression<Z?>) {
let fn = try createFunction(function, 0, deterministic) { _ in block() }
return { fn([]) }
}
// MARK: -
public func createFunction<Z : Value, A : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression<A>) -> Expression<Z>) {
let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) }
return { arg in fn([arg]) }
}
public func createFunction<Z : Value, A : Value>(function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression<A?>) -> Expression<Z>) {
let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) }
return { arg in fn([arg]) }
}
public func createFunction<Z : Value, A : Value>(function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression<A>) -> Expression<Z?>) {
let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) }
return { arg in fn([arg]) }
}
public func createFunction<Z : Value, A : Value>(function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression<A?>) -> Expression<Z?>) {
let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) }
return { arg in fn([arg]) }
}
// MARK: -
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression<A>, Expression<B>) -> Expression<Z> {
let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression<A?>, Expression<B>) -> Expression<Z> {
let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression<A>, Expression<B?>) -> Expression<Z> {
let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression<A>, Expression<B>) -> Expression<Z?> {
let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression<A?>, Expression<B?>) -> Expression<Z> {
let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression<A?>, Expression<B>) -> Expression<Z?> {
let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression<A>, Expression<B?>) -> Expression<Z?> {
let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) }
return { a, b in fn([a, b]) }
}
public func createFunction<Z : Value, A : Value, B : Value>(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression<A?>, Expression<B?>) -> Expression<Z?> {
let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) }
return { a, b in fn([a, b]) }
}
// MARK: -
fileprivate func createFunction<Z : Value>(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z) throws -> (([Expressible]) -> Expression<Z>) {
createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in
block(arguments).datatypeValue
}
return { arguments in
function.quote().wrap(", ".join(arguments))
}
}
fileprivate func createFunction<Z : Value>(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z?) throws -> (([Expressible]) -> Expression<Z?>) {
createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in
block(arguments)?.datatypeValue
}
return { arguments in
function.quote().wrap(", ".join(arguments))
}
}
}

View file

@ -0,0 +1,147 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol ExpressionType : Expressible { // extensions cannot have inheritance clauses
associatedtype UnderlyingType = Void
var template: String { get }
var bindings: [Binding?] { get }
init(_ template: String, _ bindings: [Binding?])
}
extension ExpressionType {
public init(literal: String) {
self.init(literal, [])
}
public init(_ identifier: String) {
self.init(literal: identifier.quote())
}
public init<U : ExpressionType>(_ expression: U) {
self.init(expression.template, expression.bindings)
}
}
/// An `Expression` represents a raw SQL fragment and any associated bindings.
public struct Expression<Datatype> : ExpressionType {
public typealias UnderlyingType = Datatype
public var template: String
public var bindings: [Binding?]
public init(_ template: String, _ bindings: [Binding?]) {
self.template = template
self.bindings = bindings
}
}
public protocol Expressible {
var expression: Expression<Void> { get }
}
extension Expressible {
// naïve compiler for statements that cant be bound, e.g., CREATE TABLE
// FIXME: use @testable and make internal
public func asSQL() -> String {
let expressed = expression
var idx = 0
return expressed.template.characters.reduce("") { template, character in
let transcoded: String
if character == "?" {
transcoded = transcode(expressed.bindings[idx])
idx += 1
} else {
transcoded = String(character)
}
return template + transcoded
}
}
}
extension ExpressionType {
public var expression: Expression<Void> {
return Expression(template, bindings)
}
public var asc: Expressible {
return " ".join([self, Expression<Void>(literal: "ASC")])
}
public var desc: Expressible {
return " ".join([self, Expression<Void>(literal: "DESC")])
}
}
extension ExpressionType where UnderlyingType : Value {
public init(value: UnderlyingType) {
self.init("?", [value.datatypeValue])
}
}
extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value {
public static var null: Self {
return self.init(value: nil)
}
public init(value: UnderlyingType.WrappedType?) {
self.init("?", [value?.datatypeValue])
}
}
extension Value {
public var expression: Expression<Void> {
return Expression(value: self).expression
}
}
public let rowid = Expression<Int64>("ROWID")
public func cast<T: Value, U: Value>(_ expression: Expression<T>) -> Expression<U> {
return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings)
}
public func cast<T: Value, U: Value>(_ expression: Expression<T?>) -> Expression<U?> {
return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings)
}

View file

@ -0,0 +1,541 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// TODO: use `@warn_unused_result` by the time operator functions support it
public func +(lhs: Expression<String>, rhs: Expression<String>) -> Expression<String> {
return "||".infix(lhs, rhs)
}
public func +(lhs: Expression<String>, rhs: Expression<String?>) -> Expression<String?> {
return "||".infix(lhs, rhs)
}
public func +(lhs: Expression<String?>, rhs: Expression<String>) -> Expression<String?> {
return "||".infix(lhs, rhs)
}
public func +(lhs: Expression<String?>, rhs: Expression<String?>) -> Expression<String?> {
return "||".infix(lhs, rhs)
}
public func +(lhs: Expression<String>, rhs: String) -> Expression<String> {
return "||".infix(lhs, rhs)
}
public func +(lhs: Expression<String?>, rhs: String) -> Expression<String?> {
return "||".infix(lhs, rhs)
}
public func +(lhs: String, rhs: Expression<String>) -> Expression<String> {
return "||".infix(lhs, rhs)
}
public func +(lhs: String, rhs: Expression<String?>) -> Expression<String?> {
return "||".infix(lhs, rhs)
}
// MARK: -
public func +<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func +<V: Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func -<V: Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func *<V: Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return infix(lhs, rhs)
}
public func /<V: Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return infix(lhs, rhs)
}
public prefix func -<V : Value>(rhs: Expression<V>) -> Expression<V> where V.Datatype : Number {
return wrap(rhs)
}
public prefix func -<V : Value>(rhs: Expression<V?>) -> Expression<V?> where V.Datatype : Number {
return wrap(rhs)
}
// MARK: -
public func %<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func %<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func <<<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func >><V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func &<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func |<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return infix(lhs, rhs)
}
public func ^<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<V?> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<V> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<V?> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public func ^<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return (~(lhs & rhs)) & (lhs | rhs)
}
public prefix func ~<V : Value>(rhs: Expression<V>) -> Expression<V> where V.Datatype == Int64 {
return wrap(rhs)
}
public prefix func ~<V : Value>(rhs: Expression<V?>) -> Expression<V?> where V.Datatype == Int64 {
return wrap(rhs)
}
// MARK: -
public func ==<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Equatable {
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Equatable {
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<Bool?> where V.Datatype : Equatable {
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Equatable {
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<Bool> where V.Datatype : Equatable {
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: Expression<V?>, rhs: V?) -> Expression<Bool?> where V.Datatype : Equatable {
guard let rhs = rhs else { return "IS".infix(lhs, Expression<V?>(value: nil)) }
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Equatable {
return "=".infix(lhs, rhs)
}
public func ==<V : Value>(lhs: V?, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Equatable {
guard let lhs = lhs else { return "IS".infix(Expression<V?>(value: nil), rhs) }
return "=".infix(lhs, rhs)
}
public func !=<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Equatable {
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Equatable {
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<Bool?> where V.Datatype : Equatable {
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Equatable {
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<Bool> where V.Datatype : Equatable {
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: Expression<V?>, rhs: V?) -> Expression<Bool?> where V.Datatype : Equatable {
guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression<V?>(value: nil)) }
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Equatable {
return infix(lhs, rhs)
}
public func !=<V : Value>(lhs: V?, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Equatable {
guard let lhs = lhs else { return "IS NOT".infix(Expression<V?>(value: nil), rhs) }
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: Expression<V>, rhs: V) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: V, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ><V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func >=<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: Expression<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: Expression<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: Expression<V?>, rhs: Expression<V>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: Expression<V?>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: Expression<V>, rhs: V) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: Expression<V?>, rhs: V) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: V, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func <=<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return infix(lhs, rhs)
}
public func ~=<V : Value>(lhs: ClosedRange<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Binding & Comparable {
return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding])
}
public func ~=<V : Value>(lhs: ClosedRange<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Binding & Comparable {
return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding])
}
// MARK: -
public func &&(lhs: Expression<Bool>, rhs: Expression<Bool>) -> Expression<Bool> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Expression<Bool>, rhs: Expression<Bool?>) -> Expression<Bool?> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Expression<Bool?>, rhs: Expression<Bool>) -> Expression<Bool?> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Expression<Bool?>, rhs: Expression<Bool?>) -> Expression<Bool?> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Expression<Bool>, rhs: Bool) -> Expression<Bool> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Expression<Bool?>, rhs: Bool) -> Expression<Bool?> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Bool, rhs: Expression<Bool>) -> Expression<Bool> {
return "AND".infix(lhs, rhs)
}
public func &&(lhs: Bool, rhs: Expression<Bool?>) -> Expression<Bool?> {
return "AND".infix(lhs, rhs)
}
public func ||(lhs: Expression<Bool>, rhs: Expression<Bool>) -> Expression<Bool> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Expression<Bool>, rhs: Expression<Bool?>) -> Expression<Bool?> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Expression<Bool?>, rhs: Expression<Bool>) -> Expression<Bool?> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Expression<Bool?>, rhs: Expression<Bool?>) -> Expression<Bool?> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Expression<Bool>, rhs: Bool) -> Expression<Bool> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Expression<Bool?>, rhs: Bool) -> Expression<Bool?> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Bool, rhs: Expression<Bool>) -> Expression<Bool> {
return "OR".infix(lhs, rhs)
}
public func ||(lhs: Bool, rhs: Expression<Bool?>) -> Expression<Bool?> {
return "OR".infix(lhs, rhs)
}
public prefix func !(rhs: Expression<Bool>) -> Expression<Bool> {
return "NOT ".wrap(rhs)
}
public prefix func !(rhs: Expression<Bool?>) -> Expression<Bool?> {
return "NOT ".wrap(rhs)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,519 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
extension SchemaType {
// MARK: - DROP TABLE / VIEW / VIRTUAL TABLE
public func drop(ifExists: Bool = false) -> String {
return drop("TABLE", tableName(), ifExists)
}
}
extension Table {
// MARK: - CREATE TABLE
public func create(temporary: Bool = false, ifNotExists: Bool = false, block: (TableBuilder) -> Void) -> String {
let builder = TableBuilder()
block(builder)
let clauses: [Expressible?] = [
create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists),
"".wrap(builder.definitions) as Expression<Void>
]
return " ".join(clauses.flatMap { $0 }).asSQL()
}
public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String {
let clauses: [Expressible?] = [
create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists),
Expression<Void>(literal: "AS"),
query
]
return " ".join(clauses.flatMap { $0 }).asSQL()
}
// MARK: - ALTER TABLE ADD COLUMN
public func addColumn<V : Value>(_ name: Expression<V>, check: Expression<Bool>? = nil, defaultValue: V) -> String {
return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil))
}
public func addColumn<V : Value>(_ name: Expression<V>, check: Expression<Bool?>, defaultValue: V) -> String {
return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil))
}
public func addColumn<V : Value>(_ name: Expression<V?>, check: Expression<Bool>? = nil, defaultValue: V? = nil) -> String {
return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil))
}
public func addColumn<V : Value>(_ name: Expression<V?>, check: Expression<Bool?>, defaultValue: V? = nil) -> String {
return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil))
}
public func addColumn<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool>? = nil, references table: QueryType, _ other: Expression<V>) -> String where V.Datatype == Int64 {
return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil))
}
public func addColumn<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool?>, references table: QueryType, _ other: Expression<V>) -> String where V.Datatype == Int64 {
return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil))
}
public func addColumn<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, references table: QueryType, _ other: Expression<V>) -> String where V.Datatype == Int64 {
return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil))
}
public func addColumn<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, references table: QueryType, _ other: Expression<V>) -> String where V.Datatype == Int64 {
return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil))
}
public func addColumn<V : Value>(_ name: Expression<V>, check: Expression<Bool>? = nil, defaultValue: V, collate: Collation) -> String where V.Datatype == String {
return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate))
}
public func addColumn<V : Value>(_ name: Expression<V>, check: Expression<Bool?>, defaultValue: V, collate: Collation) -> String where V.Datatype == String {
return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate))
}
public func addColumn<V : Value>(_ name: Expression<V?>, check: Expression<Bool>? = nil, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String {
return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate))
}
public func addColumn<V : Value>(_ name: Expression<V?>, check: Expression<Bool?>, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String {
return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate))
}
fileprivate func addColumn(_ expression: Expressible) -> String {
return " ".join([
Expression<Void>(literal: "ALTER TABLE"),
tableName(),
Expression<Void>(literal: "ADD COLUMN"),
expression
]).asSQL()
}
// MARK: - ALTER TABLE RENAME TO
public func rename(_ to: Table) -> String {
return rename(to: to)
}
// MARK: - CREATE INDEX
public func createIndex(_ columns: Expressible...) -> String {
return createIndex(columns)
}
public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String {
let clauses: [Expressible?] = [
create("INDEX", indexName(columns), unique ? .Unique : nil, ifNotExists),
Expression<Void>(literal: "ON"),
tableName(qualified: false),
"".wrap(columns) as Expression<Void>
]
return " ".join(clauses.flatMap { $0 }).asSQL()
}
// MARK: - DROP INDEX
public func dropIndex(_ columns: Expressible...) -> String {
return dropIndex(columns)
}
public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String {
return drop("INDEX", indexName(columns), ifExists)
}
fileprivate func indexName(_ columns: [Expressible]) -> Expressible {
let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joined(separator: " ").lowercased()
let index = string.characters.reduce("") { underscored, character in
guard character != "\"" else {
return underscored
}
guard "a"..."z" ~= character || "0"..."9" ~= character else {
return underscored + "_"
}
return underscored + String(character)
}
return database(namespace: index)
}
}
extension View {
// MARK: - CREATE VIEW
public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String {
let clauses: [Expressible?] = [
create(View.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists),
Expression<Void>(literal: "AS"),
query
]
return " ".join(clauses.flatMap { $0 }).asSQL()
}
// MARK: - DROP VIEW
public func drop(ifExists: Bool = false) -> String {
return drop("VIEW", tableName(), ifExists)
}
}
extension VirtualTable {
// MARK: - CREATE VIRTUAL TABLE
public func create(_ using: Module, ifNotExists: Bool = false) -> String {
let clauses: [Expressible?] = [
create(VirtualTable.identifier, tableName(), nil, ifNotExists),
Expression<Void>(literal: "USING"),
using
]
return " ".join(clauses.flatMap { $0 }).asSQL()
}
// MARK: - ALTER TABLE RENAME TO
public func rename(_ to: VirtualTable) -> String {
return rename(to: to)
}
}
public final class TableBuilder {
fileprivate var definitions = [Expressible]()
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: Expression<V>? = nil) {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: V) {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool?>, defaultValue: Expression<V>? = nil) {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool?>, defaultValue: V) {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: Expression<V>? = nil) {
column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: Expression<V?>) {
column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: V) {
column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, defaultValue: Expression<V>? = nil) {
column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, defaultValue: Expression<V?>) {
column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, defaultValue: V) {
column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, primaryKey: Bool, check: Expression<Bool>? = nil, defaultValue: Expression<V>? = nil) {
column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, primaryKey: Bool, check: Expression<Bool?>, defaultValue: Expression<V>? = nil) {
column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, primaryKey: PrimaryKey, check: Expression<Bool>? = nil) where V.Datatype == Int64 {
column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, primaryKey: PrimaryKey, check: Expression<Bool?>) where V.Datatype == Int64 {
column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool>? = nil, references table: QueryType, _ other: Expression<V>) where V.Datatype == Int64 {
column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool?>, references table: QueryType, _ other: Expression<V>) where V.Datatype == Int64 {
column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, references table: QueryType, _ other: Expression<V>) where V.Datatype == Int64 {
column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, references table: QueryType, _ other: Expression<V>) where V.Datatype == Int64 {
column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: Expression<V>? = nil, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: V, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool?>, defaultValue: Expression<V>? = nil, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V>, unique: Bool = false, check: Expression<Bool?>, defaultValue: V, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: Expression<V>? = nil, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: Expression<V?>, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool>? = nil, defaultValue: V, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, defaultValue: Expression<V>? = nil, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, defaultValue: Expression<V?>, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
public func column<V : Value>(_ name: Expression<V?>, unique: Bool = false, check: Expression<Bool?>, defaultValue: V, collate: Collation) where V.Datatype == String {
column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate)
}
fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) {
definitions.append(definition(name, datatype, primaryKey, null, unique, check, defaultValue, references, collate))
}
// MARK: -
public func primaryKey<T : Value>(_ column: Expression<T>) {
primaryKey([column])
}
public func primaryKey<T : Value, U : Value>(_ compositeA: Expression<T>, _ b: Expression<U>) {
primaryKey([compositeA, b])
}
public func primaryKey<T : Value, U : Value, V : Value>(_ compositeA: Expression<T>, _ b: Expression<U>, _ c: Expression<V>) {
primaryKey([compositeA, b, c])
}
fileprivate func primaryKey(_ composite: [Expressible]) {
definitions.append("PRIMARY KEY".prefix(composite))
}
public func unique(_ columns: Expressible...) {
unique(columns)
}
public func unique(_ columns: [Expressible]) {
definitions.append("UNIQUE".prefix(columns))
}
public func check(_ condition: Expression<Bool>) {
check(Expression<Bool?>(condition))
}
public func check(_ condition: Expression<Bool?>) {
definitions.append("CHECK".prefix(condition))
}
public enum Dependency: String {
case noAction = "NO ACTION"
case restrict = "RESTRICT"
case setNull = "SET NULL"
case setDefault = "SET DEFAULT"
case cascade = "CASCADE"
}
public func foreignKey<T : Value>(_ column: Expression<T>, references table: QueryType, _ other: Expression<T>, update: Dependency? = nil, delete: Dependency? = nil) {
foreignKey(column, (table, other), update, delete)
}
public func foreignKey<T : Value>(_ column: Expression<T?>, references table: QueryType, _ other: Expression<T>, update: Dependency? = nil, delete: Dependency? = nil) {
foreignKey(column, (table, other), update, delete)
}
public func foreignKey<T : Value, U : Value>(_ composite: (Expression<T>, Expression<U>), references table: QueryType, _ other: (Expression<T>, Expression<U>), update: Dependency? = nil, delete: Dependency? = nil) {
let composite = ", ".join([composite.0, composite.1])
let references = (table, ", ".join([other.0, other.1]))
foreignKey(composite, references, update, delete)
}
public func foreignKey<T : Value, U : Value, V : Value>(_ composite: (Expression<T>, Expression<U>, Expression<V>), references table: QueryType, _ other: (Expression<T>, Expression<U>, Expression<V>), update: Dependency? = nil, delete: Dependency? = nil) {
let composite = ", ".join([composite.0, composite.1, composite.2])
let references = (table, ", ".join([other.0, other.1, other.2]))
foreignKey(composite, references, update, delete)
}
fileprivate func foreignKey(_ column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) {
let clauses: [Expressible?] = [
"FOREIGN KEY".prefix(column),
reference(references),
update.map { Expression<Void>(literal: "ON UPDATE \($0.rawValue)") },
delete.map { Expression<Void>(literal: "ON DELETE \($0.rawValue)") }
]
definitions.append(" ".join(clauses.flatMap { $0 }))
}
}
public enum PrimaryKey {
case `default`
case autoincrement
}
public struct Module {
fileprivate let name: String
fileprivate let arguments: [Expressible]
public init(_ name: String, _ arguments: [Expressible]) {
self.init(name: name.quote(), arguments: arguments)
}
init(name: String, arguments: [Expressible]) {
self.name = name
self.arguments = arguments
}
}
extension Module : Expressible {
public var expression: Expression<Void> {
return name.wrap(arguments)
}
}
// MARK: - Private
private extension QueryType {
func create(_ identifier: String, _ name: Expressible, _ modifier: Modifier?, _ ifNotExists: Bool) -> Expressible {
let clauses: [Expressible?] = [
Expression<Void>(literal: "CREATE"),
modifier.map { Expression<Void>(literal: $0.rawValue) },
Expression<Void>(literal: identifier),
ifNotExists ? Expression<Void>(literal: "IF NOT EXISTS") : nil,
name
]
return " ".join(clauses.flatMap { $0 })
}
func rename(to: Self) -> String {
return " ".join([
Expression<Void>(literal: "ALTER TABLE"),
tableName(),
Expression<Void>(literal: "RENAME TO"),
Expression<Void>(to.clauses.from.name)
]).asSQL()
}
func drop(_ identifier: String, _ name: Expressible, _ ifExists: Bool) -> String {
let clauses: [Expressible?] = [
Expression<Void>(literal: "DROP \(identifier)"),
ifExists ? Expression<Void>(literal: "IF EXISTS") : nil,
name
]
return " ".join(clauses.flatMap { $0 }).asSQL()
}
}
private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible {
let clauses: [Expressible?] = [
column,
Expression<Void>(literal: datatype),
primaryKey.map { Expression<Void>(literal: $0 == .autoincrement ? "PRIMARY KEY AUTOINCREMENT" : "PRIMARY KEY") },
null ? nil : Expression<Void>(literal: "NOT NULL"),
unique ? Expression<Void>(literal: "UNIQUE") : nil,
check.map { " ".join([Expression<Void>(literal: "CHECK"), $0]) },
defaultValue.map { "DEFAULT".prefix($0) },
references.map(reference),
collate.map { " ".join([Expression<Void>(literal: "COLLATE"), $0]) }
]
return " ".join(clauses.flatMap { $0 })
}
private func reference(_ primary: (QueryType, Expressible)) -> Expressible {
return " ".join([
Expression<Void>(literal: "REFERENCES"),
primary.0.tableName(qualified: false),
"".wrap(primary.1) as Expression<Void>
])
}
private enum Modifier : String {
case Unique = "UNIQUE"
case Temporary = "TEMPORARY"
}

View file

@ -0,0 +1,277 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
precedencegroup ColumnAssignment {
associativity: left
assignment: true
lowerThan: AssignmentPrecedence
}
infix operator <- : ColumnAssignment
public struct Setter {
let column: Expressible
let value: Expressible
fileprivate init<V : Value>(column: Expression<V>, value: Expression<V>) {
self.column = column
self.value = value
}
fileprivate init<V : Value>(column: Expression<V>, value: V) {
self.column = column
self.value = value
}
fileprivate init<V : Value>(column: Expression<V?>, value: Expression<V>) {
self.column = column
self.value = value
}
fileprivate init<V : Value>(column: Expression<V?>, value: Expression<V?>) {
self.column = column
self.value = value
}
fileprivate init<V : Value>(column: Expression<V?>, value: V?) {
self.column = column
self.value = Expression<V?>(value: value)
}
}
extension Setter : Expressible {
public var expression: Expression<Void> {
return "=".infix(column, value, wrap: false)
}
}
public func <-<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter {
return Setter(column: column, value: value)
}
public func <-<V : Value>(column: Expression<V>, value: V) -> Setter {
return Setter(column: column, value: value)
}
public func <-<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter {
return Setter(column: column, value: value)
}
public func <-<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter {
return Setter(column: column, value: value)
}
public func <-<V : Value>(column: Expression<V?>, value: V?) -> Setter {
return Setter(column: column, value: value)
}
public func +=(column: Expression<String>, value: Expression<String>) -> Setter {
return column <- column + value
}
public func +=(column: Expression<String>, value: String) -> Setter {
return column <- column + value
}
public func +=(column: Expression<String?>, value: Expression<String>) -> Setter {
return column <- column + value
}
public func +=(column: Expression<String?>, value: Expression<String?>) -> Setter {
return column <- column + value
}
public func +=(column: Expression<String?>, value: String) -> Setter {
return column <- column + value
}
public func +=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column + value
}
public func +=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype : Number {
return column <- column + value
}
public func +=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column + value
}
public func +=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype : Number {
return column <- column + value
}
public func +=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype : Number {
return column <- column + value
}
public func -=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column - value
}
public func -=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype : Number {
return column <- column - value
}
public func -=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column - value
}
public func -=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype : Number {
return column <- column - value
}
public func -=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype : Number {
return column <- column - value
}
public func *=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column * value
}
public func *=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype : Number {
return column <- column * value
}
public func *=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column * value
}
public func *=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype : Number {
return column <- column * value
}
public func *=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype : Number {
return column <- column * value
}
public func /=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column / value
}
public func /=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype : Number {
return column <- column / value
}
public func /=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype : Number {
return column <- column / value
}
public func /=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype : Number {
return column <- column / value
}
public func /=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype : Number {
return column <- column / value
}
public func %=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column % value
}
public func %=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column % value
}
public func %=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column % value
}
public func %=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype == Int64 {
return column <- column % value
}
public func %=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column % value
}
public func <<=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column << value
}
public func <<=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column << value
}
public func <<=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column << value
}
public func <<=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype == Int64 {
return column <- column << value
}
public func <<=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column << value
}
public func >>=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column >> value
}
public func >>=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column >> value
}
public func >>=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column >> value
}
public func >>=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype == Int64 {
return column <- column >> value
}
public func >>=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column >> value
}
public func &=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column & value
}
public func &=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column & value
}
public func &=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column & value
}
public func &=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype == Int64 {
return column <- column & value
}
public func &=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column & value
}
public func |=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column | value
}
public func |=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column | value
}
public func |=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column | value
}
public func |=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype == Int64 {
return column <- column | value
}
public func |=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column | value
}
public func ^=<V : Value>(column: Expression<V>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column ^ value
}
public func ^=<V : Value>(column: Expression<V>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column ^ value
}
public func ^=<V : Value>(column: Expression<V?>, value: Expression<V>) -> Setter where V.Datatype == Int64 {
return column <- column ^ value
}
public func ^=<V : Value>(column: Expression<V?>, value: Expression<V?>) -> Setter where V.Datatype == Int64 {
return column <- column ^ value
}
public func ^=<V : Value>(column: Expression<V?>, value: V) -> Setter where V.Datatype == Int64 {
return column <- column ^ value
}
public postfix func ++<V : Value>(column: Expression<V>) -> Setter where V.Datatype == Int64 {
return Expression<Int>(column) += 1
}
public postfix func ++<V : Value>(column: Expression<V?>) -> Setter where V.Datatype == Int64 {
return Expression<Int>(column) += 1
}
public postfix func --<V : Value>(column: Expression<V>) -> Setter where V.Datatype == Int64 {
return Expression<Int>(column) -= 1
}
public postfix func --<V : Value>(column: Expression<V?>) -> Setter where V.Datatype == Int64 {
return Expression<Int>(column) -= 1
}

View file

@ -0,0 +1,138 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "SQLite-Bridging.h"
#import "fts3_tokenizer.h"
#pragma mark - FTS
typedef struct __SQLiteTokenizer {
sqlite3_tokenizer base;
__unsafe_unretained _SQLiteTokenizerNextCallback callback;
} __SQLiteTokenizer;
typedef struct __SQLiteTokenizerCursor {
void * base;
const char * input;
int inputOffset;
int inputLength;
int idx;
} __SQLiteTokenizerCursor;
static NSMutableDictionary * __SQLiteTokenizerMap;
static int __SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_tokenizer ** ppTokenizer) {
__SQLiteTokenizer * tokenizer = (__SQLiteTokenizer *)sqlite3_malloc(sizeof(__SQLiteTokenizer));
if (!tokenizer) {
return SQLITE_NOMEM;
}
memset(tokenizer, 0, sizeof(* tokenizer));
NSString * key = [NSString stringWithUTF8String:argv[0]];
tokenizer->callback = [__SQLiteTokenizerMap objectForKey:key];
if (!tokenizer->callback) {
return SQLITE_ERROR;
}
*ppTokenizer = &tokenizer->base;
return SQLITE_OK;
}
static int __SQLiteTokenizerDestroy(sqlite3_tokenizer * pTokenizer) {
sqlite3_free(pTokenizer);
return SQLITE_OK;
}
static int __SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pInput, int nBytes, sqlite3_tokenizer_cursor ** ppCursor) {
__SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)sqlite3_malloc(sizeof(__SQLiteTokenizerCursor));
if (!cursor) {
return SQLITE_NOMEM;
}
cursor->input = pInput;
cursor->inputOffset = 0;
cursor->inputLength = 0;
cursor->idx = 0;
*ppCursor = (sqlite3_tokenizer_cursor *)cursor;
return SQLITE_OK;
}
static int __SQLiteTokenizerClose(sqlite3_tokenizer_cursor * pCursor) {
sqlite3_free(pCursor);
return SQLITE_OK;
}
static int __SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char ** ppToken, int * pnBytes, int * piStartOffset, int * piEndOffset, int * piPosition) {
__SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)pCursor;
__SQLiteTokenizer * tokenizer = (__SQLiteTokenizer *)cursor->base;
cursor->inputOffset += cursor->inputLength;
const char * input = cursor->input + cursor->inputOffset;
const char * token = [tokenizer->callback(input, &cursor->inputOffset, &cursor->inputLength) cStringUsingEncoding:NSUTF8StringEncoding];
if (!token) {
return SQLITE_DONE;
}
*ppToken = token;
*pnBytes = (int)strlen(token);
*piStartOffset = cursor->inputOffset;
*piEndOffset = cursor->inputOffset + cursor->inputLength;
*piPosition = cursor->idx++;
return SQLITE_OK;
}
static const sqlite3_tokenizer_module __SQLiteTokenizerModule = {
0,
__SQLiteTokenizerCreate,
__SQLiteTokenizerDestroy,
__SQLiteTokenizerOpen,
__SQLiteTokenizerClose,
__SQLiteTokenizerNext
};
int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__SQLiteTokenizerMap = [NSMutableDictionary new];
});
sqlite3_stmt * stmt;
int status = sqlite3_prepare_v2((sqlite3 *)db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0);
if (status != SQLITE_OK ){
return status;
}
const sqlite3_tokenizer_module * pModule = &__SQLiteTokenizerModule;
sqlite3_bind_text(stmt, 1, moduleName, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 2, &pModule, sizeof(pModule), SQLITE_STATIC);
sqlite3_step(stmt);
status = sqlite3_finalize(stmt);
if (status != SQLITE_OK ){
return status;
}
[__SQLiteTokenizerMap setObject:[callback copy] forKey:[NSString stringWithUTF8String:submoduleName]];
return SQLITE_OK;
}

View file

@ -0,0 +1,161 @@
/*
** 2006 July 10
**
** The author disclaims copyright to this source code.
**
*************************************************************************
** Defines the interface to tokenizers used by fulltext-search. There
** are three basic components:
**
** sqlite3_tokenizer_module is a singleton defining the tokenizer
** interface functions. This is essentially the class structure for
** tokenizers.
**
** sqlite3_tokenizer is used to define a particular tokenizer, perhaps
** including customization information defined at creation time.
**
** sqlite3_tokenizer_cursor is generated by a tokenizer to generate
** tokens from a particular input.
*/
#ifndef _FTS3_TOKENIZER_H_
#define _FTS3_TOKENIZER_H_
/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time.
** If tokenizers are to be allowed to call sqlite3_*() functions, then
** we will need a way to register the API consistently.
*/
#import "sqlite3.h"
/*
** Structures used by the tokenizer interface. When a new tokenizer
** implementation is registered, the caller provides a pointer to
** an sqlite3_tokenizer_module containing pointers to the callback
** functions that make up an implementation.
**
** When an fts3 table is created, it passes any arguments passed to
** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the
** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer
** implementation. The xCreate() function in turn returns an
** sqlite3_tokenizer structure representing the specific tokenizer to
** be used for the fts3 table (customized by the tokenizer clause arguments).
**
** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen()
** method is called. It returns an sqlite3_tokenizer_cursor object
** that may be used to tokenize a specific input buffer based on
** the tokenization rules supplied by a specific sqlite3_tokenizer
** object.
*/
typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
typedef struct sqlite3_tokenizer sqlite3_tokenizer;
typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
struct sqlite3_tokenizer_module {
/*
** Structure version. Should always be set to 0 or 1.
*/
int iVersion;
/*
** Create a new tokenizer. The values in the argv[] array are the
** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL
** TABLE statement that created the fts3 table. For example, if
** the following SQL is executed:
**
** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2)
**
** then argc is set to 2, and the argv[] array contains pointers
** to the strings "arg1" and "arg2".
**
** This method should return either SQLITE_OK (0), or an SQLite error
** code. If SQLITE_OK is returned, then *ppTokenizer should be set
** to point at the newly created tokenizer structure. The generic
** sqlite3_tokenizer.pModule variable should not be initialized by
** this callback. The caller will do so.
*/
int (*xCreate)(
int argc, /* Size of argv array */
const char *const*argv, /* Tokenizer argument strings */
sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
);
/*
** Destroy an existing tokenizer. The fts3 module calls this method
** exactly once for each successful call to xCreate().
*/
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
/*
** Create a tokenizer cursor to tokenize an input buffer. The caller
** is responsible for ensuring that the input buffer remains valid
** until the cursor is closed (using the xClose() method).
*/
int (*xOpen)(
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
);
/*
** Destroy an existing tokenizer cursor. The fts3 module calls this
** method exactly once for each successful call to xOpen().
*/
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
/*
** Retrieve the next token from the tokenizer cursor pCursor. This
** method should either return SQLITE_OK and set the values of the
** "OUT" variables identified below, or SQLITE_DONE to indicate that
** the end of the buffer has been reached, or an SQLite error code.
**
** *ppToken should be set to point at a buffer containing the
** normalized version of the token (i.e. after any case-folding and/or
** stemming has been performed). *pnBytes should be set to the length
** of this buffer in bytes. The input text that generated the token is
** identified by the byte offsets returned in *piStartOffset and
** *piEndOffset. *piStartOffset should be set to the index of the first
** byte of the token in the input buffer. *piEndOffset should be set
** to the index of the first byte just past the end of the token in
** the input buffer.
**
** The buffer *ppToken is set to point at is managed by the tokenizer
** implementation. It is only required to be valid until the next call
** to xNext() or xClose().
*/
/* TODO(shess) current implementation requires pInput to be
** nul-terminated. This should either be fixed, or pInput/nBytes
** should be converted to zInput.
*/
int (*xNext)(
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
int *piPosition /* OUT: Number of tokens returned before this one */
);
/***********************************************************************
** Methods below this point are only available if iVersion>=1.
*/
/*
** Configure the language id of a tokenizer cursor.
*/
int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid);
};
struct sqlite3_tokenizer {
const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
/* Tokenizer implementations will typically add additional fields */
};
struct sqlite3_tokenizer_cursor {
sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
/* Tokenizer implementations will typically add additional fields */
};
int fts3_global_term_cnt(int iTerm, int iCol);
int fts3_term_cnt(int iTerm, int iCol);
#endif /* _FTS3_TOKENIZER_H_ */

View file

@ -0,0 +1,37 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
@import Foundation;
#ifndef COCOAPODS
#import "sqlite3.h"
#endif
typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround
NS_ASSUME_NONNULL_BEGIN
typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength);
int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback);
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1 @@
gems/

View file

@ -0,0 +1,4 @@
source 'https://rubygems.org'
gem 'cocoapods', '~> 1.1.0'
gem 'minitest'

View file

@ -0,0 +1,74 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.3)
activesupport (4.2.7.1)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
claide (1.0.1)
cocoapods (1.1.1)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.1, < 2.0)
cocoapods-core (= 1.1.1)
cocoapods-deintegrate (>= 1.0.1, < 2.0)
cocoapods-downloader (>= 1.1.2, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.1.1, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored (~> 1.2)
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
molinillo (~> 0.5.1)
nap (~> 1.0)
xcodeproj (>= 1.3.3, < 2.0)
cocoapods-core (1.1.1)
activesupport (>= 4.0.2, < 5)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
cocoapods-deintegrate (1.0.1)
cocoapods-downloader (1.1.2)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
cocoapods-stats (1.0.0)
cocoapods-trunk (1.1.1)
nap (>= 0.8, < 2.0)
netrc (= 0.7.8)
cocoapods-try (1.1.0)
colored (1.2)
escape (0.0.4)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.0.2)
i18n (0.7.0)
json (1.8.3)
minitest (5.9.1)
molinillo (0.5.4)
nanaimo (0.2.2)
nap (1.1.0)
netrc (0.7.8)
thread_safe (0.3.5)
tzinfo (1.2.2)
thread_safe (~> 0.1)
xcodeproj (1.4.1)
CFPropertyList (~> 2.3.3)
activesupport (>= 3)
claide (>= 1.0.1, < 2.0)
colored (~> 1.2)
nanaimo (~> 0.2.0)
PLATFORMS
ruby
DEPENDENCIES
cocoapods (~> 1.1.0)
minitest
BUNDLED WITH
1.13.3

View file

@ -0,0 +1,13 @@
test: install repo_update
@set -e; \
for test in *_test.rb; do \
bundle exec ./$$test; \
done
repo_update:
@bundle exec pod repo update --silent
install:
@bundle install --path gems
.PHONY: test install

View file

@ -0,0 +1,43 @@
#!/usr/bin/env ruby
require 'minitest/autorun'
require_relative 'test_running_validator'
class IntegrationTest < Minitest::Test
def test_validate_project
assert validator.validate, "validation failed: #{validator.failure_reason}"
end
private
def validator
@validator ||= TestRunningValidator.new(podspec, []).tap do |validator|
validator.test_files = Dir["#{project_test_dir}/**/*.swift"]
validator.test_resources = Dir["#{project_test_dir}/fixtures"]
validator.config.verbose = true
validator.no_clean = true
validator.use_frameworks = true
validator.fail_fast = true
validator.local = true
validator.allow_warnings = true
subspec = ENV['VALIDATOR_SUBSPEC']
if subspec == 'none'
validator.no_subspecs = true
else
validator.only_subspec = subspec
end
if ENV['IOS_SIMULATOR']
validator.ios_simulator = ENV['IOS_SIMULATOR']
end
end
end
def podspec
File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec')
end
def project_test_dir
File.expand_path(File.dirname(__FILE__) + '/../SQLiteTests')
end
end

View file

@ -0,0 +1,120 @@
require 'cocoapods'
require 'cocoapods/validator'
require 'fileutils'
class TestRunningValidator < Pod::Validator
APP_TARGET = 'App'
TEST_TARGET = 'Tests'
attr_accessor :test_files
attr_accessor :test_resources
attr_accessor :ios_simulator
attr_accessor :tvos_simulator
attr_accessor :watchos_simulator
def initialize(spec_or_path, source_urls)
super(spec_or_path, source_urls)
self.test_files = []
self.test_resources = []
self.ios_simulator = :oldest
self.tvos_simulator = :oldest
self.watchos_simulator = :oldest
end
def create_app_project
super
project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj")
create_test_target(project)
project.save
end
def add_app_project_import
super
project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj')
group = project.new_group(TEST_TARGET)
test_target = project.targets.last
test_target.add_resources(test_resources.map { |resource| group.new_file(resource) })
test_target.add_file_references(test_files.map { |file| group.new_file(file) })
add_swift_version(test_target)
project.save
end
def install_pod
super
if local?
FileUtils.ln_s file.dirname, validation_dir + "Pods/#{spec.name}"
end
end
def podfile_from_spec(*args)
super(*args).tap do |pod_file|
add_test_target(pod_file)
end
end
def build_pod
super
Pod::UI.message "\Testing with xcodebuild.\n".yellow do
run_tests
end
end
private
def create_test_target(project)
test_target = project.new_target(:unit_test_bundle, TEST_TARGET, consumer.platform_name, deployment_target)
create_test_scheme(project, test_target)
end
def create_test_scheme(project, test_target)
project.recreate_user_schemes
test_scheme = Xcodeproj::XCScheme.new(test_scheme_path(project))
test_scheme.add_test_target(test_target)
test_scheme.save!
end
def test_scheme_path(project)
Xcodeproj::XCScheme.user_data_dir(project.path) + "#{TEST_TARGET}.xcscheme"
end
def add_test_target(pod_file)
app_target = pod_file.target_definitions[APP_TARGET]
Pod::Podfile::TargetDefinition.new(TEST_TARGET, app_target)
end
def run_tests
command = [
'clean', 'build', 'build-for-testing', 'test-without-building',
'-workspace', File.join(validation_dir, "#{APP_TARGET}.xcworkspace"),
'-scheme', TEST_TARGET,
'-configuration', 'Debug'
]
case consumer.platform_name
when :ios
command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator)
command += Fourflusher::SimControl.new.destination(ios_simulator, 'iOS', deployment_target)
when :osx
command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks)
when :tvos
command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator)
command += Fourflusher::SimControl.new.destination(tvos_simulator, 'tvOS', deployment_target)
when :watchos
# there's no XCTest on watchOS (https://openradar.appspot.com/21760513)
return
else
return
end
output, status = _xcodebuild(command)
unless status.success?
message = 'Returned an unsuccessful exit code.'
if config.verbose?
message += "\nXcode output: \n#{output}\n"
else
message += ' You can use `--verbose` for more information.'
end
error('xcodebuild', message)
end
output
end
end

View file

@ -0,0 +1,68 @@
import XCTest
import SQLite
class AggregateFunctionsTests : XCTestCase {
func test_distinct_prependsExpressionsWithDistinctKeyword() {
AssertSQL("DISTINCT \"int\"", int.distinct)
AssertSQL("DISTINCT \"intOptional\"", intOptional.distinct)
AssertSQL("DISTINCT \"double\"", double.distinct)
AssertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct)
AssertSQL("DISTINCT \"string\"", string.distinct)
AssertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct)
}
func test_count_wrapsOptionalExpressionsWithCountFunction() {
AssertSQL("count(\"intOptional\")", intOptional.count)
AssertSQL("count(\"doubleOptional\")", doubleOptional.count)
AssertSQL("count(\"stringOptional\")", stringOptional.count)
}
func test_max_wrapsComparableExpressionsWithMaxFunction() {
AssertSQL("max(\"int\")", int.max)
AssertSQL("max(\"intOptional\")", intOptional.max)
AssertSQL("max(\"double\")", double.max)
AssertSQL("max(\"doubleOptional\")", doubleOptional.max)
AssertSQL("max(\"string\")", string.max)
AssertSQL("max(\"stringOptional\")", stringOptional.max)
AssertSQL("max(\"date\")", date.max)
AssertSQL("max(\"dateOptional\")", dateOptional.max)
}
func test_min_wrapsComparableExpressionsWithMinFunction() {
AssertSQL("min(\"int\")", int.min)
AssertSQL("min(\"intOptional\")", intOptional.min)
AssertSQL("min(\"double\")", double.min)
AssertSQL("min(\"doubleOptional\")", doubleOptional.min)
AssertSQL("min(\"string\")", string.min)
AssertSQL("min(\"stringOptional\")", stringOptional.min)
AssertSQL("min(\"date\")", date.min)
AssertSQL("min(\"dateOptional\")", dateOptional.min)
}
func test_average_wrapsNumericExpressionsWithAvgFunction() {
AssertSQL("avg(\"int\")", int.average)
AssertSQL("avg(\"intOptional\")", intOptional.average)
AssertSQL("avg(\"double\")", double.average)
AssertSQL("avg(\"doubleOptional\")", doubleOptional.average)
}
func test_sum_wrapsNumericExpressionsWithSumFunction() {
AssertSQL("sum(\"int\")", int.sum)
AssertSQL("sum(\"intOptional\")", intOptional.sum)
AssertSQL("sum(\"double\")", double.sum)
AssertSQL("sum(\"doubleOptional\")", doubleOptional.sum)
}
func test_total_wrapsNumericExpressionsWithTotalFunction() {
AssertSQL("total(\"int\")", int.total)
AssertSQL("total(\"intOptional\")", intOptional.total)
AssertSQL("total(\"double\")", double.total)
AssertSQL("total(\"doubleOptional\")", doubleOptional.total)
}
func test_count_withStar_wrapsStarWithCountFunction() {
AssertSQL("count(*)", count(*))
}
}

View file

@ -0,0 +1,23 @@
import XCTest
import SQLite
class BlobTests : XCTestCase {
func test_toHex() {
let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255])
XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff")
}
func test_init_array() {
let blob = Blob(bytes: [42, 42, 42])
XCTAssertEqual(blob.bytes, [42, 42, 42])
}
func test_init_unsafeRawPointer() {
let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 3)
pointer.initialize(to: 42, count: 3)
let blob = Blob(bytes: pointer, length: 3)
XCTAssertEqual(blob.bytes, [42, 42, 42])
}
}

View file

@ -0,0 +1,98 @@
#if SQLITE_SWIFT_SQLCIPHER
import XCTest
import SQLite
import SQLCipher
class CipherTests: XCTestCase {
let db1 = try! Connection()
let db2 = try! Connection()
override func setUp() {
// db
try! db1.key("hello")
try! db1.run("CREATE TABLE foo (bar TEXT)")
try! db1.run("INSERT INTO foo (bar) VALUES ('world')")
// db2
let key2 = keyData()
try! db2.key(Blob(bytes: key2.bytes, length: key2.length))
try! db2.run("CREATE TABLE foo (bar TEXT)")
try! db2.run("INSERT INTO foo (bar) VALUES ('world')")
super.setUp()
}
func test_key() {
XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64)
}
func test_key_blob_literal() {
let db = try! Connection()
try! db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'")
}
func test_rekey() {
try! db1.rekey("goodbye")
XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64)
}
func test_data_key() {
XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64)
}
func test_data_rekey() {
let newKey = keyData()
try! db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length))
XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64)
}
func test_keyFailure() {
let path = "\(NSTemporaryDirectory())/db.sqlite3"
_ = try? FileManager.default.removeItem(atPath: path)
let connA = try! Connection(path)
defer { try! FileManager.default.removeItem(atPath: path) }
try! connA.key("hello")
try! connA.run("CREATE TABLE foo (bar TEXT)")
let connB = try! Connection(path, readonly: true)
do {
try connB.key("world")
XCTFail("expected exception")
} catch Result.error(_, let code, _) {
XCTAssertEqual(SQLITE_NOTADB, code)
} catch {
XCTFail("unexpected error: \(error)")
}
}
func test_open_db_encrypted_with_sqlcipher() {
// $ sqlcipher SQLiteTests/fixtures/encrypted.sqlite
// sqlite> pragma key = 'sqlcipher-test';
// sqlite> CREATE TABLE foo (bar TEXT);
// sqlite> INSERT INTO foo (bar) VALUES ('world');
let encryptedFile = fixture("encrypted", withExtension: "sqlite")
try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile)
XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile))
let conn = try! Connection(encryptedFile)
try! conn.key("sqlcipher-test")
XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64)
}
private func keyData(length: Int = 64) -> NSMutableData {
let keyData = NSMutableData(length: length)!
let result = SecRandomCopyBytes(kSecRandomDefault, length,
keyData.mutableBytes.assumingMemoryBound(to: UInt8.self))
XCTAssertEqual(0, result)
return keyData
}
}
#endif

View file

@ -0,0 +1,384 @@
import XCTest
@testable import SQLite
#if SQLITE_SWIFT_STANDALONE
import sqlite3
#elseif SQLITE_SWIFT_SQLCIPHER
import SQLCipher
#else
import SQLite3
#endif
class ConnectionTests : SQLiteTestCase {
override func setUp() {
super.setUp()
CreateUsersTable()
}
func test_init_withInMemory_returnsInMemoryConnection() {
let db = try! Connection(.inMemory)
XCTAssertEqual("", db.description)
}
func test_init_returnsInMemoryByDefault() {
let db = try! Connection()
XCTAssertEqual("", db.description)
}
func test_init_withTemporary_returnsTemporaryConnection() {
let db = try! Connection(.temporary)
XCTAssertEqual("", db.description)
}
func test_init_withURI_returnsURIConnection() {
let db = try! Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3"))
XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description)
}
func test_init_withString_returnsURIConnection() {
let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")
XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description)
}
func test_readonly_returnsFalseOnReadWriteConnections() {
XCTAssertFalse(db.readonly)
}
func test_readonly_returnsTrueOnReadOnlyConnections() {
let db = try! Connection(readonly: true)
XCTAssertTrue(db.readonly)
}
func test_changes_returnsZeroOnNewConnections() {
XCTAssertEqual(0, db.changes)
}
func test_lastInsertRowid_returnsLastIdAfterInserts() {
try! InsertUser("alice")
XCTAssertEqual(1, db.lastInsertRowid)
}
func test_lastInsertRowid_doesNotResetAfterError() {
XCTAssert(db.lastInsertRowid == 0)
try! InsertUser("alice")
XCTAssertEqual(1, db.lastInsertRowid)
XCTAssertThrowsError(
try db.run("INSERT INTO \"users\" (email, age, admin) values ('invalid@example.com', 12, 'invalid')")
) { error in
if case SQLite.Result.error(_, let code, _) = error {
XCTAssertEqual(SQLITE_CONSTRAINT, code)
} else {
XCTFail("expected error")
}
}
XCTAssertEqual(1, db.lastInsertRowid)
}
func test_changes_returnsNumberOfChanges() {
try! InsertUser("alice")
XCTAssertEqual(1, db.changes)
try! InsertUser("betsy")
XCTAssertEqual(1, db.changes)
}
func test_totalChanges_returnsTotalNumberOfChanges() {
XCTAssertEqual(0, db.totalChanges)
try! InsertUser("alice")
XCTAssertEqual(1, db.totalChanges)
try! InsertUser("betsy")
XCTAssertEqual(2, db.totalChanges)
}
func test_prepare_preparesAndReturnsStatements() {
_ = try! db.prepare("SELECT * FROM users WHERE admin = 0")
_ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0)
_ = try! db.prepare("SELECT * FROM users WHERE admin = ?", [0])
_ = try! db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0])
}
func test_run_preparesRunsAndReturnsStatements() {
try! db.run("SELECT * FROM users WHERE admin = 0")
try! db.run("SELECT * FROM users WHERE admin = ?", 0)
try! db.run("SELECT * FROM users WHERE admin = ?", [0])
try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0])
AssertSQL("SELECT * FROM users WHERE admin = 0", 4)
}
func test_scalar_preparesRunsAndReturnsScalarValues() {
XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64)
XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64)
XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64)
XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64)
AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4)
}
func test_execute_comment() {
try! db.run("-- this is a comment\nSELECT 1")
AssertSQL("-- this is a comment", 0)
AssertSQL("SELECT 1", 0)
}
func test_transaction_executesBeginDeferred() {
try! db.transaction(.deferred) {}
AssertSQL("BEGIN DEFERRED TRANSACTION")
}
func test_transaction_executesBeginImmediate() {
try! db.transaction(.immediate) {}
AssertSQL("BEGIN IMMEDIATE TRANSACTION")
}
func test_transaction_executesBeginExclusive() {
try! db.transaction(.exclusive) {}
AssertSQL("BEGIN EXCLUSIVE TRANSACTION")
}
func test_transaction_beginsAndCommitsTransactions() {
let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com")
try! db.transaction {
try stmt.run()
}
AssertSQL("BEGIN DEFERRED TRANSACTION")
AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')")
AssertSQL("COMMIT TRANSACTION")
AssertSQL("ROLLBACK TRANSACTION", 0)
}
func test_transaction_beginsAndRollsTransactionsBack() {
let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com")
do {
try db.transaction {
try stmt.run()
try stmt.run()
}
} catch {
}
AssertSQL("BEGIN DEFERRED TRANSACTION")
AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2)
AssertSQL("ROLLBACK TRANSACTION")
AssertSQL("COMMIT TRANSACTION", 0)
}
func test_savepoint_beginsAndCommitsSavepoints() {
let db = self.db
try! db.savepoint("1") {
try db.savepoint("2") {
try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com")
}
}
AssertSQL("SAVEPOINT '1'")
AssertSQL("SAVEPOINT '2'")
AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')")
AssertSQL("RELEASE SAVEPOINT '2'")
AssertSQL("RELEASE SAVEPOINT '1'")
AssertSQL("ROLLBACK TO SAVEPOINT '2'", 0)
AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0)
}
func test_savepoint_beginsAndRollsSavepointsBack() {
let db = self.db
let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com")
do {
try db.savepoint("1") {
try db.savepoint("2") {
try stmt.run()
try stmt.run()
try stmt.run()
}
try db.savepoint("2") {
try stmt.run()
try stmt.run()
try stmt.run()
}
}
} catch {
}
AssertSQL("SAVEPOINT '1'")
AssertSQL("SAVEPOINT '2'")
AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2)
AssertSQL("ROLLBACK TO SAVEPOINT '2'")
AssertSQL("ROLLBACK TO SAVEPOINT '1'")
AssertSQL("RELEASE SAVEPOINT '2'", 0)
AssertSQL("RELEASE SAVEPOINT '1'", 0)
}
func test_updateHook_setsUpdateHook_withInsert() {
async { done in
db.updateHook { operation, db, table, rowid in
XCTAssertEqual(Connection.Operation.insert, operation)
XCTAssertEqual("main", db)
XCTAssertEqual("users", table)
XCTAssertEqual(1, rowid)
done()
}
try! InsertUser("alice")
}
}
func test_updateHook_setsUpdateHook_withUpdate() {
try! InsertUser("alice")
async { done in
db.updateHook { operation, db, table, rowid in
XCTAssertEqual(Connection.Operation.update, operation)
XCTAssertEqual("main", db)
XCTAssertEqual("users", table)
XCTAssertEqual(1, rowid)
done()
}
try! db.run("UPDATE users SET email = 'alice@example.com'")
}
}
func test_updateHook_setsUpdateHook_withDelete() {
try! InsertUser("alice")
async { done in
db.updateHook { operation, db, table, rowid in
XCTAssertEqual(Connection.Operation.delete, operation)
XCTAssertEqual("main", db)
XCTAssertEqual("users", table)
XCTAssertEqual(1, rowid)
done()
}
try! db.run("DELETE FROM users WHERE id = 1")
}
}
func test_commitHook_setsCommitHook() {
async { done in
db.commitHook {
done()
}
try! db.transaction {
try self.InsertUser("alice")
}
XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64)
}
}
func test_rollbackHook_setsRollbackHook() {
async { done in
db.rollbackHook(done)
do {
try db.transaction {
try self.InsertUser("alice")
try self.InsertUser("alice") // throw
}
} catch {
}
XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64)
}
}
func test_commitHook_withRollback_rollsBack() {
async { done in
db.commitHook {
throw NSError(domain: "com.stephencelis.SQLiteTests", code: 1, userInfo: nil)
}
db.rollbackHook(done)
do {
try db.transaction {
try self.InsertUser("alice")
}
} catch {
}
XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64)
}
}
func test_createFunction_withArrayArguments() {
db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } }
XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String)
XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil)
}
func test_createFunction_createsQuotableFunction() {
db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } }
XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String)
XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil)
}
func test_createCollation_createsCollation() {
try! db.createCollation("NODIACRITIC") { lhs, rhs in
return lhs.compare(rhs, options: .diacriticInsensitive)
}
XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64)
}
func test_createCollation_createsQuotableCollation() {
try! db.createCollation("NO DIACRITIC") { lhs, rhs in
return lhs.compare(rhs, options: .diacriticInsensitive)
}
XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64)
}
func test_interrupt_interruptsLongRunningQuery() {
try! InsertUsers("abcdefghijklmnopqrstuvwxyz".characters.map { String($0) })
db.createFunction("sleep") { args in
usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000))
return nil
}
let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1)
try! stmt.run()
let deadline = DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC)
_ = DispatchQueue.global(priority: .background).asyncAfter(deadline: deadline, execute: db.interrupt)
AssertThrows(try stmt.run())
}
}
class ResultTests : XCTestCase {
let connection = try! Connection(.inMemory)
func test_init_with_ok_code_returns_nil() {
XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?)
}
func test_init_with_row_code_returns_nil() {
XCTAssertNil(Result(errorCode: SQLITE_ROW, connection: connection, statement: nil) as Result?)
}
func test_init_with_done_code_returns_nil() {
XCTAssertNil(Result(errorCode: SQLITE_DONE, connection: connection, statement: nil) as Result?)
}
func test_init_with_other_code_returns_error() {
if case .some(.error(let message, let code, let statement)) =
Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) {
XCTAssertEqual("not an error", message)
XCTAssertEqual(SQLITE_MISUSE, code)
XCTAssertNil(statement)
XCTAssert(self.connection === connection)
} else {
XCTFail()
}
}
func test_description_contains_error_code() {
XCTAssertEqual("not an error (code: 21)",
Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description)
}
func test_description_contains_statement_and_error_code() {
let statement = try! Statement(connection, "SELECT 1")
XCTAssertEqual("not an error (SELECT 1) (code: 21)",
Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description)
}
}

View file

@ -0,0 +1,136 @@
import XCTest
import SQLite
class CoreFunctionsTests : XCTestCase {
func test_round_wrapsDoubleExpressionsWithRoundFunction() {
AssertSQL("round(\"double\")", double.round())
AssertSQL("round(\"doubleOptional\")", doubleOptional.round())
AssertSQL("round(\"double\", 1)", double.round(1))
AssertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2))
}
func test_random_generatesExpressionWithRandomFunction() {
AssertSQL("random()", Expression<Int64>.random())
AssertSQL("random()", Expression<Int>.random())
}
func test_length_wrapsStringExpressionWithLengthFunction() {
AssertSQL("length(\"string\")", string.length)
AssertSQL("length(\"stringOptional\")", stringOptional.length)
}
func test_lowercaseString_wrapsStringExpressionWithLowerFunction() {
AssertSQL("lower(\"string\")", string.lowercaseString)
AssertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString)
}
func test_uppercaseString_wrapsStringExpressionWithUpperFunction() {
AssertSQL("upper(\"string\")", string.uppercaseString)
AssertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString)
}
func test_like_buildsExpressionWithLikeOperator() {
AssertSQL("(\"string\" LIKE 'a%')", string.like("a%"))
AssertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%"))
AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\"))
AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\"))
}
func test_glob_buildsExpressionWithGlobOperator() {
AssertSQL("(\"string\" GLOB 'a*')", string.glob("a*"))
AssertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*"))
}
func test_match_buildsExpressionWithMatchOperator() {
AssertSQL("(\"string\" MATCH 'a*')", string.match("a*"))
AssertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*"))
}
func test_regexp_buildsExpressionWithRegexpOperator() {
AssertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$"))
AssertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$"))
}
func test_collate_buildsExpressionWithCollateOperator() {
AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.binary))
AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.nocase))
AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.rtrim))
AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.custom("CUSTOM")))
AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.binary))
AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.nocase))
AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.rtrim))
AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.custom("CUSTOM")))
}
func test_ltrim_wrapsStringWithLtrimFunction() {
AssertSQL("ltrim(\"string\")", string.ltrim())
AssertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim())
AssertSQL("ltrim(\"string\", ' ')", string.ltrim([" "]))
AssertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "]))
}
func test_ltrim_wrapsStringWithRtrimFunction() {
AssertSQL("rtrim(\"string\")", string.rtrim())
AssertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim())
AssertSQL("rtrim(\"string\", ' ')", string.rtrim([" "]))
AssertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "]))
}
func test_ltrim_wrapsStringWithTrimFunction() {
AssertSQL("trim(\"string\")", string.trim())
AssertSQL("trim(\"stringOptional\")", stringOptional.trim())
AssertSQL("trim(\"string\", ' ')", string.trim([" "]))
AssertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "]))
}
func test_replace_wrapsStringWithReplaceFunction() {
AssertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net"))
AssertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com"))
}
func test_substring_wrapsStringWithSubstrFunction() {
AssertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2))
AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1))
}
func test_subscriptWithRange_wrapsStringWithSubstrFunction() {
AssertSQL("substr(\"string\", 1, 2)", string[1..<3])
AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3])
}
func test_nilCoalescingOperator_wrapsOptionalsWithIfnullFunction() {
AssertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1)
// AssertSQL("ifnull(\"doubleOptional\", 1.0)", doubleOptional ?? 1) // rdar://problem/21677256
XCTAssertEqual("ifnull(\"doubleOptional\", 1.0)", (doubleOptional ?? 1).asSQL())
AssertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal")
AssertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int)
AssertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double)
AssertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string)
AssertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional)
AssertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional)
AssertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional)
}
func test_absoluteValue_wrapsNumberWithAbsFucntion() {
AssertSQL("abs(\"int\")", int.absoluteValue)
AssertSQL("abs(\"intOptional\")", intOptional.absoluteValue)
AssertSQL("abs(\"double\")", double.absoluteValue)
AssertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue)
}
func test_contains_buildsExpressionWithInOperator() {
AssertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string))
AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional))
}
}

View file

@ -0,0 +1,6 @@
import XCTest
import SQLite
class CustomFunctionsTests : XCTestCase {
}

View file

@ -0,0 +1,6 @@
import XCTest
import SQLite
class ExpressionTests : XCTestCase {
}

View file

@ -0,0 +1,208 @@
import XCTest
import SQLite
class FTS4Tests : XCTestCase {
func test_create_onVirtualTable_withFTS4_compilesCreateVirtualTableExpression() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4()",
virtualTable.create(.FTS4())
)
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\")",
virtualTable.create(.FTS4(string))
)
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=simple)",
virtualTable.create(.FTS4(tokenize: .Simple))
)
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", tokenize=porter)",
virtualTable.create(.FTS4([string], tokenize: .Porter))
)
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=0\")",
virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false)))
)
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")",
virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))
)
}
func test_match_onVirtualTableAsExpression_compilesMatchExpression() {
AssertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression<Bool>)
AssertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression<Bool>)
AssertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression<Bool?>)
}
func test_match_onVirtualTableAsQueryType_compilesMatchExpression() {
AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", virtualTable.match("string") as QueryType)
AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as QueryType)
AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as QueryType)
}
}
class FTS4ConfigTests : XCTestCase {
var config: FTS4Config!
override func setUp() {
super.setUp()
config = FTS4Config()
}
func test_empty_config() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4()",
sql(config))
}
func test_config_column() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\")",
sql(config.column(string)))
}
func test_config_columns() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", \"int\")",
sql(config.columns([string, int])))
}
func test_config_unindexed_column() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", notindexed=\"string\")",
sql(config.column(string, [.unindexed])))
}
func test_external_content_view() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"view\")",
sql(config.externalContent(_view )))
}
func test_external_content_virtual_table() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"virtual_table\")",
sql(config.externalContent(virtualTable)))
}
func test_tokenizer_simple() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=simple)",
sql(config.tokenizer(.Simple)))
}
func test_tokenizer_porter() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=porter)",
sql(config.tokenizer(.Porter)))
}
func test_tokenizer_unicode61() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61)",
sql(config.tokenizer(.Unicode61())))
}
func test_tokenizer_unicode61_with_options() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")",
sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"]))))
}
func test_content_less() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"\")",
sql(config.contentless()))
}
func test_config_matchinfo() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(matchinfo=\"fts3\")",
sql(config.matchInfo(.fts3)))
}
func test_config_order_asc() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"asc\")",
sql(config.order(.asc)))
}
func test_config_order_desc() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"desc\")",
sql(config.order(.desc)))
}
func test_config_compress() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(compress=\"compress_foo\")",
sql(config.compress("compress_foo")))
}
func test_config_uncompress() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(uncompress=\"uncompress_foo\")",
sql(config.uncompress("uncompress_foo")))
}
func test_config_languageId() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(languageid=\"lid\")",
sql(config.languageId("lid")))
}
func test_config_all() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\")",
sql(config
.tokenizer(.Porter)
.column(int)
.column(string, [.unindexed])
.column(date, [.unindexed])
.externalContent(table)
.matchInfo(.fts3)
.languageId("lid")
.order(.desc)
.prefix([2, 4]))
)
}
func sql(_ config: FTS4Config) -> String {
return virtualTable.create(.FTS4(config))
}
}
class FTS4IntegrationTests : SQLiteTestCase {
#if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER
func test_registerTokenizer_registersTokenizer() {
let emails = VirtualTable("emails")
let subject = Expression<String?>("subject")
let body = Expression<String?>("body")
let locale = CFLocaleCopyCurrent()
let tokenizerName = "tokenizer"
let tokenizer = CFStringTokenizerCreate(nil, "" as CFString!, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale)
try! db.registerTokenizer(tokenizerName) { string in
CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString)))
if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty {
return nil
}
let range = CFStringTokenizerGetCurrentTokenRange(tokenizer)
let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string as CFString, range)!
let token = CFStringCreateMutableCopy(nil, range.length, input)!
CFStringLowercase(token, locale)
CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false)
return (token as String, string.range(of: input as String)!)
}
try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName))))
AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")")
try! _ = db.run(emails.insert(subject <- "Aún más cáfe!"))
XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count))
}
#endif
}

View file

@ -0,0 +1,124 @@
import XCTest
import SQLite
class FTS5Tests: XCTestCase {
var config: FTS5Config!
override func setUp() {
super.setUp()
config = FTS5Config()
}
func test_empty_config() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5()",
sql(config))
}
func test_config_column() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\")",
sql(config.column(string)))
}
func test_config_columns() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\", \"int\")",
sql(config.columns([string, int])))
}
func test_config_unindexed_column() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\" UNINDEXED)",
sql(config.column(string, [.unindexed])))
}
func test_external_content_table() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"table\")",
sql(config.externalContent(table)))
}
func test_external_content_view() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"view\")",
sql(config.externalContent(_view)))
}
func test_external_content_virtual_table() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"virtual_table\")",
sql(config.externalContent(virtualTable)))
}
func test_content_less() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"\")",
sql(config.contentless()))
}
func test_content_rowid() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content_rowid=\"string\")",
sql(config.contentRowId(string)))
}
func test_tokenizer_porter() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=porter)",
sql(config.tokenizer(.Porter)))
}
func test_tokenizer_unicode61() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61)",
sql(config.tokenizer(.Unicode61())))
}
func test_tokenizer_unicode61_with_options() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")",
sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"]))))
}
func test_column_size() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(columnsize=1)",
sql(config.columnSize(1)))
}
func test_detail_full() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"full\")",
sql(config.detail(.full)))
}
func test_detail_column() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"column\")",
sql(config.detail(.column)))
}
func test_detail_none() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"none\")",
sql(config.detail(.none)))
}
func test_fts5_config_all() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, tokenize=porter, prefix=\"2,4\", content=\"table\")",
sql(config
.tokenizer(.Porter)
.column(int)
.column(string, [.unindexed])
.column(date, [.unindexed])
.externalContent(table)
.prefix([2, 4]))
)
}
func sql(_ config: FTS5Config) -> String {
return virtualTable.create(.FTS5(config))
}
}

View file

@ -0,0 +1,8 @@
import Foundation
func fixture(_ name: String, withExtension: String?) -> String {
let testBundle = Bundle(for: SQLiteTestCase.self)
return testBundle.url(
forResource: URL(string: "fixtures")?.appendingPathComponent(name).path,
withExtension: withExtension)!.path
}

View file

@ -0,0 +1,16 @@
import XCTest
import SQLite
class FoundationTests : XCTestCase {
func testDataFromBlob() {
let data = Data(bytes: [1, 2, 3])
let blob = data.datatypeValue
XCTAssertEqual([1, 2, 3], blob.bytes)
}
func testBlobToData() {
let blob = Blob(bytes: [1, 2, 3])
let data = Data.fromDatatypeValue(blob)
XCTAssertEqual(Data(bytes: [1, 2, 3]), data)
}
}

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -0,0 +1,296 @@
import XCTest
import SQLite
class OperatorsTests : XCTestCase {
func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() {
AssertSQL("(\"string\" || \"string\")", string + string)
AssertSQL("(\"string\" || \"stringOptional\")", string + stringOptional)
AssertSQL("(\"stringOptional\" || \"string\")", stringOptional + string)
AssertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional)
AssertSQL("(\"string\" || 'literal')", string + "literal")
AssertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal")
AssertSQL("('literal' || \"string\")", "literal" + string)
AssertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional)
}
func test_numberExpression_plusNumberExpression_buildsAdditiveNumberExpression() {
AssertSQL("(\"int\" + \"int\")", int + int)
AssertSQL("(\"int\" + \"intOptional\")", int + intOptional)
AssertSQL("(\"intOptional\" + \"int\")", intOptional + int)
AssertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional)
AssertSQL("(\"int\" + 1)", int + 1)
AssertSQL("(\"intOptional\" + 1)", intOptional + 1)
AssertSQL("(1 + \"int\")", 1 + int)
AssertSQL("(1 + \"intOptional\")", 1 + intOptional)
AssertSQL("(\"double\" + \"double\")", double + double)
AssertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional)
AssertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double)
AssertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional)
AssertSQL("(\"double\" + 1.0)", double + 1)
AssertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1)
AssertSQL("(1.0 + \"double\")", 1 + double)
AssertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional)
}
func test_numberExpression_minusNumberExpression_buildsSubtractiveNumberExpression() {
AssertSQL("(\"int\" - \"int\")", int - int)
AssertSQL("(\"int\" - \"intOptional\")", int - intOptional)
AssertSQL("(\"intOptional\" - \"int\")", intOptional - int)
AssertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional)
AssertSQL("(\"int\" - 1)", int - 1)
AssertSQL("(\"intOptional\" - 1)", intOptional - 1)
AssertSQL("(1 - \"int\")", 1 - int)
AssertSQL("(1 - \"intOptional\")", 1 - intOptional)
AssertSQL("(\"double\" - \"double\")", double - double)
AssertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional)
AssertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double)
AssertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional)
AssertSQL("(\"double\" - 1.0)", double - 1)
AssertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1)
AssertSQL("(1.0 - \"double\")", 1 - double)
AssertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional)
}
func test_numberExpression_timesNumberExpression_buildsMultiplicativeNumberExpression() {
AssertSQL("(\"int\" * \"int\")", int * int)
AssertSQL("(\"int\" * \"intOptional\")", int * intOptional)
AssertSQL("(\"intOptional\" * \"int\")", intOptional * int)
AssertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional)
AssertSQL("(\"int\" * 1)", int * 1)
AssertSQL("(\"intOptional\" * 1)", intOptional * 1)
AssertSQL("(1 * \"int\")", 1 * int)
AssertSQL("(1 * \"intOptional\")", 1 * intOptional)
AssertSQL("(\"double\" * \"double\")", double * double)
AssertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional)
AssertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double)
AssertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional)
AssertSQL("(\"double\" * 1.0)", double * 1)
AssertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1)
AssertSQL("(1.0 * \"double\")", 1 * double)
AssertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional)
}
func test_numberExpression_dividedByNumberExpression_buildsDivisiveNumberExpression() {
AssertSQL("(\"int\" / \"int\")", int / int)
AssertSQL("(\"int\" / \"intOptional\")", int / intOptional)
AssertSQL("(\"intOptional\" / \"int\")", intOptional / int)
AssertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional)
AssertSQL("(\"int\" / 1)", int / 1)
AssertSQL("(\"intOptional\" / 1)", intOptional / 1)
AssertSQL("(1 / \"int\")", 1 / int)
AssertSQL("(1 / \"intOptional\")", 1 / intOptional)
AssertSQL("(\"double\" / \"double\")", double / double)
AssertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional)
AssertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double)
AssertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional)
AssertSQL("(\"double\" / 1.0)", double / 1)
AssertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1)
AssertSQL("(1.0 / \"double\")", 1 / double)
AssertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional)
}
func test_numberExpression_prefixedWithMinus_buildsInvertedNumberExpression() {
AssertSQL("-(\"int\")", -int)
AssertSQL("-(\"intOptional\")", -intOptional)
AssertSQL("-(\"double\")", -double)
AssertSQL("-(\"doubleOptional\")", -doubleOptional)
}
func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() {
AssertSQL("(\"int\" % \"int\")", int % int)
AssertSQL("(\"int\" % \"intOptional\")", int % intOptional)
AssertSQL("(\"intOptional\" % \"int\")", intOptional % int)
AssertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional)
AssertSQL("(\"int\" % 1)", int % 1)
AssertSQL("(\"intOptional\" % 1)", intOptional % 1)
AssertSQL("(1 % \"int\")", 1 % int)
AssertSQL("(1 % \"intOptional\")", 1 % intOptional)
}
func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() {
AssertSQL("(\"int\" << \"int\")", int << int)
AssertSQL("(\"int\" << \"intOptional\")", int << intOptional)
AssertSQL("(\"intOptional\" << \"int\")", intOptional << int)
AssertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional)
AssertSQL("(\"int\" << 1)", int << 1)
AssertSQL("(\"intOptional\" << 1)", intOptional << 1)
AssertSQL("(1 << \"int\")", 1 << int)
AssertSQL("(1 << \"intOptional\")", 1 << intOptional)
}
func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() {
AssertSQL("(\"int\" >> \"int\")", int >> int)
AssertSQL("(\"int\" >> \"intOptional\")", int >> intOptional)
AssertSQL("(\"intOptional\" >> \"int\")", intOptional >> int)
AssertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional)
AssertSQL("(\"int\" >> 1)", int >> 1)
AssertSQL("(\"intOptional\" >> 1)", intOptional >> 1)
AssertSQL("(1 >> \"int\")", 1 >> int)
AssertSQL("(1 >> \"intOptional\")", 1 >> intOptional)
}
func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() {
AssertSQL("(\"int\" & \"int\")", int & int)
AssertSQL("(\"int\" & \"intOptional\")", int & intOptional)
AssertSQL("(\"intOptional\" & \"int\")", intOptional & int)
AssertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional)
AssertSQL("(\"int\" & 1)", int & 1)
AssertSQL("(\"intOptional\" & 1)", intOptional & 1)
AssertSQL("(1 & \"int\")", 1 & int)
AssertSQL("(1 & \"intOptional\")", 1 & intOptional)
}
func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() {
AssertSQL("(\"int\" | \"int\")", int | int)
AssertSQL("(\"int\" | \"intOptional\")", int | intOptional)
AssertSQL("(\"intOptional\" | \"int\")", intOptional | int)
AssertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional)
AssertSQL("(\"int\" | 1)", int | 1)
AssertSQL("(\"intOptional\" | 1)", intOptional | 1)
AssertSQL("(1 | \"int\")", 1 | int)
AssertSQL("(1 | \"intOptional\")", 1 | intOptional)
}
func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() {
AssertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int)
AssertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional)
AssertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int)
AssertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional)
AssertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1)
AssertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1)
AssertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int)
AssertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional)
}
func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() {
AssertSQL("~(\"int\")", ~int)
AssertSQL("~(\"intOptional\")", ~intOptional)
}
func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() {
AssertSQL("(\"bool\" = \"bool\")", bool == bool)
AssertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional)
AssertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool)
AssertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional)
AssertSQL("(\"bool\" = 1)", bool == true)
AssertSQL("(\"boolOptional\" = 1)", boolOptional == true)
AssertSQL("(1 = \"bool\")", true == bool)
AssertSQL("(1 = \"boolOptional\")", true == boolOptional)
AssertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil)
AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional)
}
func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() {
AssertSQL("(\"bool\" != \"bool\")", bool != bool)
AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional)
AssertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool)
AssertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional)
AssertSQL("(\"bool\" != 1)", bool != true)
AssertSQL("(\"boolOptional\" != 1)", boolOptional != true)
AssertSQL("(1 != \"bool\")", true != bool)
AssertSQL("(1 != \"boolOptional\")", true != boolOptional)
AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil)
AssertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional)
}
func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() {
AssertSQL("(\"bool\" > \"bool\")", bool > bool)
AssertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional)
AssertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool)
AssertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional)
AssertSQL("(\"bool\" > 1)", bool > true)
AssertSQL("(\"boolOptional\" > 1)", boolOptional > true)
AssertSQL("(1 > \"bool\")", true > bool)
AssertSQL("(1 > \"boolOptional\")", true > boolOptional)
}
func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() {
AssertSQL("(\"bool\" >= \"bool\")", bool >= bool)
AssertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional)
AssertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool)
AssertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional)
AssertSQL("(\"bool\" >= 1)", bool >= true)
AssertSQL("(\"boolOptional\" >= 1)", boolOptional >= true)
AssertSQL("(1 >= \"bool\")", true >= bool)
AssertSQL("(1 >= \"boolOptional\")", true >= boolOptional)
}
func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() {
AssertSQL("(\"bool\" < \"bool\")", bool < bool)
AssertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional)
AssertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool)
AssertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional)
AssertSQL("(\"bool\" < 1)", bool < true)
AssertSQL("(\"boolOptional\" < 1)", boolOptional < true)
AssertSQL("(1 < \"bool\")", true < bool)
AssertSQL("(1 < \"boolOptional\")", true < boolOptional)
}
func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() {
AssertSQL("(\"bool\" <= \"bool\")", bool <= bool)
AssertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional)
AssertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool)
AssertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional)
AssertSQL("(\"bool\" <= 1)", bool <= true)
AssertSQL("(\"boolOptional\" <= 1)", boolOptional <= true)
AssertSQL("(1 <= \"bool\")", true <= bool)
AssertSQL("(1 <= \"boolOptional\")", true <= boolOptional)
}
func test_patternMatchingOperator_withComparableCountableClosedRange_buildsBetweenBooleanExpression() {
AssertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int)
AssertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional)
}
func test_patternMatchingOperator_withComparableClosedRange_buildsBetweenBooleanExpression() {
AssertSQL("\"double\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= double)
AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional)
}
func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() {
AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string)
AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional)
}
func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() {
AssertSQL("(\"bool\" AND \"bool\")", bool && bool)
AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional)
AssertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool)
AssertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional)
AssertSQL("(\"bool\" AND 1)", bool && true)
AssertSQL("(\"boolOptional\" AND 1)", boolOptional && true)
AssertSQL("(1 AND \"bool\")", true && bool)
AssertSQL("(1 AND \"boolOptional\")", true && boolOptional)
}
func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() {
AssertSQL("(\"bool\" OR \"bool\")", bool || bool)
AssertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional)
AssertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool)
AssertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional)
AssertSQL("(\"bool\" OR 1)", bool || true)
AssertSQL("(\"boolOptional\" OR 1)", boolOptional || true)
AssertSQL("(1 OR \"bool\")", true || bool)
AssertSQL("(1 OR \"boolOptional\")", true || boolOptional)
}
func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() {
AssertSQL("NOT (\"bool\")", !bool)
AssertSQL("NOT (\"boolOptional\")", !boolOptional)
}
func test_precedencePreserved() {
let n = Expression<Int>(value: 1)
AssertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n)
AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n))
}
}

View file

@ -0,0 +1,365 @@
import XCTest
import SQLite
class QueryTests : XCTestCase {
let users = Table("users")
let id = Expression<Int64>("id")
let email = Expression<String>("email")
let age = Expression<Int?>("age")
let admin = Expression<Bool>("admin")
let optionalAdmin = Expression<Bool?>("admin")
let posts = Table("posts")
let userId = Expression<Int64>("user_id")
let categoryId = Expression<Int64>("category_id")
let published = Expression<Bool>("published")
let categories = Table("categories")
let tag = Expression<String>("tag")
func test_select_withExpression_compilesSelectClause() {
AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email))
}
func test_select_withStarExpression_compilesSelectClause() {
AssertSQL("SELECT * FROM \"users\"", users.select(*))
}
func test_select_withNamespacedStarExpression_compilesSelectClause() {
AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*]))
}
func test_select_withVariadicExpressions_compilesSelectClause() {
AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*)))
}
func test_select_withExpressions_compilesSelectClause() {
AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select([email, count(*)]))
}
func test_selectDistinct_withExpression_compilesSelectClause() {
AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age))
}
func test_selectDistinct_withExpressions_compilesSelectClause() {
AssertSQL("SELECT DISTINCT \"age\", \"admin\" FROM \"users\"", users.select(distinct: [age, admin]))
}
func test_selectDistinct_withStar_compilesSelectClause() {
AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *))
}
func test_join_compilesJoinClause() {
AssertSQL(
"SELECT * FROM \"users\" INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")",
users.join(posts, on: posts[userId] == users[id])
)
}
func test_join_withExplicitType_compilesJoinClauseWithType() {
AssertSQL(
"SELECT * FROM \"users\" LEFT OUTER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")",
users.join(.leftOuter, posts, on: posts[userId] == users[id])
)
AssertSQL(
"SELECT * FROM \"users\" CROSS JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")",
users.join(.cross, posts, on: posts[userId] == users[id])
)
}
func test_join_withTableCondition_compilesJoinClauseWithTableCondition() {
AssertSQL(
"SELECT * FROM \"users\" INNER JOIN \"posts\" ON ((\"posts\".\"user_id\" = \"users\".\"id\") AND \"published\")",
users.join(posts.filter(published), on: posts[userId] == users[id])
)
}
func test_join_whenChained_compilesAggregateJoinClause() {
AssertSQL(
"SELECT * FROM \"users\" " +
"INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\") " +
"INNER JOIN \"categories\" ON (\"categories\".\"id\" = \"posts\".\"category_id\")",
users.join(posts, on: posts[userId] == users[id]).join(categories, on: categories[id] == posts[categoryId])
)
}
func test_filter_compilesWhereClause() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true))
}
func test_filter_compilesWhereClause_false() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(admin == false))
}
func test_filter_compilesWhereClause_optional() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(optionalAdmin == true))
}
func test_filter_compilesWhereClause_optional_false() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(optionalAdmin == false))
}
func test_where_compilesWhereClause() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(admin == true))
}
func test_where_compilesWhereClause_false() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(admin == false))
}
func test_where_compilesWhereClause_optional() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(optionalAdmin == true))
}
func test_where_compilesWhereClause_optional_false() {
AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(optionalAdmin == false))
}
func test_filter_whenChained_compilesAggregateWhereClause() {
AssertSQL(
"SELECT * FROM \"users\" WHERE ((\"age\" >= 35) AND \"admin\")",
users.filter(age >= 35).filter(admin)
)
}
func test_group_withSingleExpressionName_compilesGroupClause() {
AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"",
users.group(age))
}
func test_group_withVariadicExpressionNames_compilesGroupClause() {
AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin))
}
func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() {
AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING \"admin\"", users.group(age, having: admin))
AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30))
}
func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() {
AssertSQL(
"SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING \"admin\"",
users.group([age, admin], having: admin)
)
AssertSQL(
"SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)",
users.group([age, admin], having: age >= 30)
)
}
func test_order_withSingleExpressionName_compilesOrderClause() {
AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age))
}
func test_order_withVariadicExpressionNames_compilesOrderClause() {
AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email))
}
func test_order_withArrayExpressionNames_compilesOrderClause() {
AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order([age, email]))
}
func test_order_withExpressionAndSortDirection_compilesOrderClause() {
// AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc))
}
func test_order_whenChained_resetsOrderClause() {
AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age))
}
func test_reverse_withoutOrder_ordersByRowIdDescending() {
// AssertSQL("SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC", users.reverse())
}
func test_reverse_withOrder_reversesOrder() {
// AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age, email.desc).reverse())
}
func test_limit_compilesLimitClause() {
AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5))
}
func test_limit_withOffset_compilesOffsetClause() {
AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5))
}
func test_limit_whenChained_overridesLimit() {
let query = users.limit(5)
AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10))
AssertSQL("SELECT * FROM \"users\"", query.limit(nil))
}
func test_limit_whenChained_withOffset_overridesOffset() {
let query = users.limit(5, offset: 5)
AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20))
AssertSQL("SELECT * FROM \"users\"", query.limit(nil))
}
func test_alias_aliasesTable() {
let managerId = Expression<Int64>("manager_id")
let managers = users.alias("managers")
AssertSQL(
"SELECT * FROM \"users\" " +
"INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")",
users.join(managers, on: managers[id] == users[managerId])
)
}
func test_insert_compilesInsertExpression() {
AssertSQL(
"INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)",
users.insert(email <- "alice@example.com", age <- 30)
)
}
func test_insert_withOnConflict_compilesInsertOrOnConflictExpression() {
AssertSQL(
"INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)",
users.insert(or: .replace, email <- "alice@example.com", age <- 30)
)
}
func test_insert_compilesInsertExpressionWithDefaultValues() {
AssertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert())
}
func test_insert_withQuery_compilesInsertExpressionWithSelectStatement() {
let emails = Table("emails")
AssertSQL(
"INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE \"admin\"",
emails.insert(users.select(email).filter(admin))
)
}
func test_update_compilesUpdateExpression() {
AssertSQL(
"UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)",
users.filter(id == 1).update(age <- 30, admin <- true)
)
}
func test_delete_compilesDeleteExpression() {
AssertSQL(
"DELETE FROM \"users\" WHERE (\"id\" = 1)",
users.filter(id == 1).delete()
)
}
func test_delete_compilesExistsExpression() {
AssertSQL(
"SELECT EXISTS (SELECT * FROM \"users\")",
users.exists
)
}
func test_count_returnsCountExpression() {
AssertSQL("SELECT count(*) FROM \"users\"", users.count)
}
func test_scalar_returnsScalarExpression() {
AssertSQL("SELECT \"int\" FROM \"table\"", table.select(int) as ScalarQuery<Int>)
AssertSQL("SELECT \"intOptional\" FROM \"table\"", table.select(intOptional) as ScalarQuery<Int?>)
AssertSQL("SELECT DISTINCT \"int\" FROM \"table\"", table.select(distinct: int) as ScalarQuery<Int>)
AssertSQL("SELECT DISTINCT \"intOptional\" FROM \"table\"", table.select(distinct: intOptional) as ScalarQuery<Int?>)
}
func test_subscript_withExpression_returnsNamespacedExpression() {
let query = Table("query")
AssertSQL("\"query\".\"blob\"", query[data])
AssertSQL("\"query\".\"blobOptional\"", query[dataOptional])
AssertSQL("\"query\".\"bool\"", query[bool])
AssertSQL("\"query\".\"boolOptional\"", query[boolOptional])
AssertSQL("\"query\".\"date\"", query[date])
AssertSQL("\"query\".\"dateOptional\"", query[dateOptional])
AssertSQL("\"query\".\"double\"", query[double])
AssertSQL("\"query\".\"doubleOptional\"", query[doubleOptional])
AssertSQL("\"query\".\"int\"", query[int])
AssertSQL("\"query\".\"intOptional\"", query[intOptional])
AssertSQL("\"query\".\"int64\"", query[int64])
AssertSQL("\"query\".\"int64Optional\"", query[int64Optional])
AssertSQL("\"query\".\"string\"", query[string])
AssertSQL("\"query\".\"stringOptional\"", query[stringOptional])
AssertSQL("\"query\".*", query[*])
}
func test_tableNamespacedByDatabase() {
let table = Table("table", database: "attached")
AssertSQL("SELECT * FROM \"attached\".\"table\"", table)
}
}
class QueryIntegrationTests : SQLiteTestCase {
let id = Expression<Int64>("id")
let email = Expression<String>("email")
override func setUp() {
super.setUp()
CreateUsersTable()
}
// MARK: -
func test_select() {
for _ in try! db.prepare(users) {
// FIXME
}
let managerId = Expression<Int64>("manager_id")
let managers = users.alias("managers")
let alice = try! db.run(users.insert(email <- "alice@example.com"))
_ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice))
for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) {
_ = user[users[managerId]]
}
}
func test_scalar() {
XCTAssertEqual(0, try! db.scalar(users.count))
XCTAssertEqual(false, try! db.scalar(users.exists))
try! InsertUsers("alice")
XCTAssertEqual(1, try! db.scalar(users.select(id.average)))
}
func test_pluck() {
let rowid = try! db.run(users.insert(email <- "alice@example.com"))
XCTAssertEqual(rowid, try! db.pluck(users)![id])
}
func test_insert() {
let id = try! db.run(users.insert(email <- "alice@example.com"))
XCTAssertEqual(1, id)
}
func test_update() {
let changes = try! db.run(users.update(email <- "alice@example.com"))
XCTAssertEqual(0, changes)
}
func test_delete() {
let changes = try! db.run(users.delete())
XCTAssertEqual(0, changes)
}
}

View file

@ -0,0 +1,17 @@
import XCTest
import SQLite
class RTreeTests : XCTestCase {
func test_create_onVirtualTable_withRTree_createVirtualTableExpression() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING rtree(\"int64\", \"double\", \"double\")",
virtualTable.create(.RTree(int64, (double, double)))
)
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING rtree(\"int64\", \"double\", \"double\", \"double\", \"double\")",
virtualTable.create(.RTree(int64, (double, double), (double, double)))
)
}
}

View file

@ -0,0 +1,775 @@
import XCTest
import SQLite
class SchemaTests : XCTestCase {
func test_drop_compilesDropTableExpression() {
XCTAssertEqual("DROP TABLE \"table\"", table.drop())
XCTAssertEqual("DROP TABLE IF EXISTS \"table\"", table.drop(ifExists: true))
}
func test_drop_compilesDropVirtualTableExpression() {
XCTAssertEqual("DROP TABLE \"virtual_table\"", virtualTable.drop())
XCTAssertEqual("DROP TABLE IF EXISTS \"virtual_table\"", virtualTable.drop(ifExists: true))
}
func test_drop_compilesDropViewExpression() {
XCTAssertEqual("DROP VIEW \"view\"", _view.drop())
XCTAssertEqual("DROP VIEW IF EXISTS \"view\"", _view.drop(ifExists: true))
}
func test_create_withBuilder_compilesCreateTableExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (" +
"\"blob\" BLOB NOT NULL, " +
"\"blobOptional\" BLOB, " +
"\"double\" REAL NOT NULL, " +
"\"doubleOptional\" REAL, " +
"\"int64\" INTEGER NOT NULL, " +
"\"int64Optional\" INTEGER, " +
"\"string\" TEXT NOT NULL, " +
"\"stringOptional\" TEXT" +
")",
table.create { t in
t.column(data)
t.column(dataOptional)
t.column(double)
t.column(doubleOptional)
t.column(int64)
t.column(int64Optional)
t.column(string)
t.column(stringOptional)
}
)
XCTAssertEqual(
"CREATE TEMPORARY TABLE \"table\" (\"int64\" INTEGER NOT NULL)",
table.create(temporary: true) { $0.column(int64) }
)
XCTAssertEqual(
"CREATE TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)",
table.create(ifNotExists: true) { $0.column(int64) }
)
XCTAssertEqual(
"CREATE TEMPORARY TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)",
table.create(temporary: true, ifNotExists: true) { $0.column(int64) }
)
}
func test_create_withQuery_compilesCreateTableExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" AS SELECT \"int64\" FROM \"view\"",
table.create(_view.select(int64))
)
}
// thoroughness test for ambiguity
func test_column_compilesColumnDefinitionExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL)",
table.create { t in t.column(int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE)",
table.create { t in t.column(int64, unique: true) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0))",
table.create { t in t.column(int64, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL DEFAULT (\"int64\"))",
table.create { t in t.column(int64, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL DEFAULT (0))",
table.create { t in t.column(int64, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0))",
table.create { t in t.column(int64, unique: true, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64, unique: true, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE DEFAULT (\"int64\"))",
table.create { t in t.column(int64, unique: true, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE DEFAULT (0))",
table.create { t in t.column(int64, unique: true, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0))",
table.create { t in t.column(int64, unique: true, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64, unique: true, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64, unique: true, check: int64 > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64, unique: true, check: int64Optional > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) DEFAULT (0))",
table.create { t in t.column(int64, unique: true, check: int64 > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (0))",
table.create { t in t.column(int64, unique: true, check: int64Optional > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64, check: int64 > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64, check: int64Optional > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (0))",
table.create { t in t.column(int64, check: int64 > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (0))",
table.create { t in t.column(int64, check: int64Optional > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0))",
table.create { t in t.column(int64, primaryKey: true, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL DEFAULT (\"int64\"))",
table.create { t in t.column(int64, primaryKey: true, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0))",
table.create { t in t.column(int64, primaryKey: true, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64, primaryKey: true, check: int64 > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER)",
table.create { t in t.column(int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE)",
table.create { t in t.column(int64Optional, unique: true) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0))",
table.create { t in t.column(int64Optional, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64Optional, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (\"int64\"))",
table.create { t in t.column(int64Optional, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (\"int64Optional\"))",
table.create { t in t.column(int64Optional, defaultValue: int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (0))",
table.create { t in t.column(int64Optional, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0))",
table.create { t in t.column(int64Optional, unique: true, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (\"int64\"))",
table.create { t in t.column(int64Optional, unique: true, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (\"int64Optional\"))",
table.create { t in t.column(int64Optional, unique: true, defaultValue: int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (0))",
table.create { t in t.column(int64Optional, unique: true, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0))",
table.create { t in t.column(int64Optional, unique: true, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64Optional\"))",
table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64Optional\"))",
table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (0))",
table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (0))",
table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))",
table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (\"int64Optional\"))",
table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (\"int64Optional\"))",
table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: int64Optional) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (0))",
table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (0))",
table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: 0) }
)
}
func test_column_withIntegerExpression_compilesPrimaryKeyAutoincrementColumnDefinitionExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
table.create { t in t.column(int64, primaryKey: .autoincrement) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64\" > 0))",
table.create { t in t.column(int64, primaryKey: .autoincrement, check: int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64Optional\" > 0))",
table.create { t in t.column(int64, primaryKey: .autoincrement, check: int64Optional > 0) }
)
}
func test_column_withIntegerExpression_compilesReferentialColumnDefinitionExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, references: qualifiedTable, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, unique: true, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, check: int64 > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, check: int64Optional > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64Optional, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64Optional, unique: true, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64Optional, check: int64 > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64Optional, check: int64Optional > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, references: table, int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))",
table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) }
)
}
func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL COLLATE RTRIM)",
table.create { t in t.column(string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)",
table.create { t in t.column(string, check: string != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)",
table.create { t in t.column(string, check: stringOptional != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(string, defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(string, defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, check: string != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, check: stringOptional != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, check: string != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(string, check: string != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(string, check: stringOptional != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(string, check: string != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(string, check: stringOptional != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL COLLATE RTRIM)",
table.create { t in t.column(stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: string != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"stringOptional\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)",
table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) }
)
}
func test_primaryKey_compilesPrimaryKeyExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (PRIMARY KEY (\"int64\"))",
table.create { t in t.primaryKey(int64) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\"))",
table.create { t in t.primaryKey(int64, string) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\"))",
table.create { t in t.primaryKey(int64, string, double) }
)
}
func test_unique_compilesUniqueExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (UNIQUE (\"int64\"))",
table.create { t in t.unique(int64) }
)
}
func test_check_compilesCheckExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (CHECK ((\"int64\" > 0)))",
table.create { t in t.check(int64 > 0) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (CHECK ((\"int64Optional\" > 0)))",
table.create { t in t.check(int64Optional > 0) }
)
}
func test_foreignKey_compilesForeignKeyExpression() {
XCTAssertEqual(
"CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\"))",
table.create { t in t.foreignKey(string, references: table, string) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (FOREIGN KEY (\"stringOptional\") REFERENCES \"table\" (\"string\"))",
table.create { t in t.foreignKey(stringOptional, references: table, string) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\") ON UPDATE CASCADE ON DELETE SET NULL)",
table.create { t in t.foreignKey(string, references: table, string, update: .cascade, delete: .setNull) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (FOREIGN KEY (\"string\", \"string\") REFERENCES \"table\" (\"string\", \"string\"))",
table.create { t in t.foreignKey((string, string), references: table, (string, string)) }
)
XCTAssertEqual(
"CREATE TABLE \"table\" (FOREIGN KEY (\"string\", \"string\", \"string\") REFERENCES \"table\" (\"string\", \"string\", \"string\"))",
table.create { t in t.foreignKey((string, string, string), references: table, (string, string, string)) }
)
}
func test_addColumn_compilesAlterTableExpression() {
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL DEFAULT (1)",
table.addColumn(int64, defaultValue: 1)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (1)",
table.addColumn(int64, check: int64 > 0, defaultValue: 1)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (1)",
table.addColumn(int64, check: int64Optional > 0, defaultValue: 1)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER",
table.addColumn(int64Optional)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0)",
table.addColumn(int64Optional, check: int64 > 0)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0)",
table.addColumn(int64Optional, check: int64Optional > 0)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER DEFAULT (1)",
table.addColumn(int64Optional, defaultValue: 1)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (1)",
table.addColumn(int64Optional, check: int64 > 0, defaultValue: 1)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (1)",
table.addColumn(int64Optional, check: int64Optional > 0, defaultValue: 1)
)
}
func test_addColumn_withIntegerExpression_compilesReferentialAlterTableExpression() {
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\")",
table.addColumn(int64, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\")",
table.addColumn(int64, unique: true, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64, check: int64 > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64, check: int64Optional > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64, unique: true, check: int64 > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64, unique: true, check: int64Optional > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\")",
table.addColumn(int64Optional, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\")",
table.addColumn(int64Optional, unique: true, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64Optional, check: int64 > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64Optional, check: int64Optional > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64Optional, unique: true, check: int64 > 0, references: table, int64)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")",
table.addColumn(int64Optional, unique: true, check: int64Optional > 0, references: table, int64)
)
}
func test_addColumn_withStringExpression_compilesCollatedAlterTableExpression() {
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM",
table.addColumn(string, defaultValue: "string", collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM",
table.addColumn(string, check: string != "", defaultValue: "string", collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM",
table.addColumn(string, check: stringOptional != "", defaultValue: "string", collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT COLLATE RTRIM",
table.addColumn(stringOptional, collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') COLLATE RTRIM",
table.addColumn(stringOptional, check: string != "", collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') COLLATE RTRIM",
table.addColumn(stringOptional, check: stringOptional != "", collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM",
table.addColumn(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim)
)
XCTAssertEqual(
"ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM",
table.addColumn(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim)
)
}
func test_rename_compilesAlterTableRenameToExpression() {
XCTAssertEqual("ALTER TABLE \"old\" RENAME TO \"table\"", Table("old").rename(table))
}
func test_createIndex_compilesCreateIndexExpression() {
XCTAssertEqual("CREATE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", table.createIndex(int64))
XCTAssertEqual(
"CREATE UNIQUE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")",
table.createIndex([int64], unique: true)
)
XCTAssertEqual(
"CREATE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")",
table.createIndex([int64], ifNotExists: true)
)
XCTAssertEqual(
"CREATE UNIQUE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")",
table.createIndex([int64], unique: true, ifNotExists: true)
)
XCTAssertEqual(
"CREATE UNIQUE INDEX IF NOT EXISTS \"main\".\"index_table_on_int64\" ON \"table\" (\"int64\")",
qualifiedTable.createIndex([int64], unique: true, ifNotExists: true)
)
}
func test_dropIndex_compilesCreateIndexExpression() {
XCTAssertEqual("DROP INDEX \"index_table_on_int64\"", table.dropIndex(int64))
XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex([int64], ifExists: true))
}
func test_create_onView_compilesCreateViewExpression() {
XCTAssertEqual(
"CREATE VIEW \"view\" AS SELECT \"int64\" FROM \"table\"",
_view.create(table.select(int64))
)
XCTAssertEqual(
"CREATE TEMPORARY VIEW \"view\" AS SELECT \"int64\" FROM \"table\"",
_view.create(table.select(int64), temporary: true)
)
XCTAssertEqual(
"CREATE VIEW IF NOT EXISTS \"view\" AS SELECT \"int64\" FROM \"table\"",
_view.create(table.select(int64), ifNotExists: true)
)
XCTAssertEqual(
"CREATE TEMPORARY VIEW IF NOT EXISTS \"view\" AS SELECT \"int64\" FROM \"table\"",
_view.create(table.select(int64), temporary: true, ifNotExists: true)
)
}
func test_create_onVirtualTable_compilesCreateVirtualTableExpression() {
XCTAssertEqual(
"CREATE VIRTUAL TABLE \"virtual_table\" USING \"custom\"('foo', 'bar')",
virtualTable.create(Module("custom", ["foo", "bar"]))
)
}
func test_rename_onVirtualTable_compilesAlterTableRenameToExpression() {
XCTAssertEqual(
"ALTER TABLE \"old\" RENAME TO \"virtual_table\"",
VirtualTable("old").rename(virtualTable)
)
}
}

View file

@ -0,0 +1,137 @@
import XCTest
import SQLite
class SetterTests : XCTestCase {
func test_setterAssignmentOperator_buildsSetter() {
AssertSQL("\"int\" = \"int\"", int <- int)
AssertSQL("\"int\" = 1", int <- 1)
AssertSQL("\"intOptional\" = \"int\"", intOptional <- int)
AssertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional)
AssertSQL("\"intOptional\" = 1", intOptional <- 1)
AssertSQL("\"intOptional\" = NULL", intOptional <- nil)
}
func test_plusEquals_withStringExpression_buildsSetter() {
AssertSQL("\"string\" = (\"string\" || \"string\")", string += string)
AssertSQL("\"string\" = (\"string\" || 'literal')", string += "literal")
AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string)
AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional)
AssertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal")
}
func test_plusEquals_withNumberExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" + \"int\")", int += int)
AssertSQL("\"int\" = (\"int\" + 1)", int += 1)
AssertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int)
AssertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1)
AssertSQL("\"double\" = (\"double\" + \"double\")", double += double)
AssertSQL("\"double\" = (\"double\" + 1.0)", double += 1)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1)
}
func test_minusEquals_withNumberExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" - \"int\")", int -= int)
AssertSQL("\"int\" = (\"int\" - 1)", int -= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int)
AssertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1)
AssertSQL("\"double\" = (\"double\" - \"double\")", double -= double)
AssertSQL("\"double\" = (\"double\" - 1.0)", double -= 1)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1)
}
func test_timesEquals_withNumberExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" * \"int\")", int *= int)
AssertSQL("\"int\" = (\"int\" * 1)", int *= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int)
AssertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1)
AssertSQL("\"double\" = (\"double\" * \"double\")", double *= double)
AssertSQL("\"double\" = (\"double\" * 1.0)", double *= 1)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1)
}
func test_dividedByEquals_withNumberExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" / \"int\")", int /= int)
AssertSQL("\"int\" = (\"int\" / 1)", int /= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int)
AssertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1)
AssertSQL("\"double\" = (\"double\" / \"double\")", double /= double)
AssertSQL("\"double\" = (\"double\" / 1.0)", double /= 1)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional)
AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1)
}
func test_moduloEquals_withIntegerExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" % \"int\")", int %= int)
AssertSQL("\"int\" = (\"int\" % 1)", int %= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int)
AssertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1)
}
func test_leftShiftEquals_withIntegerExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" << \"int\")", int <<= int)
AssertSQL("\"int\" = (\"int\" << 1)", int <<= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int)
AssertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1)
}
func test_rightShiftEquals_withIntegerExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int)
AssertSQL("\"int\" = (\"int\" >> 1)", int >>= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int)
AssertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1)
}
func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" & \"int\")", int &= int)
AssertSQL("\"int\" = (\"int\" & 1)", int &= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int)
AssertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1)
}
func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() {
AssertSQL("\"int\" = (\"int\" | \"int\")", int |= int)
AssertSQL("\"int\" = (\"int\" | 1)", int |= 1)
AssertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int)
AssertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional)
AssertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1)
}
func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() {
AssertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int)
AssertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1)
AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int)
AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional)
AssertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1)
}
func test_postfixPlus_withIntegerValue_buildsSetter() {
AssertSQL("\"int\" = (\"int\" + 1)", int++)
AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++)
}
func test_postfixMinus_withIntegerValue_buildsSetter() {
AssertSQL("\"int\" = (\"int\" - 1)", int--)
AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--)
}
}

View file

@ -0,0 +1,26 @@
import XCTest
import SQLite
class StatementTests : SQLiteTestCase {
override func setUp() {
super.setUp()
CreateUsersTable()
}
func test_cursor_to_blob() {
try! InsertUsers("alice")
let statement = try! db.prepare("SELECT email FROM users")
XCTAssert(try! statement.step())
let blob = statement.row[0] as Blob
XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!)
}
func test_zero_sized_blob_returns_null() {
let blobs = Table("blobs")
let blobColumn = Expression<Blob>("blob_column")
try! db.run(blobs.create { $0.column(blobColumn) })
try! db.run(blobs.insert(blobColumn <- Blob(bytes: [])))
let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0))
XCTAssertEqual([], blobValue.bytes)
}
}

Some files were not shown because too many files have changed in this diff Show more