Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public struct FilterDecorator: ModelBasedGraphQLDocumentDecorator {

public func decorate(_ document: SingleDirectiveGraphQLDocument,
modelSchema: ModelSchema) -> SingleDirectiveGraphQLDocument {
guard !filter.isEmpty else {
return document.copy(inputs: document.inputs)
}

var inputs = document.inputs
let modelName = modelSchema.name
if case .mutation = document.operationType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ extension QueryPredicate {
return operation.graphQLFilter(for: modelSchema)
} else if let group = self as? QueryPredicateGroup {
return group.graphQLFilter(for: modelSchema)
} else if let constant = self as? QueryPredicateConstant {
return constant.graphQLFilter(for: modelSchema)
}

preconditionFailure(
Expand All @@ -98,6 +100,15 @@ extension QueryPredicate {
}
}

extension QueryPredicateConstant: GraphQLFilterConvertible {
func graphQLFilter(for modelSchema: ModelSchema?) -> GraphQLFilter {
if self == .all {
return [:]
Comment on lines +104 to +106
Copy link
Contributor

@lawmicha lawmicha Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just thinking whether the return value should just be nil and the method return GraphQLFilter? type. I think keeping it GraphQLFilter is fine since FilterDecorator should be checking if the filter is empty anyways, just to be safe.

Copy link
Contributor Author

@wooj2 wooj2 Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think it would be useful to change if we had a use case of detecting nil vs an empty GraphQLFilter. In this case, of QueryPredicateConstant.all, i think that returning [:] is an accurate representation of .all -- that is, there is no filter -- so at this point, i'm not seeing a strong case for changing this to an optional.

}
preconditionFailure("Could not find QueryPredicateConstant \(self)")
}
}

extension QueryPredicateOperation: GraphQLFilterConvertible {

func graphQLFilter(for modelSchema: ModelSchema?) -> GraphQLFilter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,88 @@ class GraphQLRequestAnyModelWithSyncTests: XCTestCase {
XCTAssertNotNil(variables["lastSync"])
XCTAssertEqual(variables["lastSync"] as? Int, lastSync)
}

func testUpdateMutationWithEmptyFilter() {
let post = Post(title: "title", content: "content", createdAt: .now())
let documentStringValue = """
mutation UpdatePost($input: UpdatePostInput!) {
updatePost(input: $input) {
id
content
createdAt
draft
rating
status
title
updatedAt
__typename
_version
_deleted
_lastChangedAt
}
}
"""

let request = GraphQLRequest<Post>.updateMutation(of: post, where: [:])
XCTAssertEqual(documentStringValue, request.document)
XCTAssert(request.responseType == MutationSyncResult.self)

guard let variables = request.variables else {
XCTFail("The request doesn't contain variables")
return
}
guard let input = variables["input"] as? [String: Any] else {
XCTFail("The document variables property doesn't contain a valid input")
return
}
XCTAssert(input["title"] as? String == post.title)
XCTAssert(input["content"] as? String == post.content)
}

func testUpdateMutationWithFilter() {
let post = Post(title: "myTitle", content: "content", createdAt: .now())
let documentStringValue = """
mutation UpdatePost($condition: ModelPostConditionInput, $input: UpdatePostInput!) {
updatePost(condition: $condition, input: $input) {
id
content
createdAt
draft
rating
status
title
updatedAt
__typename
_version
_deleted
_lastChangedAt
}
}
"""
let filter: [String: Any] = ["title": ["eq": "myTitle"]]
let request = GraphQLRequest<Post>.updateMutation(of: post, where: filter)
XCTAssertEqual(documentStringValue, request.document)
XCTAssert(request.responseType == MutationSyncResult.self)

guard let variables = request.variables else {
XCTFail("The request doesn't contain variables")
return
}
guard let input = variables["input"] as? [String: Any] else {
XCTFail("The document variables property doesn't contain a valid input")
return
}
XCTAssert(input["title"] as? String == post.title)
XCTAssert(input["content"] as? String == post.content)

guard let condition = variables["condition"] as? [String: Any] else {
XCTFail("The document variables property doesn't contain a valid condition")
return
}
guard let conditionValue = condition["title"] as? [String: String] else {
XCTFail("Failed to get 'title' from the condition")
return
}
XCTAssertEqual(conditionValue["eq"], "myTitle")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ final class StorageEngine: StorageEngineBehavior {
var graphQLFilterJSON: String?
if let predicate = predicate {
do {
graphQLFilterJSON = try GraphQLFilterConverter.toJSON(predicate)
graphQLFilterJSON = try GraphQLFilterConverter.toJSON(predicate,
modelSchema: modelSchema)
} catch {
let dataStoreError = DataStoreError(error: error)
completion(.failure(dataStoreError))
Expand Down Expand Up @@ -519,7 +520,8 @@ final class StorageEngine: StorageEngineBehavior {
do {
var graphQLFilterJSON: String?
if let predicate = predicate {
graphQLFilterJSON = try GraphQLFilterConverter.toJSON(predicate)
graphQLFilterJSON = try GraphQLFilterConverter.toJSON(predicate,
modelSchema: modelSchema)
}

mutationEvent = try MutationEvent(model: savedModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,4 +731,76 @@ class InitialSyncOperationTests: XCTestCase {
waitForExpectations(timeout: 1)
sink.cancel()
}

func testBaseQueryWithSyncExpressionConstantAll() throws {
let storageAdapter = try SQLiteStorageEngineAdapter(connection: Connection(.inMemory))
try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas + [MockSynced.schema])

let apiWasQueried = expectation(description: "API was queried with sync expression")
let responder = QueryRequestListenerResponder<PaginatedList<AnyModel>> { request, listener in
XCTAssertEqual(request.document, """
query SyncMockSynceds($limit: Int) {
syncMockSynceds(limit: $limit) {
items {
id
__typename
_version
_deleted
_lastChangedAt
}
nextToken
startedAt
}
}
""")
XCTAssertNil(request.variables?["filter"])

let list = PaginatedList<AnyModel>(items: [], nextToken: nil, startedAt: nil)
let event: GraphQLOperation<PaginatedList<AnyModel>>.OperationResult = .success(.success(list))
listener?(event)
apiWasQueried.fulfill()
return nil
}

let apiPlugin = MockAPICategoryPlugin()
apiPlugin.responders[.queryRequestListener] = responder

let reconciliationQueue = MockReconciliationQueue()
let syncExpression = DataStoreSyncExpression.syncExpression(MockSynced.schema, where: {
QueryPredicateConstant.all
})
let configuration = DataStoreConfiguration.custom(syncPageSize: 10, syncExpressions: [syncExpression])
let operation = InitialSyncOperation(
modelSchema: MockSynced.schema,
api: apiPlugin,
reconciliationQueue: reconciliationQueue,
storageAdapter: storageAdapter,
dataStoreConfiguration: configuration)

let syncStartedReceived = expectation(description: "Sync started received, sync operation started")
let syncCompletionReceived = expectation(description: "Sync completion received, sync operation is complete")
let finishedReceived = expectation(description: "InitialSyncOperation finishe offering items")
let sink = operation
.publisher
.sink(receiveCompletion: { _ in
syncCompletionReceived.fulfill()
}, receiveValue: { value in
switch value {
case .started(modelName: let modelName, syncType: let syncType):
XCTAssertEqual(modelName, "MockSynced")
XCTAssertEqual(syncType, .fullSync)
syncStartedReceived.fulfill()
case .finished(modelName: let modelName):
XCTAssertEqual(modelName, "MockSynced")
finishedReceived.fulfill()
default:
break
}
})

operation.main()

waitForExpectations(timeout: 1)
sink.cancel()
}
}