From 0798e80d2bb0acf0e74e1edd30736ce1b21b9567 Mon Sep 17 00:00:00 2001 From: Nicolas <25302138+NicolasCwy@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:09:04 +0800 Subject: [PATCH] [#12048] Fix course migration script (#13111) * TO REMOVE: Code for development * FIXING COURSES * TO REMOVE TEST SCRIPT * Add logger class * Remove unused course script * Comment out chain verification * Fix seed error if entity count is too low * Comment out seed notif * Comment out notification seed * Remove account request seed * Verify section migration * Fix team migration * Remove previously added column * Fix student migration * Add back updated timestamp * Add more fixes for students * Verified feedback session migration * Remove previously added constraint To be fixed in the main branch instead * Verify feedback questions migration * Verify Feedback response migration * Fix broken seed for feedback questions and response * Remove feedback response updatedAtTimestamp annotation * Verify instructor migration * Fix instructor seed - name * Verify feedback response comment migration * Fix Deadline extension * Remove code used for development * Remove verbose logging for deadline extension --------- Co-authored-by: marquestye --- ...ationForAccountAndReadNotificationSql.java | 1 + .../sql/DataMigrationForCourseEntitySql.java | 668 ++++++++++++------ .../sql/DataMigrationForCourseSql.java | 57 -- .../teammates/client/scripts/sql/Logger.java | 50 ++ .../teammates/client/scripts/sql/SeedDb.java | 76 +- .../sql/VerifyCourseEntityAttributes.java | 482 +++++++++---- ...fyNonCourseEntityAttributesBaseScript.java | 28 +- .../storage/sqlentity/DeadlineExtension.java | 3 +- .../storage/sqlentity/FeedbackQuestion.java | 36 +- .../storage/sqlentity/FeedbackResponse.java | 23 +- .../sqlentity/FeedbackResponseComment.java | 3 +- .../storage/sqlentity/FeedbackSession.java | 3 +- .../teammates/storage/sqlentity/Section.java | 3 - .../teammates/storage/sqlentity/User.java | 4 +- 14 files changed, 945 insertions(+), 492 deletions(-) delete mode 100644 src/client/java/teammates/client/scripts/sql/DataMigrationForCourseSql.java create mode 100644 src/client/java/teammates/client/scripts/sql/Logger.java diff --git a/src/client/java/teammates/client/scripts/sql/DataMigrationForAccountAndReadNotificationSql.java b/src/client/java/teammates/client/scripts/sql/DataMigrationForAccountAndReadNotificationSql.java index 3c753be109f..5fa005c4aa4 100644 --- a/src/client/java/teammates/client/scripts/sql/DataMigrationForAccountAndReadNotificationSql.java +++ b/src/client/java/teammates/client/scripts/sql/DataMigrationForAccountAndReadNotificationSql.java @@ -226,6 +226,7 @@ protected void doOperation() { } else { flushEntitiesSavingBuffer(); } + flushEntitiesSavingBuffer(); } deleteCursorPositionFile(); diff --git a/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseEntitySql.java b/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseEntitySql.java index 50e3efada5f..a13b4c8ce1b 100644 --- a/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseEntitySql.java +++ b/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseEntitySql.java @@ -2,17 +2,14 @@ import java.io.File; import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; @@ -33,23 +30,23 @@ import teammates.common.datatransfer.InstructorPermissionRole; import teammates.common.datatransfer.InstructorPrivileges; import teammates.common.datatransfer.InstructorPrivilegesLegacy; -import teammates.common.util.Const; import teammates.common.util.HibernateUtil; import teammates.common.util.JsonUtils; +import teammates.common.util.SanitizationHelper; import teammates.storage.sqlentity.Account; import teammates.storage.sqlentity.BaseEntity; import teammates.storage.sqlentity.Instructor; import teammates.storage.sqlentity.Section; +import teammates.storage.sqlentity.Course; import teammates.storage.sqlentity.Student; import teammates.storage.sqlentity.Team; -import teammates.storage.entity.Course; +import teammates.storage.sqlentity.FeedbackSession; import teammates.storage.entity.CourseStudent; -import teammates.storage.entity.DeadlineExtension; import teammates.storage.entity.FeedbackQuestion; import teammates.storage.entity.FeedbackResponse; import teammates.storage.entity.FeedbackResponseComment; -import teammates.storage.entity.FeedbackSession; import teammates.storage.sqlentity.User; +import teammates.storage.sqlentity.DeadlineExtension; import teammates.storage.sqlentity.questions.FeedbackConstantSumQuestion.FeedbackConstantSumQuestionDetailsConverter; import teammates.storage.sqlentity.questions.FeedbackContributionQuestion.FeedbackContributionQuestionDetailsConverter; import teammates.storage.sqlentity.questions.FeedbackMcqQuestion.FeedbackMcqQuestionDetailsConverter; @@ -82,6 +79,8 @@ public class DataMigrationForCourseEntitySql extends DatastoreClient { private List entitiesSavingBuffer; + private Logger logger; + // Creates the folder that will contain the stored log. static { new File(BASE_LOG_URI).mkdir(); @@ -101,6 +100,8 @@ public DataMigrationForCourseEntitySql() { numberOfUpdatedEntities = new AtomicLong(); entitiesSavingBuffer = new ArrayList<>(); + + logger = new Logger("Course Chain Migration:"); verifier = new VerifyCourseEntityAttributes(); @@ -115,7 +116,7 @@ public static void main(String[] args) { new DataMigrationForCourseEntitySql().doOperationRemotely(); } - protected Query getFilterQuery() { + protected Query getFilterQuery() { return ofy().load().type(teammates.storage.entity.Course.class); } @@ -126,16 +127,23 @@ protected boolean isPreview() { /** * Migrates the course and all related entity. */ - protected void migrateCourse(Course oldCourse) throws Exception { + protected void migrateCourse(teammates.storage.entity.Course oldCourse) throws Exception { log("Start migrating course with id: " + oldCourse.getUniqueId()); - teammates.storage.sqlentity.Course newCourse = createCourse(oldCourse); - - migrateCourseEntity(newCourse); + String courseId = migrateCourseEntity(oldCourse); + migrateCourseDependencies(courseId); flushEntitiesSavingBuffer(); + // Refetch course - this is not needed but done to get around + // the inherited interface of verifier + + HibernateUtil.beginTransaction(); + Course newCourse = getCourse(courseId); + HibernateUtil.commitTransaction(); + + log(String.format("Verifying %s", courseId)); + if (!verifier.equals(newCourse, oldCourse)) { - logError("Verification failed for course with id: " + oldCourse.getUniqueId()); - return; + throw new Exception("Verification failed for course with id: " + oldCourse.getUniqueId()); } // TODO: markOldCourseAsMigrated(courseId) @@ -143,81 +151,192 @@ protected void migrateCourse(Course oldCourse) throws Exception { log("Finish migrating course with id: " + oldCourse.getUniqueId()); } - private void migrateCourseEntity(teammates.storage.sqlentity.Course newCourse) { + private void migrateCourseDependencies(String newCourseId) { Map userGoogleIdToUserMap = new HashMap<>(); Map emailToInstructorMap = new HashMap<>(); Map emailToStudentMap = new HashMap<>(); - Map sectionNameToSectionMap = migrateSectionChain(newCourse, userGoogleIdToUserMap, emailToStudentMap); - Map feedbackSessionNameToFeedbackSessionMap = - migrateFeedbackChain(newCourse, sectionNameToSectionMap); - migrateInstructorEntities(newCourse, userGoogleIdToUserMap, emailToInstructorMap); - migrateUserAccounts(newCourse, userGoogleIdToUserMap); - migrateDeadlineExtensionEntities(newCourse, feedbackSessionNameToFeedbackSessionMap, emailToInstructorMap, emailToStudentMap); + migrateSectionChain(newCourseId, userGoogleIdToUserMap, emailToStudentMap); + migrateFeedbackChain(newCourseId); + // Map feedbackSessionNameToFeedbackSessionMap = + // migrateFeedbackChain(newCourse, sectionNameToSectionMap); + migrateInstructorEntities(newCourseId, userGoogleIdToUserMap); + // migrateUserAccounts(newCourse, userGoogleIdToUserMap); + migrateDeadlineExtensionEntities(newCourseId); } // methods for migrate section chain ---------------------------------------------------------------------------------- // entities: Section, Team, Student - private Map migrateSectionChain( - teammates.storage.sqlentity.Course newCourse, Map userGoogleIdToUserMap, Map emailToStudentMap) { - log("Migrating section chain"); - List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", newCourse.getId()) - .list(); + private void migrateSectionChain( + String courseId, Map userGoogleIdToUserMap, Map emailToStudentMap) { + log(String.format("Migrating section chain for %s", courseId)); + + migrateSections(courseId); + migrateTeams(courseId); + migrateStudents(courseId); + + // Map sections = new HashMap<>(); + // Map> sectionToStuMap = oldStudents.stream() + // .collect(Collectors.groupingBy(CourseStudent::getSectionName)); + + // for (Map.Entry> entry : sectionToStuMap.entrySet()) { + // String sectionName = entry.getKey(); + // List stuList = entry.getValue(); + // // Section newSection = createSection(newCourse, sectionName); + // sections.put(sectionName, newSection); + // saveEntityDeferred(newSection); + // // migrateTeams(newCourse, newSection, stuList, userGoogleIdToUserMap, emailToStudentMap); + // } + } + + // private void migrateTeams(Course newCourse, + // Section newSection, List studentsInSection, + // Map userGoogleIdToUserMap, Map emailToStudentMap) { + // Map> teamNameToStuMap = studentsInSection.stream() + // .collect(Collectors.groupingBy(CourseStudent::getTeamName)); + // for (Map.Entry> entry : teamNameToStuMap.entrySet()) { + // String teamName = entry.getKey(); + // List stuList = entry.getValue(); + // teammates.storage.sqlentity.Team newTeam = createTeam(newSection, teamName); + // saveEntityDeferred(newTeam); + // migrateStudents(newCourse, newTeam, stuList, userGoogleIdToUserMap, emailToStudentMap); + // } + // } + + // private void migrateStudents(Course newCourse, teammates.storage.sqlentity.Team newTeam, + // List studentsInTeam, Map userGoogleIdToUserMap, Map emailToStudentMap) { + // for (CourseStudent oldStudent : studentsInTeam) { + // teammates.storage.sqlentity.Student newStudent = migrateStudent(newCourse, newTeam, oldStudent); + // emailToStudentMap.put(newStudent.getEmail(), newStudent); + // if (oldStudent.getGoogleId() != null) { + // userGoogleIdToUserMap.put(oldStudent.getGoogleId(), newStudent); + // } + // } + // } + + private String migrateCourseEntity(teammates.storage.entity.Course oldCourse) { + Course newCourse = new Course( + oldCourse.getUniqueId(), + oldCourse.getName(), + oldCourse.getTimeZone(), + oldCourse.getInstitute()); + newCourse.setDeletedAt(oldCourse.getDeletedAt()); + newCourse.setCreatedAt(oldCourse.getCreatedAt()); - Map sections = new HashMap<>(); - Map> sectionToStuMap = oldStudents.stream() - .collect(Collectors.groupingBy(CourseStudent::getSectionName)); + HibernateUtil.beginTransaction(); + HibernateUtil.persist(newCourse); + HibernateUtil.commitTransaction(); + return newCourse.getId(); + } - for (Map.Entry> entry : sectionToStuMap.entrySet()) { - String sectionName = entry.getKey(); - List stuList = entry.getValue(); - teammates.storage.sqlentity.Section newSection = createSection(newCourse, sectionName); - sections.put(sectionName, newSection); - saveEntityDeferred(newSection); - migrateTeams(newCourse, newSection, stuList, userGoogleIdToUserMap, emailToStudentMap); - } - return sections; - } - - private void migrateTeams(teammates.storage.sqlentity.Course newCourse, - teammates.storage.sqlentity.Section newSection, List studentsInSection, - Map userGoogleIdToUserMap, Map emailToStudentMap) { - Map> teamNameToStuMap = studentsInSection.stream() - .collect(Collectors.groupingBy(CourseStudent::getTeamName)); - for (Map.Entry> entry : teamNameToStuMap.entrySet()) { - String teamName = entry.getKey(); - List stuList = entry.getValue(); - teammates.storage.sqlentity.Team newTeam = createTeam(newSection, teamName); - saveEntityDeferred(newTeam); - migrateStudents(newCourse, newTeam, stuList, userGoogleIdToUserMap, emailToStudentMap); + private void migrateSections(String courseId) { + log(String.format("Migrating Sections for course %s", courseId)); + HibernateUtil.beginTransaction(); + + List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", courseId) + .list(); + + Course newCourse = getCourse(courseId); + + List sectionNames = oldStudents.stream().map(student -> student.getSectionName()) + .distinct().collect(Collectors.toList()); + + for (String sectionName : sectionNames) { + Section newSection = createSection(newCourse, sectionName); + HibernateUtil.persist(newSection); } + HibernateUtil.commitTransaction(); } - private void migrateStudents(teammates.storage.sqlentity.Course newCourse, teammates.storage.sqlentity.Team newTeam, - List studentsInTeam, Map userGoogleIdToUserMap, Map emailToStudentMap) { - for (CourseStudent oldStudent : studentsInTeam) { - teammates.storage.sqlentity.Student newStudent = migrateStudent(newCourse, newTeam, oldStudent); - emailToStudentMap.put(newStudent.getEmail(), newStudent); - if (oldStudent.getGoogleId() != null) { - userGoogleIdToUserMap.put(oldStudent.getGoogleId(), newStudent); + private void migrateTeams(String courseId) { + log(String.format("Migrating Teams for course %s", courseId)); + HibernateUtil.beginTransaction(); + + List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", courseId) + .list(); + + // Assume that team names are unique within a section but not unique among all sections + Map> oldSectionToTeamHashSet = new HashMap>(); + for (CourseStudent student : oldStudents) { + String sectionName = student.getSectionName(); + oldSectionToTeamHashSet.putIfAbsent(sectionName, new HashSet<>()); + HashSet teamHashSet = oldSectionToTeamHashSet.get(sectionName); + teamHashSet.add(student.getTeamName()); + } + + for (Entry> entrySet : oldSectionToTeamHashSet.entrySet()) { + String oldSectionName = entrySet.getKey(); + HashSet oldTeams = entrySet.getValue(); + Section newSection = getSection(courseId, oldSectionName); + for (String teamName : oldTeams) { + Team newTeam = createTeam(newSection, teamName); + HibernateUtil.persist(newTeam); } } + + HibernateUtil.commitTransaction(); } - private teammates.storage.sqlentity.Course createCourse(Course oldCourse) { - teammates.storage.sqlentity.Course newCourse = new teammates.storage.sqlentity.Course( - oldCourse.getUniqueId(), - oldCourse.getName(), - oldCourse.getTimeZone(), - oldCourse.getInstitute()); - newCourse.setDeletedAt(oldCourse.getDeletedAt()); - newCourse.setCreatedAt(oldCourse.getCreatedAt()); + private void migrateStudents(String courseId) { + log(String.format("Migrating Students for course %s", courseId)); + HibernateUtil.beginTransaction(); + List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", courseId) + .list(); + + Course newCourse = getCourse(courseId); + + + // Get postgres Sections and the names of related teams + // Key: Section Name Value: Team Name + Map> sectionToTeamNameMap = new HashMap>(); + List
newSections = getSections(courseId); + for (Section newSection : newSections) { + String sectionName = newSection.getName(); + HashSet teamHashSet = new HashSet<>(); + for (Team newTeam : newSection.getTeams()) { + teamHashSet.add(newTeam.getName()); + } + sectionToTeamNameMap.put(sectionName, teamHashSet); + } + + // Get postgres team entities with their relations to the sections + // Key: Section Name Value: Team name - Team Entity Map + Map> newSectionToTeamEntityMap = new HashMap>(); + for (Entry> entry :sectionToTeamNameMap.entrySet()) { + String sectionName = entry.getKey(); + for (String teamName : entry.getValue()) { + Team newTeam = getTeam(courseId, sectionName, teamName); - saveEntityDeferred(newCourse); - return newCourse; + newSectionToTeamEntityMap.putIfAbsent(sectionName, new HashMap()); + newSectionToTeamEntityMap.get(sectionName).putIfAbsent(teamName, newTeam); + } + } + + Map googleIdToAccountMap = new HashMap(); + + for (CourseStudent oldStudent : oldStudents) { + String googleId = oldStudent.getGoogleId(); + Account associatedAccount = getAccount(googleId); + googleIdToAccountMap.put(googleId, associatedAccount); + } + + for (CourseStudent oldStudent : oldStudents) { + Team newTeam = newSectionToTeamEntityMap + .get(oldStudent.getSectionName()) + .get(oldStudent.getTeamName()); + + Student newStudent = createStudent(newCourse, newTeam, oldStudent); + Account associatedAccount = googleIdToAccountMap.get(oldStudent.getGoogleId()); + newStudent.setAccount(associatedAccount); + + HibernateUtil.persist(newStudent); + } + + + HibernateUtil.commitTransaction(); } - private teammates.storage.sqlentity.Section createSection(teammates.storage.sqlentity.Course newCourse, + private Section createSection(Course newCourse, String sectionName) { String truncatedName = truncateToLength255(sectionName); Section newSection = new Section(newCourse, truncatedName); @@ -225,14 +344,51 @@ private teammates.storage.sqlentity.Section createSection(teammates.storage.sqle return newSection; } - private teammates.storage.sqlentity.Team createTeam(teammates.storage.sqlentity.Section section, String teamName) { + private List
getSections(String courseId) { + CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + CriteriaQuery cr = cb + .createQuery(teammates.storage.sqlentity.Section.class); + Root sectionRoot = cr.from(teammates.storage.sqlentity.Section.class); + cr.select(sectionRoot).where(cb.equal(sectionRoot.get("course").get("id"), courseId)); + List
newSections = HibernateUtil.createQuery(cr).getResultList(); + return newSections; + } + + private Section getSection(String courseId, String sectionName) { + CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + CriteriaQuery
cr = cb.createQuery(Section.class); + Root
sectionRoot = cr.from(Section.class); + cr.where(cb.and(cb.equal(sectionRoot.get("course").get("id"), courseId), + cb.equal(sectionRoot.get("name"), sectionName))); + + return HibernateUtil.createQuery(cr).getSingleResult(); + } + + private Team getTeam(String courseId, String sectionName, String teamName) { + CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + CriteriaQuery cr = cb.createQuery(Team.class); + Root teamRoot = cr.from(Team.class); + cr.where(cb.and( + cb.equal(teamRoot.get("section").get("name"), sectionName), + cb.equal(teamRoot.get("section").get("course").get("id"), courseId), + cb.equal(teamRoot.get("name"), teamName) + )); + + return HibernateUtil.createQuery(cr).getSingleResult(); + } + + private Account getAccount(String googleId) { + return HibernateUtil.getBySimpleNaturalId(Account.class, SanitizationHelper.sanitizeGoogleId(googleId)); + } + + private teammates.storage.sqlentity.Team createTeam(Section section, String teamName) { String truncatedTeamName = truncateToLength255(teamName); Team newTeam = new teammates.storage.sqlentity.Team(section, truncatedTeamName); newTeam.setCreatedAt(Instant.now()); return newTeam; } - private Student migrateStudent(teammates.storage.sqlentity.Course newCourse, + private Student createStudent(Course newCourse, teammates.storage.sqlentity.Team newTeam, CourseStudent oldStudent) { String truncatedStudentName = truncateToLength255(oldStudent.getName()); @@ -241,10 +397,9 @@ private Student migrateStudent(teammates.storage.sqlentity.Course newCourse, Student newStudent = new Student(newCourse, truncatedStudentName, oldStudent.getEmail(), truncatedComments, newTeam); - newStudent.setUpdatedAt(oldStudent.getUpdatedAt()); + // newStudent.setUpdatedAt(oldStudent.getUpdatedAt()); newStudent.setRegKey(oldStudent.getRegistrationKey()); newStudent.setCreatedAt(oldStudent.getCreatedAt()); - saveEntityDeferred(newStudent); return newStudent; } @@ -252,60 +407,92 @@ private Student migrateStudent(teammates.storage.sqlentity.Course newCourse, // methods for migrate feedback chain --------------------------------------------------------------------------------- // entities: FeedbackSession, FeedbackQuestion, FeedbackResponse, FeedbackResponseComment - private Map migrateFeedbackChain( - teammates.storage.sqlentity.Course newCourse, - Map sectionNameToSectionMap) { + private void migrateFeedbackChain(String courseId) { log("Migrating feedback chain"); - Map feedbackSessionNameToFeedbackSessionMap = - new HashMap<>(); + // Map feedbackSessionNameToFeedbackSessionMap = + // new HashMap<>(); + + migrateFeedbackSessions(courseId); + // Map> sessionNameToQuestionsMap = ofy().load().type(FeedbackQuestion.class) + // .filter("courseId", newCourse.getId()).list().stream() + // .collect(Collectors.groupingBy(FeedbackQuestion::getFeedbackSessionName)); + + // for (FeedbackSession oldSession : oldSessions) { + // teammates.storage.sqlentity.FeedbackSession newSession = migrateFeedbackSession(newCourse, oldSession, + // sessionNameToQuestionsMap, sectionNameToSectionMap); + // feedbackSessionNameToFeedbackSessionMap.put(oldSession.getFeedbackSessionName(), newSession); + // } + // return feedbackSessionNameToFeedbackSessionMap; + } - List oldSessions = ofy().load().type(FeedbackSession.class) - .filter("courseId", newCourse.getId()).list(); + private void migrateFeedbackSessions(String courseId) { + HibernateUtil.beginTransaction(); + List oldFeedbackSessions = ofy().load().type(teammates.storage.entity.FeedbackSession.class) + .filter("courseId", courseId).list(); + + Course newCourse = getCourse(courseId); - Map> sessionNameToQuestionsMap = ofy().load().type(FeedbackQuestion.class) - .filter("courseId", newCourse.getId()).list().stream() + Map> feedbackSessionNameToQuestionsMap = ofy().load().type(FeedbackQuestion.class) + .filter("courseId", courseId) + .list().stream() .collect(Collectors.groupingBy(FeedbackQuestion::getFeedbackSessionName)); - for (FeedbackSession oldSession : oldSessions) { - teammates.storage.sqlentity.FeedbackSession newSession = migrateFeedbackSession(newCourse, oldSession, - sessionNameToQuestionsMap, sectionNameToSectionMap); - feedbackSessionNameToFeedbackSessionMap.put(oldSession.getFeedbackSessionName(), newSession); - } - return feedbackSessionNameToFeedbackSessionMap; - } - - private teammates.storage.sqlentity.FeedbackSession migrateFeedbackSession( - teammates.storage.sqlentity.Course newCourse, FeedbackSession oldSession, - Map> sessionNameToQuestionsMap, - Map sectionNameToSectionMap) { - teammates.storage.sqlentity.FeedbackSession newSession = createFeedbackSession(newCourse, oldSession); - saveEntityDeferred(newSession); - - Map> questionIdToResponsesMap; - Query responsesInSession = ofy().load().type(FeedbackResponse.class) - .filter("courseId", oldSession.getCourseId()) - .filter("feedbackSessionName", oldSession.getFeedbackSessionName()); - if (responsesInSession.count() <= MAX_RESPONSE_COUNT) { - questionIdToResponsesMap = responsesInSession.list().stream() - .collect(Collectors.groupingBy(FeedbackResponse::getFeedbackQuestionId)); - } else { - questionIdToResponsesMap = null; + Map sectionNameToSectionMap = new HashMap(); + List
newSections = getSections(courseId); + for (Section newSection : newSections) { + sectionNameToSectionMap.put(newSection.getName(), newSection); } + + for (teammates.storage.entity.FeedbackSession oldFeedbackSession : oldFeedbackSessions) { + FeedbackSession newFeedbackSession = createFeedbackSession(newCourse, oldFeedbackSession); + HibernateUtil.persist(newFeedbackSession); + + String oldFeedbackSessionName = oldFeedbackSession.getFeedbackSessionName(); + + // Query responsesInSession = ofy().load().type(FeedbackResponse.class) + // .filter("courseId", courseId) + // .filter("feedbackSessionName", oldFeedbackSessionName); + + List oldQuestions = feedbackSessionNameToQuestionsMap.get(oldFeedbackSessionName); + for (FeedbackQuestion oldQuestion : oldQuestions) { + migrateFeedbackQuestion(newFeedbackSession, oldQuestion, sectionNameToSectionMap); + } - // cascade migrate questions - List oldQuestions = sessionNameToQuestionsMap.get(oldSession.getFeedbackSessionName()); - for (FeedbackQuestion oldQuestion : oldQuestions) { - migrateFeedbackQuestion(newSession, oldQuestion, questionIdToResponsesMap, sectionNameToSectionMap); } - return newSession; + + HibernateUtil.commitTransaction(); } - private void migrateFeedbackQuestion(teammates.storage.sqlentity.FeedbackSession newSession, - FeedbackQuestion oldQuestion, Map> questionIdToResponsesMap, - Map sectionNameToSectionMap) { + // private teammates.storage.sqlentity.FeedbackSession migrateFeedbackSession( + // Course newCourse, FeedbackSession oldSession, + // Map> sessionNameToQuestionsMap, + // Map sectionNameToSectionMap) { + // teammates.storage.sqlentity.FeedbackSession newSession = createFeedbackSession(newCourse, oldSession); + // saveEntityDeferred(newSession); + + // Map> questionIdToResponsesMap; + // Query responsesInSession = ofy().load().type(FeedbackResponse.class) + // .filter("courseId", oldSession.getCourseId()) + // .filter("feedbackSessionName", oldSession.getFeedbackSessionName()); + // if (responsesInSession.count() <= MAX_RESPONSE_COUNT) { + // questionIdToResponsesMap = responsesInSession.list().stream() + // .collect(Collectors.groupingBy(FeedbackResponse::getFeedbackQuestionId)); + // } else { + // questionIdToResponsesMap = null; + // } + + // // cascade migrate questions + // List oldQuestions = sessionNameToQuestionsMap.get(oldSession.getFeedbackSessionName()); + // for (FeedbackQuestion oldQuestion : oldQuestions) { + // migrateFeedbackQuestion(newSession, oldQuestion, questionIdToResponsesMap, sectionNameToSectionMap); + // } + // return newSession; + // } + + private void migrateFeedbackQuestion(teammates.storage.sqlentity.FeedbackSession newSession, FeedbackQuestion oldQuestion, Map sectionNameToSectionMap) { teammates.storage.sqlentity.FeedbackQuestion newFeedbackQuestion = createFeedbackQuestion(newSession, oldQuestion); - saveEntityDeferred(newFeedbackQuestion); + HibernateUtil.persist(newFeedbackQuestion); Map> responseIdToCommentsMap = ofy().load() .type(FeedbackResponseComment.class) @@ -314,12 +501,9 @@ private void migrateFeedbackQuestion(teammates.storage.sqlentity.FeedbackSession // cascade migrate responses List oldResponses; - if (questionIdToResponsesMap != null) { - oldResponses = questionIdToResponsesMap.get(oldQuestion.getId()); - } else { - oldResponses = ofy().load().type(FeedbackResponse.class) - .filter("feedbackQuestionId", oldQuestion.getId()).list(); - } + oldResponses = ofy().load().type(FeedbackResponse.class) + .filter("feedbackQuestionId", oldQuestion.getId()).list(); + for (FeedbackResponse oldResponse : oldResponses) { Section newGiverSection = sectionNameToSectionMap.get(oldResponse.getGiverSection()); Section newRecipientSection = sectionNameToSectionMap.get(oldResponse.getRecipientSection()); @@ -333,7 +517,7 @@ private void migrateFeedbackResponse(teammates.storage.sqlentity.FeedbackQuestio Map> responseIdToCommentsMap) { teammates.storage.sqlentity.FeedbackResponse newResponse = createFeedbackResponse(newQuestion, oldResponse, newGiverSection, newRecipientSection); - saveEntityDeferred(newResponse); + HibernateUtil.persist(newResponse); // cascade migrate response comments List oldComments = responseIdToCommentsMap.get(oldResponse.getId()); @@ -346,11 +530,11 @@ private void migrateFeedbackResponseComment(teammates.storage.sqlentity.Feedback FeedbackResponseComment oldComment, Section newGiverSection, Section newRecipientSection) { teammates.storage.sqlentity.FeedbackResponseComment newComment = createFeedbackResponseComment(newResponse, oldComment, newGiverSection, newRecipientSection); - saveEntityDeferred(newComment); + HibernateUtil.persist(newComment); } - private teammates.storage.sqlentity.FeedbackSession createFeedbackSession(teammates.storage.sqlentity.Course newCourse, - FeedbackSession oldSession) { + private FeedbackSession createFeedbackSession(Course newCourse, + teammates.storage.entity.FeedbackSession oldSession) { String truncatedSessionInstructions = truncateToLength2000(oldSession.getInstructions()); teammates.storage.sqlentity.FeedbackSession newSession = new teammates.storage.sqlentity.FeedbackSession( @@ -373,7 +557,7 @@ private teammates.storage.sqlentity.FeedbackSession createFeedbackSession(teamma newSession.setOpeningSoonEmailSent(oldSession.isSentOpeningSoonEmail()); newSession.setPublishedEmailSent(oldSession.isSentPublishedEmail()); newSession.setCreatedAt(oldSession.getCreatedTime()); - newSession.setUpdatedAt(Instant.now()); // not present in datastore session + // newSession.setUpdatedAt(Instant.now()); // not present in datastore session newSession.setDeletedAt(oldSession.getDeletedTime()); return newSession; @@ -396,7 +580,6 @@ private teammates.storage.sqlentity.FeedbackQuestion createFeedbackQuestion( getFeedbackQuestionDetails(oldQuestion)); newFeedbackQuestion.setCreatedAt(oldQuestion.getCreatedAt()); - newFeedbackQuestion.setUpdatedAt(oldQuestion.getUpdatedAt()); return newFeedbackQuestion; } @@ -520,20 +703,28 @@ private teammates.storage.sqlentity.FeedbackResponseComment createFeedbackRespon // methods for misc migration methods --------------------------------------------------------------------------------- // entities: Instructor, DeadlineExtension - private void migrateInstructorEntities(teammates.storage.sqlentity.Course newCourse, - Map userGoogleIdToUserMap, Map emailToInstructorMap) { - List oldInstructors = ofy().load().type(teammates.storage.entity.Instructor.class).filter("courseId", newCourse.getId()) + private void migrateInstructorEntities(String courseId, + Map userGoogleIdToUserMap) { + HibernateUtil.beginTransaction(); + List oldInstructors = ofy().load() + .type(teammates.storage.entity.Instructor.class) + .filter("courseId", courseId) .list(); + + Course newCourse = getCourse(courseId); + for (teammates.storage.entity.Instructor oldInstructor : oldInstructors) { - Instructor newInstructor = migrateInstructor(newCourse, oldInstructor); - emailToInstructorMap.put(newInstructor.getEmail(), newInstructor); - if (oldInstructor.getGoogleId() != null) { - userGoogleIdToUserMap.put(oldInstructor.getGoogleId(), newInstructor); - } + Instructor newInstructor = createInstructor(newCourse, oldInstructor); + newInstructor.setAccount(getAccount(oldInstructor.getGoogleId())); + // if (oldInstructor.getGoogleId() != null) { + // userGoogleIdToUserMap.put(oldInstructor.getGoogleId(), newInstructor); + // } + HibernateUtil.persist(newInstructor); } + HibernateUtil.commitTransaction(); } - private Instructor migrateInstructor(teammates.storage.sqlentity.Course newCourse, + private Instructor createInstructor(Course newCourse, teammates.storage.entity.Instructor oldInstructor) { InstructorPrivileges newPrivileges; if (oldInstructor.getInstructorPrivilegesAsText() == null) { @@ -559,85 +750,113 @@ private Instructor migrateInstructor(teammates.storage.sqlentity.Course newCours newInstructor.setCreatedAt(oldInstructor.getCreatedAt()); newInstructor.setUpdatedAt(oldInstructor.getUpdatedAt()); newInstructor.setRegKey(oldInstructor.getRegistrationKey()); - saveEntityDeferred(newInstructor); - return newInstructor; } - private void migrateDeadlineExtensionEntities(teammates.storage.sqlentity.Course newCourse, - Map feedbackSessionNameToFeedbackSessionMap, - Map emailToInstructorMap, Map emailToStudentMap) { + private void migrateDeadlineExtensionEntities(String courseId) { + HibernateUtil.beginTransaction(); log("Migrating deadline extension"); - List oldDeadlineExtensions = ofy().load().type(DeadlineExtension.class) - .filter("courseId", newCourse.getId()) + Map emailToInstructorMap = new HashMap<>(); + Map emailToStudentMap = new HashMap<>(); + + List oldDeadlineExtensions = ofy().load() + .type(teammates.storage.entity.DeadlineExtension .class) + .filter("courseId", courseId) .list(); - for (DeadlineExtension oldDeadlineExtension : oldDeadlineExtensions) { + Map feedbackSessionNameToFeedbackSessionMap = + new HashMap(); + + for (FeedbackSession newFeedbackSession : getCourse(courseId).getFeedbackSessions()) { + feedbackSessionNameToFeedbackSessionMap.put(newFeedbackSession.getName(), newFeedbackSession); + } + + for (teammates.storage.entity.DeadlineExtension oldDeadlineExtension : oldDeadlineExtensions) { String userEmail = oldDeadlineExtension.getUserEmail(); String feedbackSessionName = oldDeadlineExtension.getFeedbackSessionName(); teammates.storage.sqlentity.FeedbackSession feedbackSession = feedbackSessionNameToFeedbackSessionMap.get(feedbackSessionName); - User user; + User user = null; + log(userEmail); if (oldDeadlineExtension.getIsInstructor()) { - user = emailToInstructorMap.get(userEmail); + if (emailToInstructorMap.containsKey(userEmail)) { + user = emailToInstructorMap.get(userEmail); + } else { + Instructor instructor = getNewInstructor(courseId, userEmail); + emailToInstructorMap.put(userEmail, instructor); + user = instructor; + } + if (user == null) { logError("Instructor not found for deadline extension: " + oldDeadlineExtension); continue; } } else { - user = emailToStudentMap.get(userEmail); + if (emailToStudentMap.containsKey(userEmail)) { + user = emailToStudentMap.get(userEmail); + } else { + Student student = getNewStudent(courseId, userEmail); + emailToStudentMap.put(userEmail, student); + user = student; + } if (user == null) { logError("Student not found for deadline extension: " + oldDeadlineExtension); continue; } } - migrateDeadlineExtension(oldDeadlineExtension, feedbackSession, user); + DeadlineExtension newDeadlineExtension = createDeadlineExtension(oldDeadlineExtension, feedbackSession, user); + HibernateUtil.persist(newDeadlineExtension); } + HibernateUtil.commitTransaction(); } - private void migrateDeadlineExtension(DeadlineExtension oldDeadlineExtension, + private DeadlineExtension createDeadlineExtension(teammates.storage.entity.DeadlineExtension oldDeadlineExtension, teammates.storage.sqlentity.FeedbackSession feedbackSession, User newUser) { - teammates.storage.sqlentity.DeadlineExtension newDeadlineExtension = + DeadlineExtension newDeadlineExtension = new teammates.storage.sqlentity.DeadlineExtension( newUser, feedbackSession, oldDeadlineExtension.getEndTime()); - + newDeadlineExtension.setCreatedAt(oldDeadlineExtension.getCreatedAt()); newDeadlineExtension.setUpdatedAt(oldDeadlineExtension.getUpdatedAt()); newDeadlineExtension.setClosingSoonEmailSent(oldDeadlineExtension.getSentClosingEmail()); - saveEntityDeferred(newDeadlineExtension); + return newDeadlineExtension; } // Associate account to users(students and instructors) who have the same matching google id - private void migrateUserAccounts(teammates.storage.sqlentity.Course newCourse, Map userGoogleIdToUserMap) { - List newAccounts = getAllAccounts(new ArrayList(userGoogleIdToUserMap.keySet())); - if (newAccounts.size() != userGoogleIdToUserMap.size()) { - log("Mismatch in number of accounts: " + newAccounts.size() + " vs " + userGoogleIdToUserMap.size()); - } - for (Account account: newAccounts) { - User newUser = userGoogleIdToUserMap.get(account.getGoogleId()); - if (newUser == null) { - log("User not found for account: " + account.getGoogleId()); - continue; - } - newUser.setGoogleId(account.getGoogleId()); - newUser.setAccount(account); - saveEntityDeferred(newUser); - } - } - - private List getAllAccounts(List userGoogleIds) { - HibernateUtil.beginTransaction(); - CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); - CriteriaQuery cr = cb.createQuery(Account.class); - Root accountRoot = cr.from(Account.class); - cr.select(accountRoot).where(cb.in(accountRoot.get("googleId")).value(userGoogleIds)); - List newAccounts = HibernateUtil.createQuery(cr).getResultList(); - HibernateUtil.commitTransaction(); - return newAccounts; + // private void migrateUserAccounts(Course newCourse, Map userGoogleIdToUserMap) { + // List newAccounts = getAllAccounts(new ArrayList(userGoogleIdToUserMap.keySet())); + // if (newAccounts.size() != userGoogleIdToUserMap.size()) { + // log("Mismatch in number of accounts: " + newAccounts.size() + " vs " + userGoogleIdToUserMap.size()); + // } + // for (Account account: newAccounts) { + // User newUser = userGoogleIdToUserMap.get(account.getGoogleId()); + // if (newUser == null) { + // log("User not found for account: " + account.getGoogleId()); + // continue; + // } + // newUser.setGoogleId(account.getGoogleId()); + // newUser.setAccount(account); + // saveEntityDeferred(newUser); + // } + // } + + // private List getAllAccounts(List userGoogleIds) { + // HibernateUtil.beginTransaction(); + // CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + // CriteriaQuery cr = cb.createQuery(Account.class); + // Root accountRoot = cr.from(Account.class); + // cr.select(accountRoot).where(cb.in(accountRoot.get("googleId")).value(userGoogleIds)); + // List newAccounts = HibernateUtil.createQuery(cr).getResultList(); + // HibernateUtil.commitTransaction(); + // return newAccounts; + // } + + private Course getCourse(String courseId) { + return HibernateUtil.get(Course.class, courseId); } @Override @@ -655,33 +874,44 @@ protected void doOperation() { boolean shouldContinue = true; while (shouldContinue) { shouldContinue = false; - Query filterQueryKeys = getFilterQuery().limit(BATCH_SIZE); + Query filterQueryKeys = getFilterQuery().limit(BATCH_SIZE); if (cursor != null) { filterQueryKeys = filterQueryKeys.startAt(cursor); } QueryResults iterator = filterQueryKeys.iterator(); - Course currentOldCourse = null; + teammates.storage.entity.Course currentOldCourse = null; // Cascade delete the course if it is not fully migrated. if (iterator.hasNext()) { - currentOldCourse = (Course) iterator.next(); + currentOldCourse = (teammates.storage.entity.Course) iterator.next(); if (currentOldCourse.isMigrated()) { - currentOldCourse = (Course) iterator.next(); + currentOldCourse = (teammates.storage.entity.Course) iterator.next(); } else { deleteCourseCascade(currentOldCourse); } } + while (currentOldCourse != null) { shouldContinue = true; - doMigration(currentOldCourse); + try { + doMigration(currentOldCourse); + } catch (Exception e) { + numberOfScannedKey.incrementAndGet(); + logError(e.getMessage()); + log("Total number of course entities scanned: " + numberOfScannedKey.get()); + log("Number of affected course entities: " + numberOfAffectedEntities.get()); + log("Number of updated course entities: " + numberOfUpdatedEntities.get()); + e.printStackTrace(); + return; + } numberOfScannedKey.incrementAndGet(); cursor = iterator.getCursorAfter(); savePositionOfCursorToFile(cursor); - currentOldCourse = iterator.hasNext() ? (Course) iterator.next() : null; + currentOldCourse = iterator.hasNext() ? (teammates.storage.entity.Course) iterator.next() : null; } if (shouldContinue) { @@ -702,11 +932,11 @@ protected void doOperation() { /** * Deletes the course and its related entities from sql database. */ - private void deleteCourseCascade(Course oldCourse) { + private void deleteCourseCascade(teammates.storage.entity.Course oldCourse) { String courseId = oldCourse.getUniqueId(); HibernateUtil.beginTransaction(); - teammates.storage.sqlentity.Course newCourse = HibernateUtil.get(teammates.storage.sqlentity.Course.class, courseId); + Course newCourse = HibernateUtil.get(Course.class, courseId); if (newCourse == null) { HibernateUtil.commitTransaction(); return; @@ -755,17 +985,11 @@ private void deleteCursorPositionFile() { /** * Migrates the entity and counts the statistics. */ - private void doMigration(Course entity) { - try { - numberOfAffectedEntities.incrementAndGet(); - if (!isPreview()) { - migrateCourse(entity); - numberOfUpdatedEntities.incrementAndGet(); - } - } catch (Exception e) { - logError("Problem migrating entity " + entity); - e.printStackTrace(); - logError(e.getMessage()); + private void doMigration(teammates.storage.entity.Course entity) throws Exception { + numberOfAffectedEntities.incrementAndGet(); + if (!isPreview()) { + migrateCourse(entity); + numberOfUpdatedEntities.incrementAndGet(); } } @@ -826,31 +1050,39 @@ protected String truncateToLength2000(String str) { * Logs a comment. */ protected void log(String logLine) { - System.out.println(String.format("%s %s", getLogPrefix(), logLine)); - - Path logPath = Paths.get(BASE_LOG_URI + this.getClass().getSimpleName() + ".log"); - try (OutputStream logFile = Files.newOutputStream(logPath, - StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { - logFile.write((logLine + System.lineSeparator()).getBytes(Const.ENCODING)); - } catch (Exception e) { - System.err.println("Error writing log line: " + logLine); - System.err.println(e.getMessage()); - } - } - - /** - * Returns the log prefix. - */ - protected String getLogPrefix() { - return String.format("Migrating Course chains:"); + logger.log(logLine); } /** * Logs an error and persists it to the disk. */ protected void logError(String logLine) { - System.err.println(logLine); + logger.log("[ERROR]" + logLine); + } + + private Student getNewStudent(String courseId, String email) { + CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + CriteriaQuery cr = cb + .createQuery(Student.class); + Root studentRoot = cr.from(Student.class); + cr.select(studentRoot).where(cb.and( + cb.equal(studentRoot.get("courseId"), courseId), + cb.equal(studentRoot.get("email"), email) + )); + Student newStudent = HibernateUtil.createQuery(cr).getSingleResult(); + return newStudent; + } - log("[ERROR]" + logLine); + private Instructor getNewInstructor(String courseId, String email) { + CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + CriteriaQuery cr = cb + .createQuery(Instructor.class); + Root instructorRoot = cr.from(Instructor.class); + cr.select(instructorRoot).where(cb.and( + cb.equal(instructorRoot.get("courseId"), courseId), + cb.equal(instructorRoot.get("email"), email) + )); + Instructor newInstructor = HibernateUtil.createQuery(cr).getSingleResult(); + return newInstructor; } } diff --git a/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseSql.java b/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseSql.java deleted file mode 100644 index dc0cee4724a..00000000000 --- a/src/client/java/teammates/client/scripts/sql/DataMigrationForCourseSql.java +++ /dev/null @@ -1,57 +0,0 @@ -package teammates.client.scripts.sql; - -import com.googlecode.objectify.cmd.Query; - -import teammates.storage.entity.Course; - -/** - * Data migration class for course entity. - */ -@SuppressWarnings("PMD") -public class DataMigrationForCourseSql extends - DataMigrationEntitiesBaseScriptSql { - - public static void main(String[] args) { - new DataMigrationForCourseSql().doOperationRemotely(); - } - - @Override - protected Query getFilterQuery() { - return ofy().load().type(teammates.storage.entity.Course.class); - } - - @Override - protected boolean isPreview() { - return false; - } - - /* - * Sets the migration criteria used in isMigrationNeeded. - */ - @Override - protected void setMigrationCriteria() { - // No migration criteria currently needed. - } - - @Override - protected boolean isMigrationNeeded(Course entity) { - // HibernateUtil.beginTransaction(); - // teammates.storage.sqlentity.Course course = HibernateUtil.get( - // teammates.storage.sqlentity.Course.class, entity.getUniqueId()); - // HibernateUtil.commitTransaction(); - // return course == null; - return true; - } - - @Override - protected void migrateEntity(Course oldCourse) throws Exception { - teammates.storage.sqlentity.Course newCourse = new teammates.storage.sqlentity.Course( - oldCourse.getUniqueId(), - oldCourse.getName(), - oldCourse.getTimeZone(), - oldCourse.getInstitute()); - // newCourse.setCreatedAt(oldCourse.getCreatedAt()); - newCourse.setDeletedAt(oldCourse.getDeletedAt()); - saveEntityDeferred(newCourse); - } -} diff --git a/src/client/java/teammates/client/scripts/sql/Logger.java b/src/client/java/teammates/client/scripts/sql/Logger.java new file mode 100644 index 00000000000..f5b6635ea92 --- /dev/null +++ b/src/client/java/teammates/client/scripts/sql/Logger.java @@ -0,0 +1,50 @@ +package teammates.client.scripts.sql; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import teammates.common.util.Const; + +public class Logger { + private static final String BASE_LOG_URI = "src/client/java/teammates/client/scripts/log/"; + String logPrefix; + + protected Logger(String logPrefix) { + this.logPrefix = logPrefix; + } + + /** + * Returns the prefix for the log line. + */ + private String getLogPrefix() { + return String.format("%s", logPrefix); + } + + /** + * Logs a line and persists it to the disk. + */ + public void log(String logLine) { + System.out.println(String.format("%s %s", getLogPrefix(), logLine)); + + Path logPath = Paths.get(BASE_LOG_URI + this.getClass().getSimpleName() + ".log"); + try (OutputStream logFile = Files.newOutputStream(logPath, + StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + logFile.write((logLine + System.lineSeparator()).getBytes(Const.ENCODING)); + } catch (Exception e) { + System.err.println("Error writing log line: " + logLine); + System.err.println(e.getMessage()); + } + } + + /** + * Logs an error and persists it to the disk. + */ + public void logError(String logLine) { + System.err.println(logLine); + + log("[ERROR]" + logLine); + } +} diff --git a/src/client/java/teammates/client/scripts/sql/SeedDb.java b/src/client/java/teammates/client/scripts/sql/SeedDb.java index a989bc98bed..3d892fcf99d 100644 --- a/src/client/java/teammates/client/scripts/sql/SeedDb.java +++ b/src/client/java/teammates/client/scripts/sql/SeedDb.java @@ -52,21 +52,25 @@ @SuppressWarnings("PMD") public class SeedDb extends DatastoreClient { private static final int MAX_FLUSH_SIZE = 200; - private static final int MAX_ENTITY_SIZE = 10; - private static final int MAX_STUDENT_PER_COURSE = 100; + private static final int MAX_ENTITY_SIZE = 100; + private static final int MAX_ACCOUNT_REQUESTS = 0; + private static final int MAX_NUM_COURSES = 5; + private static final int MAX_STUDENT_PER_COURSE = 1000; private static final int MAX_TEAM_PER_SECTION = 10; private static final int MAX_SECTION_PER_COURSE = 10; - private static final int MAX_FEEDBACKSESSION_FOR_EACH_COURSE_SIZE = 3; - private static final int MAX_QUESTION_PER_COURSE = 6; - private static final int MAX_RESPONSES_PER_QUESTION = 10; - private static final int MAX_COMMENTS_PER_RESPONSE = 2; - private static final int NOTIFICATION_SIZE = 1000; - private static final int READ_NOTIFICATION_SIZE = 5; + private static final int MAX_FEEDBACK_SESSION_FOR_EACH_COURSE_SIZE = 5; + private static final int MAX_QUESTION_PER_COURSE = 5; + private static final int MAX_RESPONSES_PER_QUESTION = 5; + private static final int MAX_COMMENTS_PER_RESPONSE = 1; + private static final int NOTIFICATION_SIZE = 0; + private static final int READ_NOTIFICATION_SIZE = 0; private static final double PERCENTAGE_STUDENTS_WITH_ACCOUNT = 0.1; private static final int MAX_INSTRUCTOR_PER_COURSE = 3; private static final double PERCENTAGE_INSTRUCTORS_WITH_ACCOUNT = 0.5; private static final int DEADLINE_EXTENSION_FREQ = 5; + // chunks to divide entities. Used to determine how many entities are processed before logging + private static final int logStep = 5; private Random rand = new Random(); private final LogicExtension logic = new LogicExtension(); private Closeable closeable; @@ -138,19 +142,19 @@ protected Instant getRandomInstant() { protected void persistAdditionalData() { // Each account will have this amount of read notifications assert NOTIFICATION_SIZE >= READ_NOTIFICATION_SIZE; - log("Seeding Notifications, Account and Account Request"); + log("Seeding Notifications and Account Request"); seedNotifications(notificationUuids, notificationsUuidSeen, notificationEndTimes); seedAccountRequests(); log("Seeding courses"); - for (int i = 0; i < MAX_ENTITY_SIZE; i++) { - if (i % (MAX_ENTITY_SIZE / 5) == 0) { + for (int i = 0; i < MAX_NUM_COURSES; i++) { + if (MAX_NUM_COURSES >= logStep && i % (MAX_NUM_COURSES / logStep) == 0) { log(String.format("Seeded %d %% of new sets of entities", - (int) (100 * ((float) i / (float) MAX_ENTITY_SIZE)))); + (int) (100 * ((float) i / (float) MAX_NUM_COURSES)))); } - String courseId = String.format("Course ID %s", i); + String courseId = String.format("Course-ID-%s", i); try { seedCourseWithCourseId(i, courseId); seedStudents(i, courseId); @@ -179,7 +183,6 @@ protected void flushEntityBuffer(List buffe } private void seedCourseWithCourseId(int i, String courseId) { - Random rand = new Random(); String courseName = String.format("Course %s", i); String courseInstitute = String.format("Institute %s", i); String courseTimeZone = String.format("Time Zone %s", i); @@ -250,7 +253,7 @@ private void seedFeedbackSession(int courseNumber, String courseId) { log("Seeding feedback chain for course " + courseNumber); List buffer = new ArrayList<>(); - for (int i = 0; i < MAX_FEEDBACKSESSION_FOR_EACH_COURSE_SIZE; i++) { + for (int i = 0; i < MAX_FEEDBACK_SESSION_FOR_EACH_COURSE_SIZE; i++) { try { String feedbackSessionName = String.format("Course %s Feedback Session %s", courseNumber, i); String feedbackSessionCreatorEmail = String.format("Creator Email %s", i); @@ -281,13 +284,13 @@ feedbackSessionCreatorEmail, feedbackSessionInstructions, getRandomInstant(), } private void seedFeedbackQuestions(int courseNumber, String courseId) { - assert MAX_FEEDBACKSESSION_FOR_EACH_COURSE_SIZE <= MAX_QUESTION_PER_COURSE; + assert MAX_FEEDBACK_SESSION_FOR_EACH_COURSE_SIZE <= MAX_QUESTION_PER_COURSE; int currSession = -1; List listOfCreatedFeedbackQuestions = new ArrayList<>(); for (int i = 0; i < MAX_QUESTION_PER_COURSE; i++) { try { - if (i % (MAX_QUESTION_PER_COURSE / MAX_FEEDBACKSESSION_FOR_EACH_COURSE_SIZE) == 0) { + if (i % (MAX_QUESTION_PER_COURSE / MAX_FEEDBACK_SESSION_FOR_EACH_COURSE_SIZE) == 0) { currSession++; } @@ -321,7 +324,8 @@ private void seedFeedbackQuestions(int courseNumber, String courseId) { log("Feedback questions " + e.toString()); } } - flushEntityBuffer(listOfCreatedFeedbackQuestions); + + ofy().save().entities(listOfCreatedFeedbackQuestions).now(); List listOfCreatedFeedbackResponses = new ArrayList<>(); for (teammates.storage.entity.BaseEntity fq : listOfCreatedFeedbackQuestions) { @@ -329,31 +333,31 @@ private void seedFeedbackQuestions(int courseNumber, String courseId) { String feedbackQuestionId = feedbackQuestion.getId(); FeedbackQuestionType feedbackQuestionType = feedbackQuestion.getQuestionType(); assert feedbackQuestionId != null; - List feedbackResponses = createFeedbackResponses(courseNumber, courseId, feedbackQuestionId, feedbackQuestionType); + List feedbackResponses = createFeedbackResponses(courseNumber, courseId, feedbackQuestionId, feedbackQuestionType); listOfCreatedFeedbackResponses.addAll(feedbackResponses); } - flushEntityBuffer(listOfCreatedFeedbackResponses); + ofy().save().entities(listOfCreatedFeedbackResponses).now(); - List listOfCreatedFeedbackComments = new ArrayList<>(); + List listOfCreatedFeedbackComments = new ArrayList<>(); for (teammates.storage.entity.BaseEntity fr : listOfCreatedFeedbackResponses) { FeedbackResponse feedbackResponse = (FeedbackResponse) fr; String giverSection = feedbackResponse.getGiverSection(); String recipientSection = feedbackResponse.getRecipientSection(); String feedbackResponseId = feedbackResponse.getId(); - List feedbackComments = createFeedbackResponseComments(courseNumber, courseId, feedbackResponse.getFeedbackQuestionId(), feedbackResponseId, giverSection, + List feedbackComments = createFeedbackResponseComments(courseNumber, courseId, feedbackResponse.getFeedbackQuestionId(), feedbackResponseId, giverSection, recipientSection); listOfCreatedFeedbackComments.addAll(feedbackComments); } - flushEntityBuffer(listOfCreatedFeedbackComments); - + + ofy().save().entities(listOfCreatedFeedbackComments).now(); } - private List createFeedbackResponses(int courseNumber, String courseId, String feedbackQuestionId, + private List createFeedbackResponses(int courseNumber, String courseId, String feedbackQuestionId, FeedbackQuestionType feedbackQuestionType){ int currGiverSection = -1; int currRecipientSection = 0; - List listOfCreatedFeedbackResponses = new ArrayList<>(); + List listOfCreatedFeedbackResponses = new ArrayList<>(); for (int i = 0; i < MAX_RESPONSES_PER_QUESTION; i++) { try { currGiverSection = (currGiverSection + 1) % MAX_SECTION_PER_COURSE; @@ -367,10 +371,9 @@ private List createFeedbackResponses(int co String answer = new FeedbackTextResponseDetails( String.format("Response %s for Question Id: %s", i, feedbackQuestionId)).getJsonString(); - FeedbackResponse feedbackResponse = new FeedbackResponse(feedbackSessionName, courseId, feedbackQuestionId, + teammates.storage.entity.FeedbackResponse feedbackResponse = new FeedbackResponse(feedbackSessionName, courseId, feedbackQuestionId, feedbackQuestionType, giverEmail, giverSection, recipient, recipientSection, answer); feedbackResponse.setCreatedAt(getRandomInstant()); - feedbackResponse.setLastUpdate(getRandomInstant()); listOfCreatedFeedbackResponses.add(feedbackResponse); @@ -381,12 +384,12 @@ private List createFeedbackResponses(int co return listOfCreatedFeedbackResponses; } - private List createFeedbackResponseComments(int courseNumber, String courseId, String feedbackQuestionId, String feedbackResponseId, + private List createFeedbackResponseComments(int courseNumber, String courseId, String feedbackQuestionId, String feedbackResponseId, String giverSection, String receiverSection) { int currGiverSection = -1; int currRecipientSection = 0; - List listOfCreatedFeedbackComments = new ArrayList<>(); + List listOfCreatedFeedbackComments = new ArrayList<>(); for (int i = 0; i < MAX_COMMENTS_PER_RESPONSE; i++) { try { currGiverSection = (currGiverSection + 1) % MAX_SECTION_PER_COURSE; @@ -452,10 +455,10 @@ private void seedNotifications(ArrayList notificationUuids, private void seedAccountRequests() { List buffer = new ArrayList<>(); - for (int i = 0; i < MAX_ENTITY_SIZE; i++) { - if (i % (MAX_ENTITY_SIZE / 5) == 0) { + for (int i = 0; i < MAX_ACCOUNT_REQUESTS; i++) { + if (MAX_ACCOUNT_REQUESTS >= logStep && i % (MAX_ACCOUNT_REQUESTS / logStep) == 0) { log(String.format("Seeded %d %% of account requests", - (int) (100 * ((float) i / (float) MAX_ENTITY_SIZE)))); + (int) (100 * ((float) i / (float) MAX_ACCOUNT_REQUESTS)))); } try { String accountRequestName = String.format("Account Request %s", i); @@ -493,7 +496,7 @@ private void seedInstructors(int courseNumber, String courseId) { log("Seeding instructors for course " + courseNumber); for (int i = 0; i < MAX_INSTRUCTOR_PER_COURSE; i++) { String instructorGoogleId = null; - String instructorName = String.format("Course %s Instructor &s", courseNumber, i); + String instructorName = String.format("Course %s Instructor %s", courseNumber, i); String instructorEmail = String.format("Course %s Instructor %s Email", courseNumber, i); String role = Const.InstructorPermissionRoleNames.INSTRUCTOR_PERMISSION_ROLE_COOWNER; String displayedName = String.format("Display Name %s", i); @@ -502,7 +505,7 @@ private void seedInstructors(int courseNumber, String courseId) { if (rand.nextDouble() <= PERCENTAGE_INSTRUCTORS_WITH_ACCOUNT) { int googleIdNumber = courseNumber * MAX_INSTRUCTOR_PER_COURSE + i - + MAX_ENTITY_SIZE * MAX_STUDENT_PER_COURSE; // to avoid clashes with students + + MAX_NUM_COURSES * MAX_STUDENT_PER_COURSE; // to avoid clashes with students instructorGoogleId = String.format("Account Google ID %s", googleIdNumber); Account account = createAccount(instructorGoogleId, instructorName, instructorEmail); ofy().save().entities(account).now(); @@ -526,7 +529,7 @@ private void seedDeadlineExtensions(int courseNumber, String courseId, String us Random rand = new Random(); int i = isInstructor ? 1 : 0; // matches if session's questions' giverType are students or instructors - for (; i < MAX_FEEDBACKSESSION_FOR_EACH_COURSE_SIZE; i+=2) { + for (; i < MAX_FEEDBACK_SESSION_FOR_EACH_COURSE_SIZE; i+=2) { String feedbackSessionName = String.format("Course %s Feedback Session %s", courseNumber, i); boolean sentClosingEmail = rand.nextBoolean(); Instant endTime = getRandomInstant(); @@ -572,3 +575,4 @@ protected void doOperation() { } } } + diff --git a/src/client/java/teammates/client/scripts/sql/VerifyCourseEntityAttributes.java b/src/client/java/teammates/client/scripts/sql/VerifyCourseEntityAttributes.java index 21c8a4cb815..32701f13681 100644 --- a/src/client/java/teammates/client/scripts/sql/VerifyCourseEntityAttributes.java +++ b/src/client/java/teammates/client/scripts/sql/VerifyCourseEntityAttributes.java @@ -4,20 +4,22 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.HashMap; +import java.util.HashSet; import teammates.common.datatransfer.InstructorPrivileges; import teammates.common.datatransfer.InstructorPrivilegesLegacy; import teammates.common.util.HibernateUtil; import teammates.common.util.JsonUtils; import teammates.common.util.SanitizationHelper; -import teammates.storage.entity.Course; import teammates.storage.entity.CourseStudent; import teammates.storage.entity.FeedbackQuestion; import teammates.storage.entity.FeedbackResponse; import teammates.storage.entity.FeedbackResponseComment; -import teammates.storage.entity.FeedbackSession; import teammates.storage.entity.DeadlineExtension; import teammates.storage.entity.Instructor; +import teammates.storage.sqlentity.Course; +import teammates.storage.sqlentity.FeedbackSession; import teammates.storage.sqlentity.Section; import teammates.storage.sqlentity.Student; import teammates.storage.sqlentity.Team; @@ -31,15 +33,15 @@ */ @SuppressWarnings({ "PMD", "deprecation" }) public class VerifyCourseEntityAttributes - extends VerifyNonCourseEntityAttributesBaseScript { + extends VerifyNonCourseEntityAttributesBaseScript { public VerifyCourseEntityAttributes() { - super(Course.class, - teammates.storage.sqlentity.Course.class); + super(teammates.storage.entity.Course.class, + Course.class); } @Override - protected String generateID(teammates.storage.sqlentity.Course sqlEntity) { + protected String generateID(Course sqlEntity) { return sqlEntity.getId(); } @@ -50,47 +52,84 @@ public static void main(String[] args) { // Used for sql data migration @Override - public boolean equals(teammates.storage.sqlentity.Course newCourse, Course oldCourse) { + public boolean equals(Course newCourse, teammates.storage.entity.Course oldCourse) { + // Refetch course to ensure that the information is upto date + String courseId = newCourse.getId(); try { - HibernateUtil.beginTransaction(); - newCourse = HibernateUtil.get(teammates.storage.sqlentity.Course.class, newCourse.getId()); boolean isEqual = true; - if (!verifyCourse(newCourse, oldCourse)) { - log("Failed course verification"); - isEqual = false; - } + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifyCourse(courseId, oldCourse); + HibernateUtil.commitTransaction(); + + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifySections(courseId); + HibernateUtil.commitTransaction(); - if (!verifySectionChain(newCourse)) { - log("Failed section chain verification"); - isEqual = false; - } + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifyTeams(courseId); + HibernateUtil.commitTransaction(); - if (!verifyFeedbackChain(newCourse)) { - log("Failed feedback chain verification"); - isEqual = false; - } + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifyStudents(courseId); + HibernateUtil.commitTransaction(); - if (!verifyInstructors(newCourse)) { - log("Failed instructor verification"); - isEqual = false; - } + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifyFeedbackChain(courseId); + HibernateUtil.commitTransaction(); - if (!verifyDeadlineExtensions(newCourse)) { - log("Failed deadline extension verification"); - isEqual = false; - } + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifyInstructors(newCourse); + HibernateUtil.commitTransaction(); + HibernateUtil.beginTransaction(); + isEqual = isEqual && verifyDeadlineExtensions(newCourse); HibernateUtil.commitTransaction(); + + // if (!verifySectionChain(newCourse)) { + // logValidationError("Failed section chain verification"); + // isEqual = false; + // } + + // if (!verifyFeedbackChain(newCourse)) { + // logValidationError("Failed feedback chain verification"); + // isEqual = false; + // } + + // if (!verifyInstructors(newCourse)) { + // logValidationError("Failed instructor verification"); + // isEqual = false; + // } + + // if (!verifyDeadlineExtensions(newCourse)) { + // logValidationError("Failed deadline extension verification"); + // isEqual = false; + // } + return isEqual; } catch (IllegalArgumentException iae) { iae.printStackTrace(); - log("ERROR, IllegalArgumentException " + iae.getMessage()); + logValidationError("ERROR, IllegalArgumentException " + iae.getMessage()); HibernateUtil.commitTransaction(); return false; } } - private boolean verifyCourse(teammates.storage.sqlentity.Course sqlEntity, Course datastoreEntity) { + private Course getCourse(String courseId) { + return HibernateUtil.get(Course.class, courseId); + } + + private boolean verifyCourse(String courseId, teammates.storage.entity.Course oldCourse) { + log("Verifying Course attributes"); + Course newCourse = getCourse(courseId); + boolean isEqual = true; + if (!verifyCourseEntityAttributes(newCourse, oldCourse)) { + logValidationError("Failed course verification"); + isEqual = false; + } + return isEqual; + } + + private boolean verifyCourseEntityAttributes(Course sqlEntity, teammates.storage.entity.Course datastoreEntity) { return sqlEntity.getId().equals(datastoreEntity.getUniqueId()) && sqlEntity.getName().equals(datastoreEntity.getName()) && sqlEntity.getTimeZone().equals(datastoreEntity.getTimeZone()) @@ -103,140 +142,289 @@ private boolean verifyCourse(teammates.storage.sqlentity.Course sqlEntity, Cours // methods for verify section chain ----------------------------------------------------------------------------------- // entities: Section, Team, Student - private boolean verifySectionChain(teammates.storage.sqlentity.Course newCourse) { - // Get old and new students - List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", newCourse.getId()) + private boolean verifySections(String courseId) { + log("Verifying sections"); + // Get datastore sections + List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", courseId) .list(); - List newStudents = getNewStudents(newCourse.getId()); - - // Group students by section - Map> sectionToOldStuMap = oldStudents.stream() - .collect(Collectors.groupingBy(CourseStudent::getSectionName)); - Map> sectionToNewStuMap = newStudents.stream() - .collect(Collectors.groupingBy(Student::getSectionName)); - - List
newSections = getNewSections(newCourse.getId()); - - boolean isNotSectionsCountEqual = newSections.size() != sectionToOldStuMap.size() - || newSections.size() != sectionToNewStuMap.size(); - if (isNotSectionsCountEqual) { - log(String.format("newSection size: %d, sectionToOldStuMap: %d, sectionToOldStuMap: %d", newSections.size(), - sectionToOldStuMap.size(), sectionToNewStuMap.size())); - log("Section chain - section count not equal"); - return false; - } - return newSections.stream().allMatch(section -> { - List oldSectionStudents = sectionToOldStuMap.get(section.getName()); - List newSectionStudents = sectionToNewStuMap.get(section.getName()); + List
newSections = getNewSections(courseId); - // If either of the sectionStudent is null, - // then section is not present in the corresponding datastore or sql - // which means a possible migration error - boolean sectionNameNotPresent = oldSectionStudents == null || newSectionStudents == null; - if (sectionNameNotPresent) { - log("Section chain - section name not present"); - return false; - } - - // Group students by team - Map> teamNameToOldStuMap = oldSectionStudents.stream() - .collect(Collectors.groupingBy(CourseStudent::getTeamName)); - Map> teamNameToNewStuMap = newSectionStudents.stream() - .collect(Collectors.groupingBy(Student::getTeamName)); - return verifyTeams(section, teamNameToOldStuMap, teamNameToNewStuMap); - }); + HashSet oldSectionNames = new HashSet(); + HashSet newSectionNames = new HashSet(); - } + for (CourseStudent oldStudent : oldStudents) { + oldSectionNames.add(oldStudent.getSectionName()); + } - private boolean verifyTeams(Section newSection, - Map> teamNameToOldStuMap, Map> teamNameToNewStuMap) { + for (Section newSection : newSections) { + newSectionNames.add(newSection.getName());; + } - List newTeams = newSection.getTeams(); + boolean isSectionsCountEqual = newSectionNames.size() == oldSectionNames.size(); + if (!isSectionsCountEqual) { + logValidationError(String.format("Section chain - section count not equal (%d but expected %d)", newSectionNames.size(), + oldSectionNames.size())); + return false; + } - boolean isNotTeamCountEqual = newTeams.size() != teamNameToNewStuMap.size() - || newTeams.size() != teamNameToOldStuMap.size(); - if (isNotTeamCountEqual) { - log("Section chain - team count not equal"); + if (!newSectionNames.equals(oldSectionNames)) { + logValidationError(String.format("Section chain - section attributes are not equal")); return false; } + return true; + } - return newTeams.stream().allMatch(team -> { - List oldTeamStudents = teamNameToOldStuMap.get(team.getName()); - List newTeamStudents = teamNameToNewStuMap.get(team.getName()); + // private boolean verifyTeams(Section newSection, + // Map> teamNameToOldStuMap, Map> teamNameToNewStuMap) { + + // List newTeams = newSection.getTeams(); + + // boolean isNotTeamCountEqual = newTeams.size() != teamNameToNewStuMap.size() + // || newTeams.size() != teamNameToOldStuMap.size(); + // if (isNotTeamCountEqual) { + // logValidationError("Section chain - team count not equal"); + // return false; + // } + + // return newTeams.stream().allMatch(team -> { + // List oldTeamStudents = teamNameToOldStuMap.get(team.getName()); + // List newTeamStudents = teamNameToNewStuMap.get(team.getName()); + + // // If either of the teamStudent is null, + // // then team is not present in the corresponding datastore or sql + // // which means a possible migration error + // boolean teamNameNotPresent = oldTeamStudents == null || newTeamStudents == null; + // if (teamNameNotPresent) { + // logValidationError("Section chain - team name not present"); + // return false; + // } + // return verifyStudents(oldTeamStudents, newTeamStudents); + // }); + // } + + private boolean verifyTeams(String courseId) { + log("Verifying teams"); + // Assume that team names are unique within a section but not unique among all sections + + // get all datastore students related to course + List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", courseId) + .list(); - // If either of the teamStudent is null, - // then team is not present in the corresponding datastore or sql - // which means a possible migration error - boolean teamNameNotPresent = oldTeamStudents == null || newTeamStudents == null; - if (teamNameNotPresent) { - log("Section chain - team name not present"); - return false; + // get all teams related to sections + int numberOldTeams = 0; + Map> oldSectionToTeamHashSet = new HashMap>(); + for (CourseStudent student : oldStudents) { + String sectionName = student.getSectionName(); + oldSectionToTeamHashSet.putIfAbsent(sectionName, new HashSet<>()); + HashSet teamHashSet = oldSectionToTeamHashSet.get(sectionName); + boolean addedToSet = teamHashSet.add(student.getTeamName()); + if (addedToSet) { + numberOldTeams += 1; } - return verifyStudents(oldTeamStudents, newTeamStudents); - }); - } + } - private boolean verifyStudents( - List oldTeamStudents, List newTeamStudents) { - if (oldTeamStudents.size() != newTeamStudents.size()) { - log("Section chain - number of students not equal"); + // map team to section + int numberNewTeams = 0; + Map> newSectionToTeamHashSet = new HashMap>(); + List
newSections = getNewSections(courseId); + for (Section newSection : newSections) { + String sectionName = newSection.getName(); + HashSet teamHashSet = new HashSet<>(); + for (Team newTeam : newSection.getTeams()) { + boolean addedToSet = teamHashSet.add(newTeam.getName()); + if (addedToSet) { + numberNewTeams += 1; + } + } + newSectionToTeamHashSet.put(sectionName, teamHashSet); + } + + boolean isTeamCountEqual = numberNewTeams == numberOldTeams; + if (!isTeamCountEqual) { + logValidationError(String.format("Section chain - team count not equal (%d but expected %d)", numberNewTeams, + numberOldTeams)); return false; } - oldTeamStudents.sort((a, b) -> a.getEmail().compareTo(b.getEmail())); - newTeamStudents.sort((a, b) -> a.getEmail().compareTo(b.getEmail())); - for (int i = 0; i < oldTeamStudents.size(); i++) { - CourseStudent oldStudent = oldTeamStudents.get(i); - Student newStudent = newTeamStudents.get(i); + + if (!newSectionToTeamHashSet.equals(oldSectionToTeamHashSet)) { + logValidationError(String.format("Section chain - team attributes are not equal")); + return false; + } + return true; + } + + private boolean verifyStudents(String courseId) { + log("Verifying students"); + List oldStudents = ofy().load().type(CourseStudent.class).filter("courseId", courseId) + .list(); + + Map studentIdToStudentMap = new HashMap(); + + for (Student newStudent : getNewStudents(courseId)) { + // Assume that every course have students with unique emails + studentIdToStudentMap.put(newStudent.getEmail(), newStudent); + } + + + for (CourseStudent oldStudent : oldStudents) { + Student newStudent = studentIdToStudentMap.get(oldStudent.getEmail()); if (!verifyStudent(oldStudent, newStudent)) { - log("Section chain - student failed attribute comparison. Old:" + oldStudent + " New:" + newStudent); return false; } } return true; } + // private boolean verifySectionChain(Course newCourse) { + // // Get old and new students + + // List newStudents = getNewStudents(newCourse.getId()); + + // // Group students by section + // Map> sectionToOldStuMap = oldStudents.stream() + // .collect(Collectors.groupingBy(CourseStudent::getSectionName)); + // Map> sectionToNewStuMap = newStudents.stream() + // .collect(Collectors.groupingBy(Student::getSectionName)); + + // List
newSections = getNewSections(newCourse.getId()); + + // boolean isNotSectionsCountEqual = newSections.size() != sectionToOldStuMap.size() + // || newSections.size() != sectionToNewStuMap.size(); + // if (isNotSectionsCountEqual) { + // logValidationError(String.format("newSection size: %d, sectionToOldStuMap: %d, sectionToOldStuMap: %d", newSections.size(), + // sectionToOldStuMap.size(), sectionToNewStuMap.size())); + // logValidationError("Section chain - section count not equal"); + // return false; + // } + + // return newSections.stream().allMatch(section -> { + // List oldSectionStudents = sectionToOldStuMap.get(section.getName()); + // List newSectionStudents = sectionToNewStuMap.get(section.getName()); + + // // If either of the sectionStudent is null, + // // then section is not present in the corresponding datastore or sql + // // which means a possible migration error + // boolean sectionNameNotPresent = oldSectionStudents == null || newSectionStudents == null; + // if (sectionNameNotPresent) { + // logValidationError("Section chain - section name not present"); + // return false; + // } + + // // Group students by team + // Map> teamNameToOldStuMap = oldSectionStudents.stream() + // .collect(Collectors.groupingBy(CourseStudent::getTeamName)); + // Map> teamNameToNewStuMap = newSectionStudents.stream() + // .collect(Collectors.groupingBy(Student::getTeamName)); + // return verifyTeams(section, teamNameToOldStuMap, teamNameToNewStuMap); + // }); + + // } + + // private boolean verifyTeams(Section newSection, + // Map> teamNameToOldStuMap, Map> teamNameToNewStuMap) { + + // List newTeams = newSection.getTeams(); + + // boolean isNotTeamCountEqual = newTeams.size() != teamNameToNewStuMap.size() + // || newTeams.size() != teamNameToOldStuMap.size(); + // if (isNotTeamCountEqual) { + // logValidationError("Section chain - team count not equal"); + // return false; + // } + + // return newTeams.stream().allMatch(team -> { + // List oldTeamStudents = teamNameToOldStuMap.get(team.getName()); + // List newTeamStudents = teamNameToNewStuMap.get(team.getName()); + + // // If either of the teamStudent is null, + // // then team is not present in the corresponding datastore or sql + // // which means a possible migration error + // boolean teamNameNotPresent = oldTeamStudents == null || newTeamStudents == null; + // if (teamNameNotPresent) { + // logValidationError("Section chain - team name not present"); + // return false; + // } + // return verifyStudents(oldTeamStudents, newTeamStudents); + // }); + // } + + // private boolean verifyStudents( + // List oldTeamStudents, List newTeamStudents) { + // if (oldTeamStudents.size() != newTeamStudents.size()) { + // logValidationError("Section chain - number of students not equal"); + // return false; + // } + // oldTeamStudents.sort((a, b) -> a.getEmail().compareTo(b.getEmail())); + // newTeamStudents.sort((a, b) -> a.getEmail().compareTo(b.getEmail())); + // for (int i = 0; i < oldTeamStudents.size(); i++) { + // CourseStudent oldStudent = oldTeamStudents.get(i); + // Student newStudent = newTeamStudents.get(i); + // if (!verifyStudent(oldStudent, newStudent)) { + // logValidationError("Section chain - student failed attribute comparison. Old:" + oldStudent + " New:" + newStudent); + // return false; + // } + // } + // return true; + // } + + + private boolean verifyStudent(CourseStudent oldStudent, Student newStudent) { if (!(newStudent.getGoogleId() == null ? newStudent.getGoogleId() == oldStudent.getGoogleId() : newStudent.getGoogleId().equals(oldStudent.getGoogleId()))) { - log("Mismatch in google ids " + newStudent.getGoogleId() + " " + oldStudent.getGoogleId()); + logValidationError(String.format("Mismatch in google ids. Expected %s but got %s", + newStudent.getGoogleId(), + oldStudent.getGoogleId())); + return false; } - return newStudent.getName().equals(oldStudent.getName()) + boolean attributesAreEqual = newStudent.getName().equals(oldStudent.getName()) && newStudent.getEmail().equals(oldStudent.getEmail()) && newStudent.getComments().equals(oldStudent.getComments()) - && newStudent.getUpdatedAt().equals(oldStudent.getUpdatedAt()) && newStudent.getCreatedAt().equals(oldStudent.getCreatedAt()) && newStudent.getRegKey().equals(oldStudent.getRegistrationKey()) + && newStudent.getCourseId().equals(oldStudent.getCourseId()) + && newStudent.getSectionName().equals(oldStudent.getSectionName()) + && newStudent.getTeamName().equals(oldStudent.getTeamName()) && (newStudent.getGoogleId() == null ? newStudent.getGoogleId() == oldStudent.getGoogleId() : newStudent.getGoogleId().equals(oldStudent.getGoogleId()) ); + + if (!attributesAreEqual) { + logValidationError(String.format("Section chain - student attributes are not equal")); + return false; + } + + return true; } // methods for verify feedback chain ----------------------------------------------------------------------------------- // entities: FeedbackSession, FeedbackQuestion, FeedbackResponse, FeedbackResponseComment - private boolean verifyFeedbackChain(teammates.storage.sqlentity.Course newCourse) { - List newSessions = newCourse.getFeedbackSessions(); - List oldSessions = ofy().load().type(FeedbackSession.class) + private boolean verifyFeedbackChain(String courseId) { + log("Verifying feedback chain"); + Course newCourse = getCourse(courseId); + List newSessions = newCourse.getFeedbackSessions(); + List oldSessions = ofy().load().type(teammates.storage.entity.FeedbackSession.class) .filter("courseId", newCourse.getId()).list(); if (newSessions.size() != oldSessions.size()) { - log(String.format("Mismatched session counts for course id: %s. Old size: %d, New size: %d", newCourse.getId(), newSessions.size(), oldSessions.size())); + logValidationError(String.format("Mismatched session counts for course id: %s. Old size: %d, New size: %d", newCourse.getId(), newSessions.size(), oldSessions.size())); return false; } - Map sessionNameToOldSessionMap = oldSessions.stream() - .collect(Collectors.toMap(FeedbackSession::getFeedbackSessionName, session -> session)); + Map sessionNameToOldSessionMap = oldSessions.stream() + .collect(Collectors.toMap(teammates.storage.entity.FeedbackSession::getFeedbackSessionName, session -> session)); return newSessions.stream().allMatch(newSession -> { - FeedbackSession oldSession = sessionNameToOldSessionMap.get(newSession.getName()); + teammates.storage.entity.FeedbackSession oldSession = sessionNameToOldSessionMap.get(newSession.getName()); return verifyFeedbackSession(oldSession, newSession); }); } - private boolean verifyFeedbackSession(FeedbackSession oldSession, teammates.storage.sqlentity.FeedbackSession newSession) { + private boolean verifyFeedbackSession(teammates.storage.entity.FeedbackSession oldSession, FeedbackSession newSession) { boolean doFieldsMatch = newSession.getCourse().getId().equals(oldSession.getCourseId()) && newSession.getName().equals(oldSession.getFeedbackSessionName()) && newSession.getCreatorEmail().equals(oldSession.getCreatorEmail()) @@ -253,11 +441,12 @@ private boolean verifyFeedbackSession(FeedbackSession oldSession, teammates.stor && newSession.isClosedEmailSent() == oldSession.isSentClosedEmail() && newSession.isClosingSoonEmailSent() == oldSession.isSentClosingEmail() && newSession.isPublishedEmailSent() == oldSession.isSentPublishedEmail() + && newSession.isPublishedEmailEnabled() == oldSession.isPublishedEmailEnabled() && newSession.getCreatedAt().equals(oldSession.getCreatedTime()) && (newSession.getDeletedAt() == oldSession.getDeletedTime() || newSession.getDeletedAt().equals(oldSession.getDeletedTime())); if (!doFieldsMatch) { - log(String.format("Mismatched fields for session: %s, course id: %s", + logValidationError(String.format("Mismatched fields for session: %s, course id: %s", oldSession.getFeedbackSessionName(), oldSession.getCourseId())); return false; } @@ -268,7 +457,7 @@ private boolean verifyFeedbackSession(FeedbackSession oldSession, teammates.stor .filter("feedbackSessionName", newSession.getName()).list(); if (newQuestions.size() != oldQuestions.size()) { - log(String.format("Mismatched question counts for session: %s, course id: %s", + logValidationError(String.format("Mismatched question counts for session: %s, course id: %s", oldSession.getFeedbackSessionName(), oldSession.getCourseId())); return false; } @@ -293,10 +482,9 @@ private boolean verifyFeedbackQuestion(FeedbackQuestion oldQuestion, && newQuestion.getShowGiverNameTo().equals(oldQuestion.getShowGiverNameTo()) && newQuestion.getShowRecipientNameTo().equals(oldQuestion.getShowRecipientNameTo()) && newQuestion.getQuestionDetailsCopy().getJsonString().equals(oldQuestion.getQuestionText()) - && newQuestion.getCreatedAt().equals(oldQuestion.getCreatedAt()) - && newQuestion.getUpdatedAt().equals(oldQuestion.getUpdatedAt()); + && newQuestion.getCreatedAt().equals(oldQuestion.getCreatedAt()); if (!doFieldsMatch) { - log(String.format("Mismatched fields for question %s, session: %s, course id: %s", + logValidationError(String.format("Mismatched fields for question %s, session: %s, course id: %s", oldQuestion.getQuestionNumber(), oldQuestion.getFeedbackSessionName(), oldQuestion.getCourseId())); return false; } @@ -306,7 +494,7 @@ private boolean verifyFeedbackQuestion(FeedbackQuestion oldQuestion, .filter("feedbackQuestionId", oldQuestion.getId()).list(); if (newResponses.size() != oldResponses.size()) { - log(String.format("Mismatched response counts for question. New: %d, Old: %d, %s, session: %s, course id: %s", + logValidationError(String.format("Mismatched response counts for question. New: %d, Old: %d, %s, session: %s, course id: %s", newResponses.size(), oldResponses.size(), oldQuestion.getQuestionNumber(), oldQuestion.getFeedbackSessionName(), oldQuestion.getCourseId())); return false; @@ -315,12 +503,14 @@ private boolean verifyFeedbackQuestion(FeedbackQuestion oldQuestion, Map responseIdToOldResponseMap = oldResponses.stream() .collect(Collectors.toMap(FeedbackResponse::getId, response -> response)); - return newResponses.stream().allMatch(newResponse -> { + boolean responsesAreEqual = newResponses.stream().allMatch(newResponse -> { String oldResponseId = FeedbackResponse.generateId(oldQuestion.getId(), newResponse.getGiver(), newResponse.getRecipient()); FeedbackResponse oldResponse = responseIdToOldResponseMap.get(oldResponseId); return verifyFeedbackResponse(oldResponse, newResponse); }); + + return responsesAreEqual; } private boolean verifyFeedbackResponse(FeedbackResponse oldResponse, @@ -331,10 +521,9 @@ private boolean verifyFeedbackResponse(FeedbackResponse oldResponse, && newResponse.getRecipient().equals(oldResponse.getRecipientEmail()) && newResponse.getRecipientSectionName().equals(oldResponse.getRecipientSection()) && newResponse.getCreatedAt().equals(oldResponse.getCreatedAt()) - && newResponse.getUpdatedAt().equals(oldResponse.getUpdatedAt()) && newResponse.getFeedbackResponseDetailsCopy().getJsonString().equals(oldResponse.getAnswer()); if (!allFieldsMatch) { - log(String.format("Mismatched fields for response %s, question %s, session: %s, course id: %s", + logValidationError(String.format("Mismatched fields for response %s, question %s, session: %s, course id: %s", oldResponse.getId(), oldResponse.getFeedbackQuestionId(), oldResponse.getFeedbackSessionName(), oldResponse.getCourseId())); return false; @@ -346,7 +535,7 @@ private boolean verifyFeedbackResponse(FeedbackResponse oldResponse, .filter("feedbackResponseId", oldResponse.getId()).list(); if (newComments.size() != oldComments.size()) { - log(String.format("Mismatched comment counts for response %s, question %s, session: %s, course id: %s", + logValidationError(String.format("Mismatched comment counts for response %s, question %s, session: %s, course id: %s", oldResponse.getId(), oldResponse.getFeedbackQuestionId(), oldResponse.getFeedbackSessionName(), oldResponse.getCourseId())); return false; @@ -355,7 +544,7 @@ private boolean verifyFeedbackResponse(FeedbackResponse oldResponse, boolean allCommentFieldsMatch = oldComments.stream().allMatch(oldComment -> newComments.stream() .anyMatch(newComment -> verifyFeedbackResponseComment(oldComment, newComment))); if (!allCommentFieldsMatch) { - log(String.format("Mismatched fields for comments in response %s, question %s, session: %s, course id: %s", + logValidationError(String.format("Mismatched fields for comments in response %s, question %s, session: %s, course id: %s", oldResponse.getId(), oldResponse.getFeedbackQuestionId(), oldResponse.getFeedbackSessionName(), oldResponse.getCourseId())); return false; @@ -378,18 +567,17 @@ private boolean verifyFeedbackResponseComment(FeedbackResponseComment oldComment && newComment.getShowCommentTo().equals(oldComment.getShowCommentTo()) && newComment.getShowGiverNameTo().equals(oldComment.getShowGiverNameTo()) && newComment.getCreatedAt().equals(oldComment.getCreatedAt()) - && newComment.getUpdatedAt().equals(oldComment.getLastEditedAt()) && newComment.getLastEditorEmail().equals(oldComment.getLastEditorEmail()); } // Verify Instructor ---------------------------- - private boolean verifyInstructors(teammates.storage.sqlentity.Course newCourse) { + private boolean verifyInstructors(Course newCourse) { List newInstructors = getNewInstructors(newCourse.getId()); List oldInstructors = ofy().load().type(Instructor.class).filter("courseId", newCourse.getId()) .list(); if (oldInstructors.size() != newInstructors.size()) { - log("Feedback chain - Instructor counts not equal"); + logValidationError("Feedback chain - Instructor counts not equal"); return false; } @@ -399,7 +587,7 @@ private boolean verifyInstructors(teammates.storage.sqlentity.Course newCourse) Instructor oldInstructor = oldInstructors.get(i); teammates.storage.sqlentity.Instructor newInstructor = newInstructors.get(i); if (!verifyInstructor(oldInstructor, newInstructor)) { - log("Feedback chain - Instructor attributes failed comparison"); + logValidationError("Feedback chain - Instructor attributes failed comparison"); return false; } } @@ -425,31 +613,43 @@ private boolean verifyInstructor(Instructor oldInstructor, && newInstructor.getPrivileges().equals(oldPrivileges) && newInstructor.isDisplayedToStudents() == oldInstructor.isDisplayedToStudents() && newInstructor.getCreatedAt().equals(oldInstructor.getCreatedAt()) - && newInstructor.getUpdatedAt().equals(oldInstructor.getUpdatedAt()) + // && newInstructor.getUpdatedAt().equals(oldInstructor.getUpdatedAt()); && (newInstructor.getGoogleId() == null ? newInstructor.getGoogleId() == oldInstructor.getGoogleId() : newInstructor.getGoogleId().equals(oldInstructor.getGoogleId())); } // Verify DeadlineExtensions ---------------------------- - private boolean verifyDeadlineExtensions(teammates.storage.sqlentity.Course newCourse) { + private boolean verifyDeadlineExtensions(Course newCourse) { List newDeadlineExt = getNewDeadlineExtensions(newCourse.getId()); List oldDeadlineExt = ofy().load() .type(DeadlineExtension.class).filter("courseId", newCourse.getId()).list(); if (oldDeadlineExt.size() != newDeadlineExt.size()) { - log("Deadline extension size not equal"); + logValidationError("Deadline extension size not equal"); return false; } - newDeadlineExt.sort((a, b) -> a.getId().compareTo(b.getId())); - oldDeadlineExt.sort((a, b) -> a.getId().compareTo(b.getId())); + newDeadlineExt.sort((a, b) -> a.getCreatedAt().compareTo(b.getCreatedAt())); + oldDeadlineExt.sort((a, b) -> a.getCreatedAt().compareTo(b.getCreatedAt())); for (int i = 0; i < oldDeadlineExt.size(); i++) { DeadlineExtension oldDeadline = oldDeadlineExt.get(i); teammates.storage.sqlentity.DeadlineExtension newDeadline = newDeadlineExt.get(i); if (!verifyDeadlineExtension(oldDeadline, newDeadline)) { - log("Deadline extension failed comparison"); + logValidationError("Deadline extension failed comparison"); + // logValidationError(String.format("Expected oldDeadline with feedback name %s, userEmail %s, endTime %s, closingEmailSent %b, createdAt %s", + // oldDeadline.getFeedbackSessionName(), + // oldDeadline.getUserEmail(), + // oldDeadline.getEndTime(), + // oldDeadline.getSentClosingEmail(), + // oldDeadline.getCreatedAt())); + // logValidationError(String.format("Expected oldDeadline with feedback name %s, userEmail %s, endTime %s, closingEmailSent %b, createdAt %s", + // newDeadline.getFeedbackSession().getName(), + // newDeadline.getUser().getEmail(), + // newDeadline.getEndTime(), + // newDeadline.isClosingSoonEmailSent(), + // newDeadline.getCreatedAt())); return false; } } @@ -462,7 +662,7 @@ private boolean verifyDeadlineExtension(DeadlineExtension oldDeadline, && newDeadline.getUser().getEmail().equals(oldDeadline.getUserEmail()) && newDeadline.getEndTime().equals(oldDeadline.getEndTime()) && newDeadline.isClosingSoonEmailSent() == oldDeadline.getSentClosingEmail() - && newDeadline.getUpdatedAt().equals(oldDeadline.getUpdatedAt()) + // && newDeadline.getUpdatedAt().equals(oldDeadline.getUpdatedAt()) && newDeadline.getCreatedAt().equals(oldDeadline.getCreatedAt()); } @@ -482,7 +682,17 @@ private List
getNewSections(String courseId) { CriteriaQuery cr = cb .createQuery(teammates.storage.sqlentity.Section.class); Root sectionRoot = cr.from(teammates.storage.sqlentity.Section.class); - cr.select(sectionRoot).where(cb.equal(sectionRoot.get("courseId"), courseId)); + cr.select(sectionRoot).where(cb.equal(sectionRoot.get("course").get("id"), courseId)); + List
newSections = HibernateUtil.createQuery(cr).getResultList(); + return newSections; + } + + private List
getnew(String courseId) { + CriteriaBuilder cb = HibernateUtil.getCriteriaBuilder(); + CriteriaQuery cr = cb + .createQuery(teammates.storage.sqlentity.Section.class); + Root sectionRoot = cr.from(teammates.storage.sqlentity.Section.class); + cr.select(sectionRoot).where(cb.equal(sectionRoot.get("course").get("id"), courseId)); List
newSections = HibernateUtil.createQuery(cr).getResultList(); return newSections; } diff --git a/src/client/java/teammates/client/scripts/sql/VerifyNonCourseEntityAttributesBaseScript.java b/src/client/java/teammates/client/scripts/sql/VerifyNonCourseEntityAttributesBaseScript.java index 345a2248ed1..3f1d5c4bcf5 100644 --- a/src/client/java/teammates/client/scripts/sql/VerifyNonCourseEntityAttributesBaseScript.java +++ b/src/client/java/teammates/client/scripts/sql/VerifyNonCourseEntityAttributesBaseScript.java @@ -46,6 +46,8 @@ public abstract class VerifyNonCourseEntityAttributesBaseScript datastoreEntityClass, Class sqlEntityClass) { this.datastoreEntityClass = datastoreEntityClass; @@ -56,10 +58,9 @@ public VerifyNonCourseEntityAttributesBaseScript( String password = ClientProperties.SCRIPT_API_PASSWORD; HibernateUtil.buildSessionFactory(connectionUrl, username, password); - } - private String getLogPrefix() { - return String.format("%s verifying fields:", sqlEntityClass.getName()); + String logPrefix = String.format("%s verifying fields:", sqlEntityClass.getName()); + this.logger = new Logger(logPrefix); } /** @@ -135,7 +136,7 @@ protected List> checkAllEntitiesForFailures() { List> failures = new LinkedList<>(); int numPages = getNumPages(); if (numPages == 0) { - log("No entities available for verification"); + logError("No entities available for verification"); return failures; } @@ -193,9 +194,9 @@ protected void runCheckAllEntities(Class sqlEntityClass, System.out.println("========================================"); if (!failedEntities.isEmpty()) { - log("Errors detected"); + logError("Errors detected"); for (Map.Entry failure : failedEntities) { - log("Sql entity: " + failure.getKey() + " datastore entity: " + failure.getValue()); + logValidationError("Sql entity: " + failure.getKey() + " datastore entity: " + failure.getValue()); } } else { log("No errors detected"); @@ -207,12 +208,21 @@ protected void runCheckAllEntities(Class sqlEntityClass, HibernateUtil.commitTransaction(); } + protected void log(String logLine) { + logger.log(logLine); + } + + + protected void logError(String logLine) { + logger.logError(logLine); + } + /** - * Log a line. + * Log a validation error. * @param logLine the line to log */ - protected void log(String logLine) { - System.out.println(String.format("%s %s", getLogPrefix(), logLine)); + protected void logValidationError(String logLine) { + logger.log(String.format("[ERROR IN VALIDATION] %s", logLine)); } /** diff --git a/src/main/java/teammates/storage/sqlentity/DeadlineExtension.java b/src/main/java/teammates/storage/sqlentity/DeadlineExtension.java index 11c26a5dd0b..3d28a2e1266 100644 --- a/src/main/java/teammates/storage/sqlentity/DeadlineExtension.java +++ b/src/main/java/teammates/storage/sqlentity/DeadlineExtension.java @@ -8,6 +8,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.annotations.UpdateTimestamp; import teammates.common.util.FieldValidator; @@ -47,7 +48,7 @@ public class DeadlineExtension extends BaseEntity { @Column(nullable = false) private boolean isClosingSoonEmailSent; - // @UpdateTimestamp + @UpdateTimestamp @Column(nullable = false) private Instant updatedAt; diff --git a/src/main/java/teammates/storage/sqlentity/FeedbackQuestion.java b/src/main/java/teammates/storage/sqlentity/FeedbackQuestion.java index 1faae40d724..bf65e7f9682 100644 --- a/src/main/java/teammates/storage/sqlentity/FeedbackQuestion.java +++ b/src/main/java/teammates/storage/sqlentity/FeedbackQuestion.java @@ -6,6 +6,21 @@ import java.util.Objects; import java.util.UUID; +import org.hibernate.annotations.UpdateTimestamp; + +import teammates.common.datatransfer.FeedbackParticipantType; +import teammates.common.datatransfer.questions.FeedbackQuestionDetails; +import teammates.common.util.FieldValidator; +import teammates.storage.sqlentity.questions.FeedbackConstantSumQuestion; +import teammates.storage.sqlentity.questions.FeedbackContributionQuestion; +import teammates.storage.sqlentity.questions.FeedbackMcqQuestion; +import teammates.storage.sqlentity.questions.FeedbackMsqQuestion; +import teammates.storage.sqlentity.questions.FeedbackNumericalScaleQuestion; +import teammates.storage.sqlentity.questions.FeedbackRankOptionsQuestion; +import teammates.storage.sqlentity.questions.FeedbackRankRecipientsQuestion; +import teammates.storage.sqlentity.questions.FeedbackRubricQuestion; +import teammates.storage.sqlentity.questions.FeedbackTextQuestion; + import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -19,31 +34,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; import org.hibernate.annotations.UpdateTimestamp; -import teammates.common.datatransfer.FeedbackParticipantType; -import teammates.common.datatransfer.questions.FeedbackQuestionDetails; -import teammates.common.util.FieldValidator; -import teammates.storage.sqlentity.questions.FeedbackConstantSumQuestion; -import teammates.storage.sqlentity.questions.FeedbackContributionQuestion; -import teammates.storage.sqlentity.questions.FeedbackMcqQuestion; -import teammates.storage.sqlentity.questions.FeedbackMsqQuestion; -import teammates.storage.sqlentity.questions.FeedbackNumericalScaleQuestion; -import teammates.storage.sqlentity.questions.FeedbackRankOptionsQuestion; -import teammates.storage.sqlentity.questions.FeedbackRankRecipientsQuestion; -import teammates.storage.sqlentity.questions.FeedbackRubricQuestion; -import teammates.storage.sqlentity.questions.FeedbackTextQuestion; - /** * Represents a feedback question. */ @Entity -@Table(name = "FeedbackQuestions", uniqueConstraints = { - @UniqueConstraint(name = "Unique question number per session", - columnNames = { "questionNumber", "sessionId" }), -}) +@Table(name = "FeedbackQuestions") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class FeedbackQuestion extends BaseEntity implements Comparable { @Id @@ -85,7 +83,7 @@ public abstract class FeedbackQuestion extends BaseEntity implements Comparable< @Convert(converter = FeedbackParticipantTypeListConverter.class) private List showRecipientNameTo; - // @UpdateTimestamp + @UpdateTimestamp @Column private Instant updatedAt; diff --git a/src/main/java/teammates/storage/sqlentity/FeedbackResponse.java b/src/main/java/teammates/storage/sqlentity/FeedbackResponse.java index 25a69df23cd..831fef1c570 100644 --- a/src/main/java/teammates/storage/sqlentity/FeedbackResponse.java +++ b/src/main/java/teammates/storage/sqlentity/FeedbackResponse.java @@ -6,6 +6,19 @@ import java.util.Objects; import java.util.UUID; +import org.hibernate.annotations.UpdateTimestamp; + +import teammates.common.datatransfer.questions.FeedbackResponseDetails; +import teammates.storage.sqlentity.responses.FeedbackConstantSumResponse; +import teammates.storage.sqlentity.responses.FeedbackContributionResponse; +import teammates.storage.sqlentity.responses.FeedbackMcqResponse; +import teammates.storage.sqlentity.responses.FeedbackMsqResponse; +import teammates.storage.sqlentity.responses.FeedbackNumericalScaleResponse; +import teammates.storage.sqlentity.responses.FeedbackRankOptionsResponse; +import teammates.storage.sqlentity.responses.FeedbackRankRecipientsResponse; +import teammates.storage.sqlentity.responses.FeedbackRubricResponse; +import teammates.storage.sqlentity.responses.FeedbackTextResponse; + import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -19,16 +32,6 @@ import org.hibernate.annotations.UpdateTimestamp; -import teammates.common.datatransfer.questions.FeedbackResponseDetails; -import teammates.storage.sqlentity.responses.FeedbackConstantSumResponse; -import teammates.storage.sqlentity.responses.FeedbackContributionResponse; -import teammates.storage.sqlentity.responses.FeedbackMcqResponse; -import teammates.storage.sqlentity.responses.FeedbackMsqResponse; -import teammates.storage.sqlentity.responses.FeedbackNumericalScaleResponse; -import teammates.storage.sqlentity.responses.FeedbackRankOptionsResponse; -import teammates.storage.sqlentity.responses.FeedbackRankRecipientsResponse; -import teammates.storage.sqlentity.responses.FeedbackRubricResponse; -import teammates.storage.sqlentity.responses.FeedbackTextResponse; /** * Represents a Feedback Response. diff --git a/src/main/java/teammates/storage/sqlentity/FeedbackResponseComment.java b/src/main/java/teammates/storage/sqlentity/FeedbackResponseComment.java index c81b6f06a34..2b7ad1efa23 100644 --- a/src/main/java/teammates/storage/sqlentity/FeedbackResponseComment.java +++ b/src/main/java/teammates/storage/sqlentity/FeedbackResponseComment.java @@ -7,6 +7,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.annotations.UpdateTimestamp; import teammates.common.datatransfer.FeedbackParticipantType; import teammates.common.util.FieldValidator; @@ -71,7 +72,7 @@ public class FeedbackResponseComment extends BaseEntity { @Convert(converter = FeedbackParticipantTypeListConverter.class) private List showGiverNameTo; - // @UpdateTimestamp + @UpdateTimestamp private Instant updatedAt; @Column(nullable = false) diff --git a/src/main/java/teammates/storage/sqlentity/FeedbackSession.java b/src/main/java/teammates/storage/sqlentity/FeedbackSession.java index ddad1ed00b1..2d190c8448f 100644 --- a/src/main/java/teammates/storage/sqlentity/FeedbackSession.java +++ b/src/main/java/teammates/storage/sqlentity/FeedbackSession.java @@ -12,6 +12,7 @@ import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.annotations.UpdateTimestamp; import teammates.common.util.Const; import teammates.common.util.FieldValidator; @@ -107,7 +108,7 @@ public class FeedbackSession extends BaseEntity { @OnDelete(action = OnDeleteAction.CASCADE) private List feedbackQuestions = new ArrayList<>(); - // @UpdateTimestamp + @UpdateTimestamp private Instant updatedAt; private Instant deletedAt; diff --git a/src/main/java/teammates/storage/sqlentity/Section.java b/src/main/java/teammates/storage/sqlentity/Section.java index 9f5ac3a4b60..799e2d30358 100644 --- a/src/main/java/teammates/storage/sqlentity/Section.java +++ b/src/main/java/teammates/storage/sqlentity/Section.java @@ -38,9 +38,6 @@ public class Section extends BaseEntity { @JoinColumn(name = "courseId") private Course course; - @Column(nullable = false, insertable = false, updatable = false) - private String courseId; - @Column(nullable = false) private String name; diff --git a/src/main/java/teammates/storage/sqlentity/User.java b/src/main/java/teammates/storage/sqlentity/User.java index e8c446780a1..0ec85bd8fdd 100644 --- a/src/main/java/teammates/storage/sqlentity/User.java +++ b/src/main/java/teammates/storage/sqlentity/User.java @@ -5,6 +5,8 @@ import java.util.Objects; import java.util.UUID; +import org.hibernate.annotations.UpdateTimestamp; + import teammates.common.util.SanitizationHelper; import teammates.common.util.StringHelper; @@ -53,7 +55,7 @@ public abstract class User extends BaseEntity { @Column(nullable = false) private String regKey; - // @UpdateTimestamp + @UpdateTimestamp private Instant updatedAt; protected User() {