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

V9 non course migration fix to check extra SQL accounts #12966

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 @@ -20,6 +20,11 @@
import com.google.cloud.datastore.QueryResults;
import com.googlecode.objectify.cmd.Query;

import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;

// import jakarta.persistence.criteria.CriteriaDelete;

import teammates.client.connector.DatastoreClient;
Expand Down Expand Up @@ -126,6 +131,27 @@ private void doMigration(teammates.storage.entity.Account entity) {
* Migrates the entity.
*/
protected void migrateEntity(teammates.storage.entity.Account oldAccount) {
HibernateUtil.beginTransaction();

CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder();
CriteriaQuery<teammates.storage.sqlentity.Account> cr = cb.createQuery(
teammates.storage.sqlentity.Account.class);
Root<teammates.storage.sqlentity.Account> root = cr.from(teammates.storage.sqlentity.Account.class);
cr.select(root).where(cb.equal(root.get("googleId"), oldAccount.getGoogleId()));

TypedQuery<teammates.storage.sqlentity.Account> query = HibernateUtil.createQuery(cr);


boolean isEntityInDb = query.getResultList().size() != 0;
HibernateUtil.commitTransaction();

// In db, but somehow not set as migrated.
if (isEntityInDb) {
oldAccount.setMigrated(true);
entitiesOldAccountSavingBuffer.add(oldAccount);
return;
};

teammates.storage.sqlentity.Account newAccount = new teammates.storage.sqlentity.Account(
oldAccount.getGoogleId(),
oldAccount.getName(),
Expand All @@ -136,7 +162,6 @@ protected void migrateEntity(teammates.storage.entity.Account oldAccount) {
oldAccount.setMigrated(true);
entitiesOldAccountSavingBuffer.add(oldAccount);
migrateReadNotification(oldAccount, newAccount);

}

private void migrateReadNotification(teammates.storage.entity.Account oldAccount,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package teammates.client.scripts.sql;

import java.io.IOException;
// CHECKSTYLE.OFF:ImportOrder
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import com.google.cloud.datastore.Cursor;
import com.google.cloud.datastore.QueryResults;
import com.googlecode.objectify.cmd.Query;

import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Root;
import teammates.storage.sqlentity.Account;
import teammates.test.FileHelper;
import teammates.client.connector.DatastoreClient;
import teammates.client.util.ClientProperties;
import teammates.common.util.HibernateUtil;
// CHECKSTYLE.ON:ImportOrder

/**
* Protected methods may be overriden.
*/
@SuppressWarnings("PMD")
public class MarkIsMigratedForAccounts extends DatastoreClient {
private static final String BASE_LOG_URI = "src/client/java/teammates/client/scripts/log/";

/* NOTE
* Before running the verification, please enable hibernate.jdbc.fetch_size in HibernateUtil.java
* for optimized batch-fetching.
*/

/**
* Batch size to fetch per page.
*/
protected static final int CONST_SQL_FETCH_BASE_SIZE = 1000;

AtomicLong numberOfScannedKey;
AtomicLong numberOfAffectedEntities;
AtomicLong numberOfUpdatedEntities;

/** Datastore entity class. */
protected Class<teammates.storage.entity.Account> datastoreEntityClass = teammates.storage.entity.Account.class;

/** SQL entity class. */
protected Class<Account> sqlEntityClass = Account.class;

private long entitiesVerified = 0;
private long entitiesSetToIsMigrated = 0;

public MarkIsMigratedForAccounts() {
numberOfScannedKey = new AtomicLong();
numberOfAffectedEntities = new AtomicLong();
numberOfUpdatedEntities = new AtomicLong();

String connectionUrl = ClientProperties.SCRIPT_API_URL;
String username = ClientProperties.SCRIPT_API_NAME;
String password = ClientProperties.SCRIPT_API_PASSWORD;

HibernateUtil.buildSessionFactory(connectionUrl, username, password);
}

private String getLogPrefix() {
return String.format("%s verifying fields:", sqlEntityClass.getName());
}

/**
* Generate the Datstore id of entity to compare with on Datastore side.
*/
protected String generateID(Account sqlEntity) {
return sqlEntity.getGoogleId();
}

/**
* Lookup data store entities.
*/
protected Map<String, teammates.storage.entity.Account> lookupDataStoreEntities(List<String> datastoreEntitiesIds) {
return ofy().load().type(teammates.storage.entity.Account.class).ids(datastoreEntitiesIds);
}

/**
* Calculate offset.
*/
protected int calculateOffset(int pageNum) {
return (pageNum - 1) * CONST_SQL_FETCH_BASE_SIZE;
}

/**
* Get number of pages in database table.
*/
protected Query<teammates.storage.entity.Account> getFilterQuery() {
return ofy().load().type(teammates.storage.entity.Account.class);
}

/**
* Get number of pages in database table.
*/
private int getNumPages() {
CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
countQuery.select(cb.count(countQuery.from(sqlEntityClass)));
long countResults = HibernateUtil.createQuery(countQuery).getSingleResult().longValue();
int numPages = (int) (Math.ceil((double) countResults / (double) CONST_SQL_FETCH_BASE_SIZE));
log(String.format("Has %d entities with %d pages", countResults, numPages));

return numPages;
}

/**
* Sort SQL entities by id in ascending order and return entities on page.
* @param pageNum page in a sorted entities tables
* @return list of SQL entities on page num
*/
protected List<Account> lookupSqlEntitiesByPageNumber(int pageNum) {
CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder();
CriteriaQuery<Account> pageQuery = cb.createQuery(sqlEntityClass);

// sort by id to maintain stable order.
Root<Account> root = pageQuery.from(sqlEntityClass);
pageQuery.select(root);
List<Order> orderList = new LinkedList<>();
orderList.add(cb.asc(root.get("id")));
pageQuery.orderBy(orderList);

// perform query with pagination
TypedQuery<Account> query = HibernateUtil.createQuery(pageQuery);
query.setFirstResult(calculateOffset(pageNum));
query.setMaxResults(CONST_SQL_FETCH_BASE_SIZE);

return query.getResultList();
}

/**
* Lookup sql side, have all the sql entities for each sql entity, lookup
* datastore entity.
* If does not match, return failure.
*/
protected List<Map.Entry<Account, Account>> checkAllEntitiesForFailures() {
// WARNING: failures list might lead to OoM if too many entities,
// but okay since will fail anyway.
List<Map.Entry<Account, Account>> failures = new LinkedList<>();
int numPages = getNumPages();
if (numPages == 0) {
log("No entities available for verification");
return failures;
}

List<teammates.storage.entity.Account> setMigratedAccountBuffer = new ArrayList<>();

/* Query SQL and compare against datastore */
for (int currPageNum = 1; currPageNum <= numPages; currPageNum++) {
log(String.format("Scanning Progress %d %%",
(int) ((float) currPageNum / (float) numPages * 100)));

long startTimeForSql = System.currentTimeMillis();
List<Account> sqlEntities = lookupSqlEntitiesByPageNumber(currPageNum);
long endTimeForSql = System.currentTimeMillis();
log("Querying for SQL for page " + currPageNum + " took "
+ (endTimeForSql - startTimeForSql) + " milliseconds");

List<String> datastoreEntitiesIds = sqlEntities.stream()
.map(entity -> generateID(entity)).collect(Collectors.toList());

long startTimeForDatastore = System.currentTimeMillis();
Map<String, teammates.storage.entity.Account> datastoreEntities = lookupDataStoreEntities(datastoreEntitiesIds);
long endTimeForDatastore = System.currentTimeMillis();
log("Querying for Datastore for page " + currPageNum + " took "
+ (endTimeForDatastore - startTimeForDatastore) + " milliseconds");

entitiesVerified += sqlEntities.size();
for (Account sqlEntity : sqlEntities) {
teammates.storage.entity.Account datastoreEntity = datastoreEntities.get(generateID(sqlEntity));
if (datastoreEntity == null) {
entitiesVerified -= 1;
failures.add(new AbstractMap.SimpleEntry<Account, Account>(sqlEntity, null));
continue;
}

if (!datastoreEntity.isMigrated()) {
datastoreEntity.setMigrated(true);
setMigratedAccountBuffer.add(datastoreEntity);
}
}

/* Flushing the buffer */
if (setMigratedAccountBuffer.size() != 0 ) {
long startTimeForDatastoreFlushing = System.currentTimeMillis();
entitiesSetToIsMigrated += setMigratedAccountBuffer.size();
ofy().save().entities(setMigratedAccountBuffer).now();
setMigratedAccountBuffer.clear();
long endTimeForDatastoreFlushing = System.currentTimeMillis();
log("Flushing for datastore " + (endTimeForDatastoreFlushing - startTimeForDatastoreFlushing) + " milliseconds");
}
}

/* Query datastore and compare against SQL */
return failures;
}

/**
* Main function to run to verify isEqual between sql and datastore DBs.
*/
protected void runCheckAllEntities(Class<Account> sqlEntityClass,
Class<teammates.storage.entity.Account> datastoreEntityClass) {
HibernateUtil.beginTransaction();
long checkStartTime = System.currentTimeMillis();
List<Map.Entry<Account, Account>> failedEntities = checkAllEntitiesForFailures();

System.out.println("========================================");
if (!failedEntities.isEmpty()) {
log("Errors detected");
for (Map.Entry<Account, Account> failure : failedEntities) {
log("Sql entity: " + failure.getKey() + " datastore entity: " + failure.getValue());
}
} else {
log("No errors detected");
}

long checkEndTime = System.currentTimeMillis();

log("Entity took " + (checkEndTime - checkStartTime) + " milliseconds to verify");
log("Verified " + entitiesVerified + " SQL entities successfully");
log("Number of datastore accounts set to isMigrated " + entitiesSetToIsMigrated);

HibernateUtil.commitTransaction();
}

/**
* Log a line.
* @param logLine the line to log
*/
protected void log(String logLine) {
System.out.println(String.format("%s %s", getLogPrefix(), logLine));
}

/**
* Run the operation.
*/
protected void doOperation() {
runCheckAllEntities(this.sqlEntityClass, this.datastoreEntityClass);
}

public static void main(String[] args) {
MarkIsMigratedForAccounts operation = new MarkIsMigratedForAccounts();
operation.doOperationRemotely();
}
}