Skip to content

Commit 7cc9826

Browse files
feat(api): Lazy Loading and Custom Selection Set (#2592)
Co-authored-by: Erica Eaton <[email protected]>
1 parent d22b04c commit 7cc9826

File tree

114 files changed

+9281
-731
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+9281
-731
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ __pycache__/
8686
**/amplifyconfiguration_v2.json
8787
**/credentials.json
8888
**/google_client_creds.json
89+
**/amplifyconfiguration*.json
8990

9091
# IDE files
9192
.idea/**

aws-api/src/main/java/com/amplifyframework/api/aws/ApiGraphQLRequestOptions.java renamed to aws-api-appsync/src/main/java/com/amplifyframework/api/aws/ApiGraphQLRequestOptions.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ public final class ApiGraphQLRequestOptions implements GraphQLRequestOptions {
2727
private static final String ITEMS_KEY = "items";
2828
private static final String NEXT_TOKEN_KEY = "nextToken";
2929

30+
private static final int DEFAULT_MAX_DEPTH = 2;
31+
32+
private int maxDepth = DEFAULT_MAX_DEPTH;
33+
34+
/**
35+
* Public constructor to create ApiGraphQLRequestOptions.
36+
*/
37+
public ApiGraphQLRequestOptions() {}
38+
39+
ApiGraphQLRequestOptions(int maxDepth) {
40+
this.maxDepth = maxDepth;
41+
}
42+
3043
@NonNull
3144
@Override
3245
public List<String> paginationFields() {
@@ -47,7 +60,7 @@ public String listField() {
4760

4861
@Override
4962
public int maxDepth() {
50-
return 2;
63+
return maxDepth;
5164
}
5265

5366
@NonNull

aws-api-appsync/src/main/java/com/amplifyframework/api/aws/GraphQLRequestHelper.java

+74-21
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
import com.amplifyframework.api.graphql.MutationType;
2323
import com.amplifyframework.core.model.AuthRule;
2424
import com.amplifyframework.core.model.AuthStrategy;
25+
import com.amplifyframework.core.model.LoadedModelReference;
2526
import com.amplifyframework.core.model.Model;
2627
import com.amplifyframework.core.model.ModelAssociation;
2728
import com.amplifyframework.core.model.ModelField;
2829
import com.amplifyframework.core.model.ModelIdentifier;
30+
import com.amplifyframework.core.model.ModelReference;
2931
import com.amplifyframework.core.model.ModelSchema;
3032
import com.amplifyframework.core.model.SerializedCustomType;
3133
import com.amplifyframework.core.model.SerializedModel;
@@ -171,7 +173,7 @@ public static Map<String, Object> getDeleteMutationInputMap(
171173
@NonNull ModelSchema schema, @NonNull Model instance) throws AmplifyException {
172174
final Map<String, Object> input = new HashMap<>();
173175
for (String fieldName : schema.getPrimaryIndexFields()) {
174-
input.put(fieldName, extractFieldValue(fieldName, instance, schema));
176+
input.put(fieldName, extractFieldValue(fieldName, instance, schema, true));
175177
}
176178
return input;
177179
}
@@ -224,21 +226,30 @@ private static Map<String, Object> extractFieldLevelData(
224226
continue;
225227
}
226228

227-
Object fieldValue = extractFieldValue(modelField.getName(), instance, schema);
229+
Object fieldValue = extractFieldValue(modelField.getName(), instance, schema, false);
230+
Object underlyingFieldValue = fieldValue;
231+
if (modelField.isModelReference() && fieldValue != null) {
232+
ModelReference<?> modelReference = (ModelReference<?>) fieldValue;
233+
if (modelReference instanceof LoadedModelReference) {
234+
underlyingFieldValue = ((LoadedModelReference) modelReference).getValue();
235+
}
236+
}
228237

229238
if (association == null) {
230239
result.put(fieldName, fieldValue);
231240
} else if (association.isOwner()) {
232-
if (fieldValue == null && MutationType.CREATE.equals(type)) {
241+
if ((fieldValue == null ||
242+
(modelField.isModelReference() && underlyingFieldValue == null)) &&
243+
MutationType.CREATE.equals(type)) {
233244
// Do not set null values on associations for create mutations.
234245
} else if (schema.getVersion() >= 1 && association.getTargetNames() != null
235246
&& association.getTargetNames().length > 0) {
236247
// When target name length is more than 0 there are two scenarios, one is when
237248
// there is custom primary key and other is when we have composite primary key.
238-
insertForeignKeyValues(result, modelField, fieldValue, association);
249+
insertForeignKeyValues(result, modelField, fieldValue, underlyingFieldValue, association);
239250
} else {
240251
String targetName = association.getTargetName();
241-
result.put(targetName, extractAssociateId(modelField, fieldValue));
252+
result.put(targetName, extractAssociateId(modelField, fieldValue, underlyingFieldValue));
242253
}
243254
}
244255
// Ignore if field is associated, but is not a "belongsTo" relationship
@@ -250,58 +261,94 @@ private static void insertForeignKeyValues(
250261
Map<String, Object> result,
251262
ModelField modelField,
252263
Object fieldValue,
264+
Object underlyingFieldValue,
253265
ModelAssociation association) {
254266
if (modelField.isModel() && fieldValue == null) {
255-
// When there is no model field value, set null for removal of values or deassociation.
267+
// When there is no model field value, set null for removal of values or association.
256268
for (String key : association.getTargetNames()) {
257269
result.put(key, null);
258270
}
259-
} else if (modelField.isModel() && fieldValue instanceof Model) {
260-
if (((Model) fieldValue).resolveIdentifier() instanceof ModelIdentifier<?>) {
261-
final ModelIdentifier<?> primaryKey = (ModelIdentifier<?>) ((Model) fieldValue).resolveIdentifier();
262-
ListIterator<String> targetNames = Arrays.asList(association.getTargetNames()).listIterator();
263-
Iterator<? extends Serializable> sortedKeys = primaryKey.sortedKeys().listIterator();
271+
} else if ((modelField.isModel() || modelField.isModelReference()) && underlyingFieldValue instanceof Model) {
272+
if (((Model) underlyingFieldValue).resolveIdentifier() instanceof ModelIdentifier<?>) {
273+
// Here, we are unwrapping our ModelReference to grab our foreign keys.
274+
// If we have a ModelIdentifier, we can pull all the key values, but we don't have
275+
// the key names. We must grab those from the association target names
276+
final ModelIdentifier<?> primaryKey =
277+
(ModelIdentifier<?>) ((Model) underlyingFieldValue).resolveIdentifier();
278+
ListIterator<String> targetNames =
279+
Arrays.asList(association.getTargetNames()).listIterator();
280+
Iterator<? extends Serializable> sortedKeys =
281+
primaryKey.sortedKeys().listIterator();
264282

265283
result.put(targetNames.next(), primaryKey.key());
266284

267285
while (targetNames.hasNext()) {
268286
result.put(targetNames.next(), sortedKeys.next());
269287
}
270-
} else if ((fieldValue instanceof SerializedModel)) {
271-
SerializedModel serializedModel = ((SerializedModel) fieldValue);
288+
} else if ((underlyingFieldValue instanceof SerializedModel)) {
289+
SerializedModel serializedModel = ((SerializedModel) underlyingFieldValue);
272290
ModelSchema serializedSchema = serializedModel.getModelSchema();
273291
if (serializedSchema != null &&
274292
serializedSchema.getPrimaryIndexFields().size() > 1) {
275293

276-
ListIterator<String> primaryKeyFieldsIterator = serializedSchema.getPrimaryIndexFields()
294+
ListIterator<String> primaryKeyFieldsIterator =
295+
serializedSchema.getPrimaryIndexFields()
277296
.listIterator();
278297
for (String targetName : association.getTargetNames()) {
279298
result.put(targetName, serializedModel.getSerializedData()
280299
.get(primaryKeyFieldsIterator.next()));
281300
}
282301
} else {
283-
result.put(association.getTargetNames()[0], ((Model) fieldValue).resolveIdentifier().toString());
302+
// our key was not a ModelIdentifier type, so it must be a singular primary key
303+
result.put(
304+
association.getTargetNames()[0],
305+
((Model) underlyingFieldValue).resolveIdentifier().toString()
306+
);
284307
}
285308
} else {
286-
result.put(association.getTargetNames()[0], ((Model) fieldValue).resolveIdentifier().toString());
309+
// our key was not a ModelIdentifier type, so it must be a singular primary key
310+
result.put(
311+
association.getTargetNames()[0],
312+
((Model) underlyingFieldValue).resolveIdentifier().toString()
313+
);
314+
}
315+
} else if (modelField.isModelReference() && fieldValue instanceof ModelReference) {
316+
// Here we are unwrapping our ModelReference and inserting
317+
Map<String, Object> identifiers = ((ModelReference<?>) fieldValue).getIdentifier();
318+
if (identifiers.isEmpty()) {
319+
for (String key : association.getTargetNames()) {
320+
result.put(key, null);
321+
}
287322
}
288323
}
289324
}
290325

291-
private static Object extractAssociateId(ModelField modelField, Object fieldValue) {
292-
if (modelField.isModel() && fieldValue instanceof Model) {
293-
return ((Model) fieldValue).resolveIdentifier();
326+
private static Object extractAssociateId(ModelField modelField, Object fieldValue, Object underlyingFieldValue) {
327+
if ((modelField.isModel() || modelField.isModelReference()) && underlyingFieldValue instanceof Model) {
328+
return ((Model) underlyingFieldValue).resolveIdentifier();
294329
} else if (modelField.isModel() && fieldValue instanceof Map) {
295330
return ((Map<?, ?>) fieldValue).get("id");
296331
} else if (modelField.isModel() && fieldValue == null) {
297332
// When there is no model field value, set null for removal of values or deassociation.
298333
return null;
334+
} else if (modelField.isModelReference() && fieldValue instanceof ModelReference) {
335+
Map<String, Object> identifiers = ((ModelReference<?>) fieldValue).getIdentifier();
336+
if (identifiers.isEmpty()) {
337+
return null;
338+
} else {
339+
return identifiers.get("id");
340+
}
299341
} else {
300342
throw new IllegalStateException("Associated data is not Model or Map.");
301343
}
302344
}
303345

304-
private static Object extractFieldValue(String fieldName, Model instance, ModelSchema schema)
346+
private static Object extractFieldValue(
347+
String fieldName,
348+
Model instance,
349+
ModelSchema schema,
350+
Boolean extractLazyValue
351+
)
305352
throws AmplifyException {
306353
if (instance instanceof SerializedModel) {
307354
SerializedModel serializedModel = (SerializedModel) instance;
@@ -316,7 +363,13 @@ private static Object extractFieldValue(String fieldName, Model instance, ModelS
316363
try {
317364
Field privateField = instance.getClass().getDeclaredField(fieldName);
318365
privateField.setAccessible(true);
319-
return privateField.get(instance);
366+
Object fieldInstance = privateField.get(instance);
367+
// In some cases, we don't want to return a ModelReference value. If extractLazyValue
368+
// is set, we unwrap the reference to grab to value underneath
369+
if (extractLazyValue && fieldInstance != null && privateField.getType() == LoadedModelReference.class) {
370+
return ((LoadedModelReference<?>) fieldInstance).getValue();
371+
}
372+
return fieldInstance;
320373
} catch (Exception exception) {
321374
throw new AmplifyException(
322375
"An invalid field was provided. " + fieldName + " is not present in " + schema.getName(),

0 commit comments

Comments
 (0)