Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datastore): Add syncExpression field to LastSyncMetadata #2936

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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 @@ -934,7 +934,10 @@ private Completable updateModels() {
Objects.requireNonNull(databaseConnectionHandle);
sqliteStorageHelper.update(databaseConnectionHandle, oldVersion, newVersion);
} else {
LOG.debug("Database up to date. Checking ModelMetadata.");
// We only need to do the model migration here because
// the current implementation of sqliteStorageHelper.update will drop all existing tables and recreate tables with new schemas,
// However, this might be changed in the future
LOG.debug("Database up to date. Checking System Models.");
new ModelMigrations(databaseConnectionHandle, modelsProvider).apply();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package com.amplifyframework.datastore.storage.sqlite.migrations;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.amplifyframework.core.Amplify;
import com.amplifyframework.core.category.CategoryType;
import com.amplifyframework.core.model.ModelProvider;
import com.amplifyframework.logging.Logger;
import com.amplifyframework.util.Wrap;

public class AddSyncExpressionToLastSyncMetadata implements ModelMigration{
private static final Logger LOG = Amplify.Logging.logger(CategoryType.DATASTORE, "amplify:aws-datastore");
private final SQLiteDatabase database;
private final ModelProvider modelProvider;
private final String newSyncExpColumnName = "syncExpression";

/**
* Constructor for the migration class.
* @param database Connection to the SQLite database.
* @param modelProvider The model provider.
*/
public AddSyncExpressionToLastSyncMetadata(SQLiteDatabase database, ModelProvider modelProvider) {
this.database = database;
this.modelProvider = modelProvider;
}

@Override
public void apply() {
if(!needsMigration()) {
LOG.debug("No LastSyncMetadata migration needed.");
return;
}
addNewSyncExpColumnName();
}

/**
* Existing rows in LasySyncMetadata will have 'null' for ${newSyncExpColumnName} value,
* until the next sync/hydrate operation
*/
private void addNewSyncExpColumnName() {
try {
database.beginTransaction();
final String addColumnSql = "ALTER TABLE LastSyncMetadata ADD COLUMN " +
newSyncExpColumnName + " TEXT";
database.execSQL(addColumnSql);
database.setTransactionSuccessful();
LOG.debug("Successfully upgraded LastSyncMetadata table with new field: " + newSyncExpColumnName);
} finally {
if (database.inTransaction()) {
database.endTransaction();
}
}
}

private boolean needsMigration() {
final String checkColumnSql = "SELECT COUNT(*) FROM pragma_table_info('LastSyncMetadata') " +
"WHERE name=" + Wrap.inSingleQuotes(newSyncExpColumnName);
try (Cursor queryResults = database.rawQuery(checkColumnSql, new String[]{})) {
if (queryResults.moveToNext()) {
int recordNum = queryResults.getInt(0);
return recordNum==0; // needs to be upgraded if there's no column named ${newSyncExpColumnName}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class ModelMigrations {
public ModelMigrations(SQLiteDatabase databaseConnectionHandle, ModelProvider modelsProvider) {
List<ModelMigration> migrationClasses = new ArrayList<>();
migrationClasses.add(new AddModelNameToModelMetadataKey(databaseConnectionHandle, modelsProvider));
migrationClasses.add(new AddSyncExpressionToLastSyncMetadata(databaseConnectionHandle, modelsProvider));
this.modelMigrations = Immutable.of(migrationClasses);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import java.util.UUID;

/**
* Metadata about the last time that a model class was sync'd with AppSync backend.
* Metadata about the last time that a model class was sync'd with AppSync backend using a certain syncExpression.
* This metadata is persisted locally as a system model. This metadata is inspected
* whenever the Sync Engine starts up. The system consider the value of
* {@link LastSyncMetadata#getLastSyncTime()} to decide whether or not it should
Expand All @@ -39,13 +39,15 @@ public final class LastSyncMetadata implements Model {
private final @ModelField(targetType = "String", isRequired = true) String modelClassName;
private final @ModelField(targetType = "AWSTimestamp", isRequired = true) Long lastSyncTime;
private final @ModelField(targetType = "String", isRequired = true) String lastSyncType;
private final @ModelField(targetType = "String") String syncExpression;

@SuppressWarnings("checkstyle:ParameterName") // The field is named "id" in the model; keep it consistent
private LastSyncMetadata(String id, String modelClassName, Long lastSyncTime, SyncType syncType) {
private LastSyncMetadata(String id, String modelClassName, Long lastSyncTime, SyncType syncType, @Nullable String syncExpression) {
this.id = id;
this.modelClassName = modelClassName;
this.lastSyncTime = lastSyncTime;
this.lastSyncType = syncType.name();
this.syncExpression = syncExpression;
}

/**
Expand All @@ -57,9 +59,9 @@ private LastSyncMetadata(String id, String modelClassName, Long lastSyncTime, Sy
* @return {@link LastSyncMetadata} for the model class
*/
public static <T extends Model> LastSyncMetadata baseSyncedAt(@NonNull String modelClassName,
@Nullable long lastSyncTime) {
@Nullable long lastSyncTime, @Nullable String syncExpression) {
Objects.requireNonNull(modelClassName);
return create(modelClassName, lastSyncTime, SyncType.BASE);
return create(modelClassName, lastSyncTime, SyncType.BASE, syncExpression);
}

/**
Expand All @@ -70,9 +72,9 @@ public static <T extends Model> LastSyncMetadata baseSyncedAt(@NonNull String mo
* @return {@link LastSyncMetadata} for the model class
*/
static <T extends Model> LastSyncMetadata deltaSyncedAt(@NonNull String modelClassName,
@Nullable long lastSyncTime) {
@Nullable long lastSyncTime, @Nullable String syncExpression) {
Objects.requireNonNull(modelClassName);
return create(modelClassName, lastSyncTime, SyncType.DELTA);
return create(modelClassName, lastSyncTime, SyncType.DELTA, syncExpression);
}

/**
Expand All @@ -84,7 +86,7 @@ static <T extends Model> LastSyncMetadata deltaSyncedAt(@NonNull String modelCla
*/
public static <T extends Model> LastSyncMetadata neverSynced(@NonNull String modelClassName) {
Objects.requireNonNull(modelClassName);
return create(modelClassName, null, SyncType.BASE);
return create(modelClassName, null, SyncType.BASE, null);
}

/**
Expand All @@ -97,9 +99,9 @@ public static <T extends Model> LastSyncMetadata neverSynced(@NonNull String mod
*/
@SuppressWarnings("WeakerAccess")
static <T extends Model> LastSyncMetadata create(
@NonNull String modelClassName, @Nullable Long lastSyncTime, @NonNull SyncType syncType) {
@NonNull String modelClassName, @Nullable Long lastSyncTime, @NonNull SyncType syncType, @Nullable String syncExpression) {
Objects.requireNonNull(modelClassName);
return new LastSyncMetadata(hash(modelClassName), modelClassName, lastSyncTime, syncType);
return new LastSyncMetadata(hash(modelClassName), modelClassName, lastSyncTime, syncType, syncExpression);
}

@NonNull
Expand Down Expand Up @@ -144,6 +146,14 @@ public String getLastSyncType() {
return lastSyncType;
}

/**
* Returns the sync expression being used in the last sync
* @return A serialized sync expression
*/
public String getSyncExpression() {
return this.syncExpression;
}

/**
* Computes a stable hash for a model class, by its name.
* Since {@link Model}s have to have unique IDs, we need an ID for this class.
Expand Down Expand Up @@ -175,6 +185,9 @@ public boolean equals(Object thatObject) {
if (!ObjectsCompat.equals(lastSyncType, that.lastSyncType)) {
return false;
}
if (!ObjectsCompat.equals(syncExpression, that.syncExpression)) {
return false;
}
return ObjectsCompat.equals(lastSyncTime, that.lastSyncTime);
}

Expand All @@ -184,6 +197,7 @@ public int hashCode() {
result = 31 * result + modelClassName.hashCode();
result = 31 * result + lastSyncTime.hashCode();
result = 31 * result + lastSyncType.hashCode();
result = 31 * result + syncExpression.hashCode();
return result;
}

Expand All @@ -194,6 +208,7 @@ public String toString() {
", modelClassName='" + modelClassName + '\'' +
", lastSyncTime=" + lastSyncTime +
", lastSyncType=" + lastSyncType +
", syncExpression" + syncExpression +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ Completable hydrate() {

private Completable createHydrationTask(ModelSchema schema) {
ModelSyncMetricsAccumulator metricsAccumulator = new ModelSyncMetricsAccumulator(schema.getName());
//TODO: QueryPredicate currentPredicate = this.queryPredicateProvider.getPredicate(schema.getName());
//TODO deserialize current predicate to deserializedCurrentPredicate
//TODO: pass deserializedCurrentPredicate to lookupLastSyncTime as the second parameter
return syncTimeRegistry.lookupLastSyncTime(schema.getName())
.map(this::filterOutOldSyncTimes)
// And for each, perform a sync. The network response will contain an Iterable<ModelWithMetadata<T>>
Expand All @@ -201,9 +204,10 @@ private Completable createHydrationTask(ModelSchema schema) {
.toSingle(() -> lastSyncTime.exists() ? SyncType.DELTA : SyncType.BASE);
})
.flatMapCompletable(syncType -> {
//TODO: pass the deserializedCurrentPredicate to saveLastDelta/BaseSync as the third parameter
Completable syncTimeSaveCompletable = SyncType.DELTA.equals(syncType) ?
syncTimeRegistry.saveLastDeltaSyncTime(schema.getName(), SyncTime.now()) :
syncTimeRegistry.saveLastBaseSyncTime(schema.getName(), SyncTime.now());
syncTimeRegistry.saveLastDeltaSyncTime(schema.getName(), SyncTime.now(), null) :
syncTimeRegistry.saveLastBaseSyncTime(schema.getName(), SyncTime.now(), null);
return syncTimeSaveCompletable.andThen(Completable.fromAction(() ->
Amplify.Hub.publish(
HubChannel.DATASTORE, metricsAccumulator.toModelSyncedEvent(syncType).toHubEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ final class SyncTimeRegistry {
this.localStorageAdapter = localStorageAdapter;
}

//TODO: add the second argument: current sync expression here
Single<SyncTime> lookupLastSyncTime(@NonNull String modelClassName) {
return Single.create(emitter -> {
QueryPredicate hasMatchingModelClassName = QueryField.field("modelClassName").eq(modelClassName);

localStorageAdapter.query(LastSyncMetadata.class, Where.matches(hasMatchingModelClassName), results -> {
try {
LastSyncMetadata syncMetadata = extractSingleResult(modelClassName, results);
//TODO: syncMetadata should contain the previous serialized sync expression
//TODO: compare the previous sync expression with the current one
//TODO: emmit SyncTime.never() if different
emitter.onSuccess(SyncTime.from(syncMetadata.getLastSyncTime()));
} catch (DataStoreException queryResultFailure) {
emitter.onError(queryResultFailure);
Expand All @@ -55,9 +59,10 @@ Single<SyncTime> lookupLastSyncTime(@NonNull String modelClassName) {
});
}

Completable saveLastDeltaSyncTime(@NonNull String modelClassName, @Nullable SyncTime syncTime) {
//TODO: change the name to saveLastDeltaSync, and add the second argument for sync expression
Completable saveLastDeltaSyncTime(@NonNull String modelClassName, @Nullable SyncTime syncTime, @Nullable String syncExpression) {
LastSyncMetadata metadata = syncTime != null && syncTime.exists() ?
LastSyncMetadata.deltaSyncedAt(modelClassName, syncTime.toLong()) :
LastSyncMetadata.deltaSyncedAt(modelClassName, syncTime.toLong(), syncExpression) :
LastSyncMetadata.neverSynced(modelClassName);

return Completable.create(emitter ->
Expand All @@ -71,9 +76,10 @@ Completable saveLastDeltaSyncTime(@NonNull String modelClassName, @Nullable Sync
);
}

Completable saveLastBaseSyncTime(@NonNull String modelClassName, @Nullable SyncTime syncTime) {
//TODO: change the name to saveLastDeltaSync, and add the second argument for sync expression
Completable saveLastBaseSyncTime(@NonNull String modelClassName, @Nullable SyncTime syncTime, @Nullable String syncExpression) {
LastSyncMetadata metadata = syncTime != null && syncTime.exists() ?
LastSyncMetadata.baseSyncedAt(modelClassName, syncTime.toLong()) :
LastSyncMetadata.baseSyncedAt(modelClassName, syncTime.toLong(), syncExpression) :
LastSyncMetadata.neverSynced(modelClassName);

return Completable.create(emitter ->
Expand Down
Loading