From 90b1dcfb667774e3259ba7e43e519358912ee370 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 17:11:08 +0530 Subject: [PATCH] fix: user pagination --- .../java/io/supertokens/inmemorydb/Start.java | 2 +- .../queries/EmailPasswordQueries.java | 10 +- .../inmemorydb/queries/GeneralQueries.java | 148 ++++++++++-------- .../queries/PasswordlessQueries.java | 10 +- .../inmemorydb/queries/ThirdPartyQueries.java | 11 +- 5 files changed, 99 insertions(+), 82 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 2dc25ca80..919459a93 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2885,7 +2885,7 @@ public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionC Connection sqlCon = (Connection) con.getConnection(); // we do not bother returning if a row was updated here or not, cause it's happening // in a transaction anyway. - GeneralQueries.unlinkAccounts_Transaction(this, sqlCon, appIdentifier, recipeUserId); + GeneralQueries.unlinkAccounts_Transaction(this, sqlCon, appIdentifier, primaryUserId, recipeUserId); } catch (SQLException e) { throw new StorageQueryException(e); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 360186f63..77cb8f4ed 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -259,8 +259,8 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + - " VALUES(?, ?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -268,6 +268,7 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden pst.setString(4, userId); pst.setString(5, EMAIL_PASSWORD.toString()); pst.setLong(6, timeJoined); + pst.setLong(7, timeJoined); }); } @@ -446,8 +447,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -455,6 +456,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC pst.setString(4, userId); pst.setString(5, EMAIL_PASSWORD.toString()); pst.setLong(6, userInfo.timeJoined); + pst.setLong(7, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index fa87d7e11..9b336371e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -87,7 +87,7 @@ static String getQueryToCreateUsersTable(Start start) { static String getQueryToCreateUserPaginationIndex(Start start) { return "CREATE INDEX all_auth_recipe_users_pagination_index ON " + Config.getConfig(start).getUsersTable() - + "(time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; + + "(primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; } static String getQueryToCreatePrimaryUserIdIndex(Start start) { @@ -558,7 +558,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant throws SQLException, StorageQueryException { // This list will be used to keep track of the result's order from the db - List usersFromQuery; + List usersFromQuery; if (dashboardSearchTags != null) { ArrayList queryList = new ArrayList<>(); @@ -732,17 +732,16 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant usersFromQuery = new ArrayList<>(); } else { - String finalQuery = "SELECT * FROM ( " + USER_SEARCH_TAG_CONDITION.toString() + " )" - + " AS finalResultTable ORDER BY time_joined " + timeJoinedOrder + ", user_id DESC "; + String finalQuery = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM ( " + USER_SEARCH_TAG_CONDITION.toString() + " )" + + " AS finalResultTable ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + ", primary_or_recipe_user_id DESC "; usersFromQuery = execute(start, finalQuery, pst -> { for (int i = 1; i <= queryList.size(); i++) { pst.setString(i, queryList.get(i - 1)); } }, result -> { - List temp = new ArrayList<>(); + List temp = new ArrayList<>(); while (result.next()) { - temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"), - result.getString("recipe_id"))); + temp.add(result.getString("primary_or_recipe_user_id")); } return temp; }); @@ -771,11 +770,11 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant recipeIdCondition = recipeIdCondition + " AND"; } String timeJoinedOrderSymbol = timeJoinedOrder.equals("ASC") ? ">" : "<"; - String QUERY = "SELECT user_id, recipe_id FROM " + getConfig(start).getUsersTable() + " WHERE " - + recipeIdCondition + " (time_joined " + timeJoinedOrderSymbol - + " ? OR (time_joined = ? AND user_id <= ?)) AND app_id = ? AND tenant_id = ?" - + " ORDER BY time_joined " + timeJoinedOrder - + ", user_id DESC LIMIT ?"; + String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + getConfig(start).getUsersTable() + " WHERE " + + recipeIdCondition + " (primary_or_recipe_user_time_joined " + timeJoinedOrderSymbol + + " ? OR (primary_or_recipe_user_time_joined = ? AND primary_or_recipe_user_id <= ?)) AND app_id = ? AND tenant_id = ?" + + " ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + + ", primary_or_recipe_user_id DESC LIMIT ?"; usersFromQuery = execute(start, QUERY, pst -> { if (includeRecipeIds != null) { for (int i = 0; i < includeRecipeIds.length; i++) { @@ -791,21 +790,20 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant pst.setString(baseIndex + 5, tenantIdentifier.getTenantId()); pst.setInt(baseIndex + 6, limit); }, result -> { - List temp = new ArrayList<>(); + List temp = new ArrayList<>(); while (result.next()) { - temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"), - result.getString("recipe_id"))); + temp.add(result.getString("primary_or_recipe_user_id")); } return temp; }); } else { String recipeIdCondition = RECIPE_ID_CONDITION.toString(); - String QUERY = "SELECT user_id, recipe_id FROM " + getConfig(start).getUsersTable() + " WHERE "; + String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + getConfig(start).getUsersTable() + " WHERE "; if (!recipeIdCondition.equals("")) { QUERY += recipeIdCondition + " AND"; } - QUERY += " app_id = ? AND tenant_id = ? ORDER BY time_joined " + timeJoinedOrder - + ", user_id DESC LIMIT ?"; + QUERY += " app_id = ? AND tenant_id = ? ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + + ", primary_or_recipe_user_id DESC LIMIT ?"; usersFromQuery = execute(start, QUERY, pst -> { if (includeRecipeIds != null) { for (int i = 0; i < includeRecipeIds.length; i++) { @@ -818,49 +816,31 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant pst.setString(baseIndex + 2, tenantIdentifier.getTenantId()); pst.setInt(baseIndex + 3, limit); }, result -> { - List temp = new ArrayList<>(); + List temp = new ArrayList<>(); while (result.next()) { - temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"), - result.getString("recipe_id"))); + temp.add(result.getString("primary_or_recipe_user_id")); } return temp; }); } } - // we create a map from recipe ID -> userId[] - Map> recipeIdToUserIdListMap = new HashMap<>(); - for (UserInfoPaginationResultHolder user : usersFromQuery) { - RECIPE_ID recipeId = RECIPE_ID.getEnumFromString(user.recipeId); - if (recipeId == null) { - throw new SQLException("Unrecognised recipe ID in database: " + user.recipeId); - } - List userIdList = recipeIdToUserIdListMap.get(recipeId); - if (userIdList == null) { - userIdList = new ArrayList<>(); - } - userIdList.add(user.userId); - recipeIdToUserIdListMap.put(recipeId, userIdList); - } - AuthRecipeUserInfo[] finalResult = new AuthRecipeUserInfo[usersFromQuery.size()]; // we give the userId[] for each recipe to fetch all those user's details - for (RECIPE_ID recipeId : recipeIdToUserIdListMap.keySet()) { - List users = getPrimaryUserInfoForUserIds(start, - tenantIdentifier.toAppIdentifier(), - recipeIdToUserIdListMap.get(recipeId)); - - // we fill in all the slots in finalResult based on their position in - // usersFromQuery - Map userIdToInfoMap = new HashMap<>(); - for (AuthRecipeUserInfo user : users) { - userIdToInfoMap.put(user.getSupertokensUserId(), user); - } - for (int i = 0; i < usersFromQuery.size(); i++) { - if (finalResult[i] == null) { - finalResult[i] = userIdToInfoMap.get(usersFromQuery.get(i).userId); - } + List users = getPrimaryUserInfoForUserIds(start, + tenantIdentifier.toAppIdentifier(), + usersFromQuery); + + // we fill in all the slots in finalResult based on their position in + // usersFromQuery + Map userIdToInfoMap = new HashMap<>(); + for (AuthRecipeUserInfo user : users) { + userIdToInfoMap.put(user.getSupertokensUserId(), user); + } + for (int i = 0; i < usersFromQuery.size(); i++) { + if (finalResult[i] == null) { + finalResult[i] = userIdToInfoMap.get(usersFromQuery.get(i)); } } @@ -882,28 +862,58 @@ public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, A public static void linkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String recipeUserId, String primaryUserId) throws SQLException, StorageQueryException { - String QUERY = "UPDATE " + getConfig(start).getUsersTable() + - " SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + - "user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, primaryUserId); - pst.setString(2, appIdentifier.getAppId()); - pst.setString(3, recipeUserId); - }); + { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + + "user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, primaryUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } + { // update primary_or_recipe_user_time_joined to min time joined + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET primary_or_recipe_user_time_joined = (SELECT MIN(time_joined) FROM " + + getConfig(start).getUsersTable() + " WHERE app_id = ? AND primary_or_recipe_user_id = ?) WHERE " + + " app_id = ? AND primary_or_recipe_user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, primaryUserId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, primaryUserId); + }); + } } public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, - String recipeUserId) + String primaryUserId, String recipeUserId) throws SQLException, StorageQueryException { - String QUERY = "UPDATE " + getConfig(start).getUsersTable() + - " SET is_linked_or_is_a_primary_user = false, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + - "user_id = ?"; - - update(sqlCon, QUERY, pst -> { - pst.setString(1, recipeUserId); - pst.setString(2, appIdentifier.getAppId()); - pst.setString(3, recipeUserId); - }); + { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = false, primary_or_recipe_user_id = ?, " + + "primary_or_recipe_user_time_joined = time_joined WHERE app_id = ? AND " + + "user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, recipeUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } + { // update primary_or_recipe_user_time_joined to min time joined + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET primary_or_recipe_user_time_joined = (SELECT MIN(time_joined) FROM " + + getConfig(start).getUsersTable() + " WHERE app_id = ? AND primary_or_recipe_user_id = ?) WHERE " + + " app_id = ? AND primary_or_recipe_user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, primaryUserId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, primaryUserId); + }); + } } public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 80ca321e9..82cd39bf9 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -383,8 +383,8 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + - " VALUES(?, ?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -392,6 +392,7 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant pst.setString(4, id); pst.setString(5, PASSWORDLESS.toString()); pst.setLong(6, timeJoined); + pst.setLong(7, timeJoined); }); } @@ -829,8 +830,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -838,6 +839,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC pst.setString(4, userInfo.id); pst.setString(5, PASSWORDLESS.toString()); pst.setLong(6, userInfo.timeJoined); + pst.setLong(7, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index cf76f9306..8a1fbef92 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -101,8 +101,9 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + - " VALUES(?, ?, ?, ?, ?, ?)"; + + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -110,6 +111,7 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden pst.setString(4, id); pst.setString(5, THIRD_PARTY.toString()); pst.setLong(6, timeJoined); + pst.setLong(7, timeJoined); }); } @@ -357,8 +359,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -366,6 +368,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC pst.setString(4, userInfo.id); pst.setString(5, THIRD_PARTY.toString()); pst.setLong(6, userInfo.timeJoined); + pst.setLong(7, userInfo.timeJoined); }); }