blob: a5c05bd6b78c68c3ae84fd483454befccf1e2f43 [file] [log] [blame]
//
// UsergridQuery.swift
// UsergridSDK
//
// Created by Robert Walsh on 7/22/15.
//
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. 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. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*
*/
import Foundation
/**
`UsergridQuery` is builder class used to construct filtered requests to Usergrid.
`UsergridQuery` objects are then passed to `UsergridClient` or `Usergrid` methods which support `UsergridQuery` as a parameter are .GET(), .PUT(), and .DELETE().
*/
public class UsergridQuery : NSObject,NSCopying {
// MARK: - Initialization -
/**
Desingated initializer for `UsergridQuery` objects.
- parameter collectionName: The collection name or `type` of entities you want to query.
- returns: A new instance of `UsergridQuery`.
*/
public init(_ collectionName: String? = nil) {
self.collectionName = collectionName
}
// MARK: - NSCopying -
/**
See the NSCopying protocol.
- parameter zone: Ignored
- returns: Returns a new instance that’s a copy of the receiver.
*/
public func copy(with zone: NSZone?) -> Any {
let queryCopy = UsergridQuery(self.collectionName)
queryCopy.requirementStrings = NSArray(array:self.requirementStrings, copyItems: true) as! [String]
queryCopy.urlTerms = NSArray(array:self.urlTerms, copyItems: true) as! [String]
for (key,value) in self.orderClauses {
queryCopy.orderClauses[key] = value
}
queryCopy.limit = self.limit
queryCopy.cursor = self.cursor
return queryCopy
}
// MARK: - Building -
/**
Constructs the string that should be appeneded to the end of the URL as a query.
- parameter autoURLEncode: Automatically encode the constructed string.
- returns: The constructed URL query sting.
*/
public func build(_ autoURLEncode: Bool = true) -> String {
return self.constructURLAppend(autoURLEncode)
}
// MARK: - Builder Methods -
/**
Contains. Query: where term contains 'val%'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func contains(_ term: String, value: String) -> Self { return self.containsWord(term, value: value) }
/**
Contains. Query: where term contains 'val%'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func containsString(_ term: String, value: String) -> Self { return self.containsWord(term, value: value) }
/**
Contains. Query: where term contains 'val%'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func containsWord(_ term: String, value: String) -> Self {
var operationValue: String = value
if !value.isUuid() {
operationValue = UsergridQuery.APOSTROPHE + value + UsergridQuery.APOSTROPHE
}
return self.addRequirement(term + UsergridQuery.SPACE + UsergridQuery.CONTAINS + UsergridQuery.SPACE + operationValue)
}
/**
Sort ascending. Query:. order by term asc.
- parameter term: The term.
- returns: `Self`
*/
@discardableResult
public func ascending(_ term: String) -> Self { return self.asc(term) }
/**
Sort ascending. Query:. order by term asc.
- parameter term: The term.
- returns: `Self`
*/
@discardableResult
public func asc(_ term: String) -> Self { return self.sort(term, sortOrder: .asc) }
/**
Sort descending. Query: order by term desc
- parameter term: The term.
- returns: `Self`
*/
@discardableResult
public func descending(_ term: String) -> Self { return self.desc(term) }
/**
Sort descending. Query: order by term desc
- parameter term: The term.
- returns: `Self`
*/
@discardableResult
public func desc(_ term: String) -> Self { return self.sort(term, sortOrder: .desc) }
/**
Filter (or Equal-to). Query: where term = 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func filter(_ term: String, value: Any) -> Self { return self.eq(term, value: value) }
/**
Equal-to. Query: where term = 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func equals(_ term: String, value: Any) -> Self { return self.eq(term, value: value) }
/**
Equal-to. Query: where term = 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func eq(_ term: String, value: Any) -> Self { return self.addOperationRequirement(term, operation:.equal, value: value) }
/**
Greater-than. Query: where term > 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func greaterThan(_ term: String, value: Any) -> Self { return self.gt(term, value: value) }
/**
Greater-than. Query: where term > 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func gt(_ term: String, value: Any) -> Self { return self.addOperationRequirement(term, operation:.greaterThan, value: value) }
/**
Greater-than-or-equal-to. Query: where term >= 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func greaterThanOrEqual(_ term: String, value: Any) -> Self { return self.gte(term, value: value) }
/**
Greater-than-or-equal-to. Query: where term >= 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func gte(_ term: String, value: Any) -> Self { return self.addOperationRequirement(term, operation:.greaterThanEqualTo, value: value) }
/**
Less-than. Query: where term < 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func lessThan(_ term: String, value: Any) -> Self { return self.lt(term, value: value) }
/**
Less-than. Query: where term < 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func lt(_ term: String, value: Any) -> Self { return self.addOperationRequirement(term, operation:.lessThan, value: value) }
/**
Less-than-or-equal-to. Query: where term <= 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func lessThanOrEqual(_ term: String, value: Any) -> Self { return self.lte(term, value: value) }
/**
Less-than-or-equal-to. Query: where term <= 'value'.
- parameter term: The term.
- parameter value: The value.
- returns: `Self`
*/
@discardableResult
public func lte(_ term: String, value: Any) -> Self { return self.addOperationRequirement(term, operation:.lessThanEqualTo, value: value) }
/**
Contains. Query: location within val of lat, long.
- parameter distance: The distance from the latitude and longitude.
- parameter latitude: The latitude.
- parameter longitude: The longitude.
- returns: `Self`
*/
@discardableResult
public func locationWithin(_ distance: Float, latitude: Float, longitude: Float) -> Self {
return self.addRequirement(UsergridQuery.LOCATION + UsergridQuery.SPACE + UsergridQuery.WITHIN + UsergridQuery.SPACE + distance.description + UsergridQuery.SPACE + UsergridQuery.OF + UsergridQuery.SPACE + latitude.description + UsergridQuery.COMMA + longitude.description )
}
/**
Or operation for conditional queries.
- returns: `Self`
*/
@discardableResult
public func or() -> Self {
if !self.requirementStrings.first!.isEmpty {
self.requirementStrings.insert(UsergridQuery.OR, at: 0)
self.requirementStrings.insert(UsergridQuery.EMPTY_STRING, at: 0)
}
return self
}
/**
And operation for conditional queries.
- returns: `Self`
*/
@discardableResult
public func and() -> Self {
if !self.requirementStrings.first!.isEmpty {
self.requirementStrings.insert(UsergridQuery.AND, at: 0)
self.requirementStrings.insert(UsergridQuery.EMPTY_STRING, at: 0)
}
return self
}
/**
Not operation for conditional queries.
- returns: `Self`
*/
@discardableResult
public func not() -> Self {
if !self.requirementStrings.first!.isEmpty {
self.requirementStrings.insert(UsergridQuery.NOT, at: 0)
self.requirementStrings.insert(UsergridQuery.EMPTY_STRING, at: 0)
}
return self
}
/**
Sort. Query: order by term `sortOrder`
- parameter term: The term.
- parameter sortOrder: The order.
- returns: `Self`
*/
@discardableResult
public func sort(_ term: String, sortOrder: UsergridQuerySortOrder) -> Self {
self.orderClauses[term] = sortOrder
return self
}
/**
Sets the collection name.
- parameter collectionName: The new collection name.
- returns: `Self`
*/
@discardableResult
public func collection(_ collectionName: String) -> Self {
self.collectionName = collectionName
return self
}
/**
Sets the collection name.
- parameter type: The new collection name.
- returns: `Self`
*/
@discardableResult
public func type(_ type: String) -> Self {
self.collectionName = type
return self
}
/**
Sets the limit on the query. Default limit is 10.
- parameter limit: The limit.
- returns: `Self`
*/
@discardableResult
public func limit(_ limit: Int) -> Self {
self.limit = limit
return self
}
/**
Adds a preconstructed query string as a requirement onto the query.
- parameter value: The query string.
- returns: `Self`
*/
@discardableResult
public func ql(_ value: String) -> Self {
return self.addRequirement(value)
}
/**
Sets the cursor of the query used internally by Usergrid's APIs.
- parameter value: The cursor.
- returns: `Self`
*/
@discardableResult
public func cursor(_ value: String?) -> Self {
self.cursor = value
return self
}
/**
A special builder property that allows you to input a pre-defined query string. All builder properties will be ignored when this property is defined.
- parameter value: The pre-defined query string.
- returns: `Self`
*/
@discardableResult
public func fromString(_ value: String?) -> Self {
self.fromStringValue = value
return self
}
/**
Adds a URL term that will be added next to the query string when constructing the URL append.
- parameter term: The term.
- parameter equalsValue: The value.
- returns: `Self`
*/
@discardableResult
public func urlTerm(_ term: String, equalsValue: String) -> Self {
if term == UsergridQuery.QL {
return self.ql(equalsValue)
} else {
self.urlTerms.append(term + UsergridQueryOperator.equal.stringValue + equalsValue)
}
return self
}
/**
Adds a string requirement to the query.
- parameter term: The term.
- parameter operation: The operation.
- parameter stringValue: The string value.
- returns: `Self`
*/
@discardableResult
public func addOperationRequirement(_ term: String, operation: UsergridQueryOperator, stringValue: String) -> Self {
return self.addOperationRequirement(term,operation:operation,value:stringValue)
}
/**
Adds a integer requirement to the query.
- parameter term: The term.
- parameter operation: The operation.
- parameter intValue: The integer value.
- returns: `Self`
*/
@discardableResult
public func addOperationRequirement(_ term: String, operation: UsergridQueryOperator, intValue: Int) -> Self {
return self.addOperationRequirement(term,operation:operation,value:intValue)
}
@discardableResult
private func addRequirement(_ requirement: String) -> Self {
var requirementString: String = self.requirementStrings.remove(at: 0)
if !requirementString.isEmpty {
requirementString += UsergridQuery.SPACE + UsergridQuery.AND + UsergridQuery.SPACE
}
requirementString += requirement
self.requirementStrings.insert(requirementString, at: 0)
return self
}
@discardableResult
private func addOperationRequirement(_ term: String, operation: UsergridQueryOperator, value: Any) -> Self {
if let stringValue = value as? String {
var operationValue: String = stringValue
if !stringValue.isUuid() {
operationValue = UsergridQuery.APOSTROPHE + stringValue + UsergridQuery.APOSTROPHE
}
return self.addRequirement(term + UsergridQuery.SPACE + operation.stringValue + UsergridQuery.SPACE + operationValue)
} else {
return self.addRequirement(term + UsergridQuery.SPACE + operation.stringValue + UsergridQuery.SPACE + (value as AnyObject).description )
}
}
private func constructOrderByString() -> String {
var orderByString = UsergridQuery.EMPTY_STRING
if !self.orderClauses.isEmpty {
var combinedClausesArray: [String] = []
for (key,value) in self.orderClauses {
combinedClausesArray.append(key + UsergridQuery.SPACE + value.stringValue)
}
for index in 0..<combinedClausesArray.count {
if index > 0 {
orderByString += UsergridQuery.COMMA
}
orderByString += combinedClausesArray[index]
}
if !orderByString.isEmpty {
orderByString = UsergridQuery.SPACE + UsergridQuery.ORDER_BY + UsergridQuery.SPACE + orderByString
}
}
return orderByString
}
private func constructURLTermsString() -> String {
return self.urlTerms.joined(separator: UsergridQuery.AMPERSAND)
}
private func constructRequirementString() -> String {
var requirementsString = UsergridQuery.EMPTY_STRING
var requirementStrings = self.requirementStrings
// If the first requirement is empty lets remove it.
if let firstRequirement = requirementStrings.first , firstRequirement.isEmpty {
requirementStrings.removeFirst()
}
// If the first requirement now is a conditional separator then we should remove it so its not placed at the end of the constructed string.
if let firstRequirement = requirementStrings.first , firstRequirement == UsergridQuery.OR || firstRequirement == UsergridQuery.NOT {
requirementStrings.removeFirst()
}
requirementsString = requirementStrings.reversed().joined(separator: UsergridQuery.SPACE)
return requirementsString
}
private func constructURLAppend(_ autoURLEncode: Bool = true) -> String {
if let fromString = self.fromStringValue {
var requirementsString = fromString
if autoURLEncode {
if let encodedRequirementsString = fromString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) {
requirementsString = encodedRequirementsString
}
}
return "\(UsergridQuery.QUESTION_MARK)\(UsergridQuery.QL)=\(requirementsString)"
}
var urlAppend = UsergridQuery.EMPTY_STRING
if self.limit != UsergridQuery.LIMIT_DEFAULT {
urlAppend += "\(UsergridQuery.LIMIT)=\(self.limit.description)"
}
let urlTermsString = self.constructURLTermsString()
if !urlTermsString.isEmpty {
if !urlAppend.isEmpty {
urlAppend += UsergridQuery.AMPERSAND
}
urlAppend += urlTermsString
}
if let cursorString = self.cursor , !cursorString.isEmpty {
if !urlAppend.isEmpty {
urlAppend += UsergridQuery.AMPERSAND
}
urlAppend += "\(UsergridQuery.CURSOR)=\(cursorString)"
}
var requirementsString = UsergridQuery.SELECT_ALL + UsergridQuery.SPACE + self.constructRequirementString()
let orderByString = self.constructOrderByString()
if !orderByString.isEmpty {
requirementsString += orderByString
}
if !requirementsString.isEmpty {
if autoURLEncode {
if let encodedRequirementsString = requirementsString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) {
requirementsString = encodedRequirementsString
}
}
if !urlAppend.isEmpty {
urlAppend += UsergridQuery.AMPERSAND
}
urlAppend += "\(UsergridQuery.QL)=\(requirementsString)"
}
if !urlAppend.isEmpty {
urlAppend = "\(UsergridQuery.QUESTION_MARK)\(urlAppend)"
}
return urlAppend
}
private(set) var collectionName: String? = nil
private(set) var cursor: String? = nil
private(set) var limit: Int = UsergridQuery.LIMIT_DEFAULT
private(set) var fromStringValue: String? = nil
private(set) var requirementStrings: [String] = [UsergridQuery.EMPTY_STRING]
private(set) var orderClauses: [String:UsergridQuerySortOrder] = [:]
private(set) var urlTerms: [String] = []
private static let LIMIT_DEFAULT = 10
private static let AMPERSAND = "&"
private static let AND = "and"
private static let APOSTROPHE = "'"
private static let COMMA = ","
private static let CONTAINS = "contains"
private static let CURSOR = "cursor"
private static let EMPTY_STRING = ""
private static let IN = "in"
private static let LIMIT = "limit"
private static let LOCATION = "location";
private static let NOT = "not"
private static let OF = "of"
private static let OR = "or"
private static let ORDER_BY = "order by"
private static let QL = "ql"
private static let QUESTION_MARK = "?"
private static let SELECT_ALL = "select *"
private static let SPACE = " "
private static let WITHIN = "within"
internal static let ASC = "asc"
internal static let DESC = "desc"
internal static let EQUAL = "="
internal static let GREATER_THAN = ">"
internal static let GREATER_THAN_EQUAL_TO = ">="
internal static let LESS_THAN = "<"
internal static let LESS_THAN_EQUAL_TO = "<="
}