Skip to content

Conversation

@drochetti
Copy link
Contributor

@drochetti drochetti commented Nov 1, 2022

Custom selection set for GraphQL queries in the API category

This feature will enable customers to define which associations will be included in selection set when querying data using the static typed models. Previously developers had no control over that and had to fallback to untyped/string-based GraphQL queries when they needed more control. This feature enables fine control with full type information of which associations should be included in a query.

A quick example of the added capability is represented in the following code snippet:

let postsWithComments = try await Amplify.API.query(request: .list(Post.self, includes: { [$0.comments] }))

Developers can go as deep as needed and/or add multiple associations. Next I'll go into more details on how this was accomplished to provide folks reviewing this PR more information about the implementation details and decisions.


Check points: (check or cross out if not relevant)

  • Added new tests to cover change, if needed
  • Build succeeds with all target using Swift Package Manager
  • All unit tests pass
  • All integration tests pass
  • Security oriented best practices and standards are followed (e.g. using input sanitization, principle of least privilege, etc)
  • Documentation update for the change if required
  • PR title conforms to conventional commit style
  • If breaking change, documentation/changelog update with migration instructions

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@drochetti drochetti added the api Issues related to the API category label Nov 1, 2022
@drochetti drochetti changed the title adds selection set customization to api category feat(api): adds selection set customization Nov 1, 2022
let schema = ModelRegistry.modelSchema(from: associatedModelName) {
let child = SelectionSet(value: .init(name: field.name, fieldType: .model))
child.withModelFields(schema.graphQLFields)
child.withModelFields(schema.primaryKey.fields)
Copy link
Contributor

Choose a reason for hiding this comment

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

this will break existing setups that use the previously generated models that do not have the LazyReference encapsulation since the associated model needs to have the full selection set for decoding. To make it backwards compatible, i passed in a flag called "primaryKeysOnly" to this method to control this to be false for the current codegen.

lawmicha@2585590#diff-75edd460680ee3bcc5a0b05a8f77b5a5be907e55532914649870c0d29948ce55

if primaryKeysOnly {
    child.withModelFields(schema.primaryKey.fields, primaryKeysOnly: primaryKeysOnly)
} else {
    child.withModelFields(schema.graphQLFields, primaryKeysOnly: primaryKeysOnly)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I reviewed your approach and will keep this PR like this since it'll be fixed once you apply yours on top of it.

Comment on lines 263 to +269
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .query)
documentBuilder.add(decorator: DirectiveNameDecorator(type: .list))

if let modelPath = modelType.rootPath as? ModelPath<M> {
let associations = includes(modelPath)
documentBuilder.add(decorator: IncludeAssociationDecorator(associations))
}
Copy link
Contributor

@lawmicha lawmicha Nov 8, 2022

Choose a reason for hiding this comment

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

For the implementation of these APIs, i checked if the modelType.rootPath can be unwrapped at the start of this method, because if it can be unwrapped means that it is using the codegen with lazy reference. If it is using codegen with lazy reference, then pass in primaryKeysOnly: true to ModelBasedGraphQLDocumentBuilder (#2583), otherwise keep primaryKeysOnly false for previous codegen version.

let primaryKeysOnly = (modelType.rootPath != nil) ? true: false

This is to account for when includes is the default value && new codegen is used then primaryKeysOnly == true. when old codegen is used, it should be primaryKeysOnly == false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. So should I leave it like this here since your PR will change it?

Copy link
Contributor

@lawmicha lawmicha left a comment

Choose a reason for hiding this comment

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

Of course, feel free to use an alternative naming/approach to primaryKeysOnly flag in the suggestions above ^

case unknown(ErrorDescription, RecoverySuggestion, Error)
}

extension DataError: AmplifyError {
Copy link
Contributor

Choose a reason for hiding this comment

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

we currently have CoreError which is returned on List operations. I think we should merge the two into one, and name it either CoreError or DataError as you have it here

Comment on lines 103 to 105
public func id(_ name: String = "id") -> FieldPath<String> {
FieldPath(name: name, parent: self)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a special use case for this or just visually appealing to have it codegenerated as "id"? When hand-writing the schema with composite keys, both keys were not "id" so they are both string(<fieldName>), which means in this case that the primary key of a model with composite key doesn't show which of the fields are its primary keys in the ModelPath extension. Should we just remove this completely? Currently, I haven't added the check for "is primaryKey and field value is 'id'" and simply generated "string()" for all string fields including properties called "id"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good callout. It made sense before when we had ids based on the naming convention. That not being the case anymore calls for a different approach, I think we can simply use the string() function.

FieldPath(name: name, parent: self)
}

public func string(_ name: String) -> FieldPath<String> {
Copy link
Contributor

@lawmicha lawmicha Dec 1, 2022

Choose a reason for hiding this comment

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

missing FieldPath<Double>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching that!

@lawmicha
Copy link
Contributor

lawmicha commented Jan 9, 2023

The changes from this PR has been merged with #2583 to data-dev-preview!

@lawmicha lawmicha closed this Feb 7, 2023
@atierian atierian deleted the feature/api-selection-set branch October 11, 2023 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issues related to the API category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants