Skip to content

Commit

Permalink
Append instead of replace when using query (#155)
Browse files Browse the repository at this point in the history
* Append instead of replace when using query select, exclude, include, and fields

* Use set instead of array
  • Loading branch information
cbaker6 committed Jun 5, 2021
1 parent 685f07a commit b064000
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 32 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.0...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

__Improvements__
- Append instead of replace when using query select, exclude, include, and fields ([#155](https://github.com/parse-community/Parse-Swift/pull/155)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.8.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.7.2...1.8.0)

Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/LiveQuery/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct StandardMessage: LiveQueryable, Codable {
struct SubscribeQuery: Encodable {
let className: String
let `where`: QueryWhere
let fields: [String]?
let fields: Set<String>?
}

struct SubscribeMessage<T: ParseObject>: LiveQueryable, Encodable {
Expand Down
68 changes: 53 additions & 15 deletions Sources/ParseSwift/Types/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -633,20 +633,20 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {
private let method: String = "GET"
internal var limit: Int = 100
internal var skip: Int = 0
internal var keys: [String]?
internal var include: [String]?
internal var keys: Set<String>?
internal var include: Set<String>?
internal var order: [Order]?
internal var isCount: Bool?
internal var explain: Bool?
internal var hint: AnyEncodable?
internal var `where` = QueryWhere()
internal var excludeKeys: [String]?
internal var excludeKeys: Set<String>?
internal var readPreference: String?
internal var includeReadPreference: String?
internal var subqueryReadPreference: String?
internal var distinct: String?
internal var pipeline: [[String: AnyEncodable]]?
internal var fields: [String]?
internal var fields: Set<String>?

/**
An enum that determines the order to sort the results based on a given key.
Expand Down Expand Up @@ -768,21 +768,31 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {

/**
Make the query include `ParseObject`s that have a reference stored at the provided keys.
If this is called multiple times, then all of the keys specified in each of the calls will be included.
- parameter keys: A variadic list of keys to load child `ParseObject`s for.
*/
public func include(_ keys: String...) -> Query<T> {
var mutableQuery = self
mutableQuery.include = keys
if mutableQuery.include != nil {
mutableQuery.include = mutableQuery.include?.union(keys)
} else {
mutableQuery.include = Set(keys)
}
return mutableQuery
}

/**
Make the query include `ParseObject`s that have a reference stored at the provided keys.
If this is called multiple times, then all of the keys specified in each of the calls will be included.
- parameter keys: An array of keys to load child `ParseObject`s for.
*/
public func include(_ keys: [String]) -> Query<T> {
var mutableQuery = self
mutableQuery.include = keys
if mutableQuery.include != nil {
mutableQuery.include = mutableQuery.include?.union(keys)
} else {
mutableQuery.include = Set(keys)
}
return mutableQuery
}

Expand All @@ -797,24 +807,34 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {
}

/**
Exclude specific keys for a `ParseObject`. Default is to nil.
Exclude specific keys for a `ParseObject`.
If this is called multiple times, then all of the keys specified in each of the calls will be excluded.
- parameter keys: A variadic list of keys include in the result.
- warning: Requires Parse Server > 4.5.0.
*/
public func exclude(_ keys: String...) -> Query<T> {
var mutableQuery = self
mutableQuery.excludeKeys = keys
if mutableQuery.excludeKeys != nil {
mutableQuery.excludeKeys = mutableQuery.excludeKeys?.union(keys)
} else {
mutableQuery.excludeKeys = Set(keys)
}
return mutableQuery
}

/**
Exclude specific keys for a `ParseObject`. Default is to nil.
- parameter keys: An array of keys to exclude.
Exclude specific keys for a `ParseObject`.
If this is called multiple times, then all of the keys specified in each of the calls will be excluded.
- parameter keys: An array of keys to exclude in the result.
- warning: Requires Parse Server > 4.5.0.
*/
public func exclude(_ keys: [String]) -> Query<T> {
var mutableQuery = self
mutableQuery.excludeKeys = keys
if mutableQuery.excludeKeys != nil {
mutableQuery.excludeKeys = mutableQuery.excludeKeys?.union(keys)
} else {
mutableQuery.excludeKeys = Set(keys)
}
return mutableQuery
}

Expand All @@ -826,7 +846,11 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {
*/
public func select(_ keys: String...) -> Query<T> {
var mutableQuery = self
mutableQuery.keys = keys
if mutableQuery.keys != nil {
mutableQuery.keys = mutableQuery.keys?.union(keys)
} else {
mutableQuery.keys = Set(keys)
}
return mutableQuery
}

Expand All @@ -838,7 +862,11 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {
*/
public func select(_ keys: [String]) -> Query<T> {
var mutableQuery = self
mutableQuery.keys = keys
if mutableQuery.keys != nil {
mutableQuery.keys = mutableQuery.keys?.union(keys)
} else {
mutableQuery.keys = Set(keys)
}
return mutableQuery
}

Expand All @@ -859,13 +887,18 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {
If you are only interested in the change of the name field, you can set `query.fields` to "name".
In this situation, when the change of a Player `ParseObject` fulfills the subscription, only the
name field will be sent to the clients instead of the full Player `ParseObject`.
If this is called multiple times, then all of the keys specified in each of the calls will be received.
- warning: This is only for `ParseLiveQuery`.
- parameter keys: A variadic list of fields to receive back instead of the whole `ParseObject`.
*/
@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public func fields(_ keys: String...) -> Query<T> {
var mutableQuery = self
mutableQuery.fields = keys
if mutableQuery.fields != nil {
mutableQuery.fields = mutableQuery.fields?.union(keys)
} else {
mutableQuery.fields = Set(keys)
}
return mutableQuery
}

Expand All @@ -876,13 +909,18 @@ public struct Query<T>: Encodable, Equatable where T: ParseObject {
If you are only interested in the change of the name field, you can set `query.fields` to "name".
In this situation, when the change of a Player `ParseObject` fulfills the subscription, only the
name field will be sent to the clients instead of the full Player `ParseObject`.
If this is called multiple times, then all of the keys specified in each of the calls will be received.
- warning: This is only for `ParseLiveQuery`.
- parameter keys: An array of fields to receive back instead of the whole `ParseObject`.
*/
@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public func fields(_ keys: [String]) -> Query<T> {
var mutableQuery = self
mutableQuery.fields = keys
if mutableQuery.fields != nil {
mutableQuery.fields = mutableQuery.fields?.union(keys)
} else {
mutableQuery.fields = Set(keys)
}
return mutableQuery
}

Expand Down
28 changes: 27 additions & 1 deletion Tests/ParseSwiftTests/ParseLiveQueryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class ParseLiveQueryTests: XCTestCase {
// swiftlint:disable:next line_length
let expected = "{\"op\":\"subscribe\",\"requestId\":1,\"query\":{\"className\":\"GameScore\",\"where\":{\"score\":{\"$gt\":9}},\"fields\":[\"score\"]}}"
let query = GameScore.query("score" > 9)
.fields("score")
.fields(["score"])
let message = SubscribeMessage(operation: .subscribe,
requestId: RequestId(value: 1),
query: query,
Expand All @@ -204,6 +204,32 @@ class ParseLiveQueryTests: XCTestCase {
XCTAssertEqual(decoded, expected)
}

func testFieldKeys() throws {
let query = GameScore.query()
XCTAssertNil(query.keys)

var query2 = GameScore.query().fields(["yolo"])
XCTAssertEqual(query2.fields?.count, 1)
XCTAssertEqual(query2.fields?.first, "yolo")

query2 = query2.fields(["hello", "wow"])
XCTAssertEqual(query2.fields?.count, 3)
XCTAssertEqual(query2.fields, ["yolo", "hello", "wow"])
}

func testFieldKeysVariadic() throws {
let query = GameScore.query()
XCTAssertNil(query.keys)

var query2 = GameScore.query().fields("yolo")
XCTAssertEqual(query2.fields?.count, 1)
XCTAssertEqual(query2.fields?.first, "yolo")

query2 = query2.fields("hello", "wow")
XCTAssertEqual(query2.fields?.count, 3)
XCTAssertEqual(query2.fields, ["yolo", "hello", "wow"])
}

func testRedirectResponseDecoding() throws {
guard let url = URL(string: "http://parse.org") else {
XCTFail("Should have url")
Expand Down
91 changes: 76 additions & 15 deletions Tests/ParseSwiftTests/ParseQueryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,23 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length
func testIncludeKeys() {
let query = GameScore.query()
XCTAssertNil(query.include)
var query2 = GameScore.query().include("yolo")
var query2 = GameScore.query().include(["yolo"])
XCTAssertEqual(query2.include?.count, 1)
XCTAssertEqual(query2.include?.first, "yolo")
query2 = query2.include("yolo", "wow")
XCTAssertEqual(query2.include?.count, 2)
XCTAssertEqual(query2.include, ["yolo", "wow"])
query2 = query2.include(["yolo"])
query2 = query2.include(["hello", "wow"])
XCTAssertEqual(query2.include?.count, 3)
XCTAssertEqual(query2.include, Set(["yolo", "hello", "wow"]))
}

func testIncludeKeysVariadic() {
let query = GameScore.query()
XCTAssertNil(query.include)
var query2 = GameScore.query().include("yolo")
XCTAssertEqual(query2.include?.count, 1)
XCTAssertEqual(query2.include, ["yolo"])
XCTAssertEqual(query2.include?.first, "yolo")
query2 = query2.include("hello", "wow")
XCTAssertEqual(query2.include?.count, 3)
XCTAssertEqual(query2.include, Set(["yolo", "hello", "wow"]))
}

func testIncludeAllKeys() {
Expand All @@ -179,8 +187,33 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length
func testExcludeKeys() throws {
let query = GameScore.query()
XCTAssertNil(query.excludeKeys)
var query2 = GameScore.query().exclude("yolo")
var query2 = GameScore.query().exclude(["yolo"])
XCTAssertEqual(query2.excludeKeys, ["yolo"])
let encoded = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded)
guard let decodedKeys = decodedDictionary["excludeKeys"],
let decodedValues = decodedKeys.value as? [String] else {
XCTFail("Should have casted")
return
}
XCTAssertEqual(decodedValues, ["yolo"])

query2 = query2.exclude(["hello", "wow"])
XCTAssertEqual(query2.excludeKeys, ["yolo", "hello", "wow"])
let encoded2 = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary2 = try JSONDecoder().decode([String: AnyCodable].self, from: encoded2)
guard let decodedKeys2 = decodedDictionary2["excludeKeys"],
let decodedValues2 = decodedKeys2.value as? [String] else {
XCTFail("Should have casted")
return
}
XCTAssertEqual(Set(decodedValues2), Set(["yolo", "hello", "wow"]))
}

func testExcludeKeysVariadic() throws {
let query = GameScore.query()
XCTAssertNil(query.excludeKeys)
var query2 = GameScore.query().exclude("yolo")
XCTAssertEqual(query2.excludeKeys, ["yolo"])
let encoded = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded)
Expand All @@ -191,23 +224,51 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length
}
XCTAssertEqual(decodedValues, ["yolo"])

query2 = GameScore.query().exclude(["yolo", "wow"])
XCTAssertEqual(query2.excludeKeys, ["yolo", "wow"])
XCTAssertEqual(query2.excludeKeys, ["yolo", "wow"])
query2 = query2.exclude("hello", "wow")
XCTAssertEqual(query2.excludeKeys, ["yolo", "hello", "wow"])
let encoded2 = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary2 = try JSONDecoder().decode([String: AnyCodable].self, from: encoded2)
guard let decodedKeys2 = decodedDictionary2["excludeKeys"],
let decodedValues2 = decodedKeys2.value as? [String] else {
XCTFail("Should have casted")
return
}
XCTAssertEqual(decodedValues2, ["yolo", "wow"])
XCTAssertEqual(Set(decodedValues2), Set(["yolo", "hello", "wow"]))
}

func testSelectKeys() throws {
let query = GameScore.query()
XCTAssertNil(query.keys)

var query2 = GameScore.query().select(["yolo"])
XCTAssertEqual(query2.keys?.count, 1)
XCTAssertEqual(query2.keys?.first, "yolo")
let encoded = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded)
guard let decodedKeys = decodedDictionary["keys"],
let decodedValues = decodedKeys.value as? [String] else {
XCTFail("Should have casted")
return
}
XCTAssertEqual(decodedValues, ["yolo"])

query2 = query2.select(["hello", "wow"])
XCTAssertEqual(query2.keys?.count, 3)
XCTAssertEqual(query2.keys, ["yolo", "hello", "wow"])
let encoded2 = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary2 = try JSONDecoder().decode([String: AnyCodable].self, from: encoded2)
guard let decodedKeys2 = decodedDictionary2["keys"],
let decodedValues2 = decodedKeys2.value as? [String] else {
XCTFail("Should have casted")
return
}
XCTAssertEqual(Set(decodedValues2), Set(["yolo", "hello", "wow"]))
}

func testSelectKeysVariadic() throws {
let query = GameScore.query()
XCTAssertNil(query.keys)

var query2 = GameScore.query().select("yolo")
XCTAssertEqual(query2.keys?.count, 1)
XCTAssertEqual(query2.keys?.first, "yolo")
Expand All @@ -220,17 +281,17 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length
}
XCTAssertEqual(decodedValues, ["yolo"])

query2 = query2.select(["yolo", "wow"])
XCTAssertEqual(query2.keys?.count, 2)
XCTAssertEqual(query2.keys, ["yolo", "wow"])
query2 = query2.select("hello", "wow")
XCTAssertEqual(query2.keys?.count, 3)
XCTAssertEqual(query2.keys, ["yolo", "hello", "wow"])
let encoded2 = try ParseCoding.jsonEncoder().encode(query2)
let decodedDictionary2 = try JSONDecoder().decode([String: AnyCodable].self, from: encoded2)
guard let decodedKeys2 = decodedDictionary2["keys"],
let decodedValues2 = decodedKeys2.value as? [String] else {
XCTFail("Should have casted")
return
}
XCTAssertEqual(decodedValues2, ["yolo", "wow"])
XCTAssertEqual(Set(decodedValues2), Set(["yolo", "hello", "wow"]))
}

func testAddingConstraints() {
Expand Down

0 comments on commit b064000

Please sign in to comment.