This is an automated email from the ASF dual-hosted git repository.
yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 74d0e1b091 [#11044] feat(idp-basic): Add built-in IdP storage services
(#11177)
74d0e1b091 is described below
commit 74d0e1b0914007b09f32be866b3f7253c87a65e1
Author: MaSai <[email protected]>
AuthorDate: Fri May 22 22:51:52 2026 +0800
[#11044] feat(idp-basic): Add built-in IdP storage services (#11177)
### What changes were proposed in this pull request?
This PR implements built-in IdP relational storage entirely in the
`idp-basic` plugin, without changes to the `core` module.
- Add `IdpUserMetaService` and `IdpGroupMetaService` for insert/delete
and user-group relation management.
- Add `IdpLegacyGarbageCollector` and `IdpLegacyGarbageCollectorManager`
for legacy metadata cleanup.
- Introduce plugin-local `NoSuchEntityException` and rename
`IdpUserPO.userName` to `username`.
- Register IdP mappers via `IdpBasicMapperPackageProvider` and package
the plugin in distribution (`copyLibAndConfigs`).
- Add mapper and service tests (`AbstractIdpMetaServiceTest`, legacy
timeline coverage, GC tests).
Fix: #11044
### Why are the changes needed?
Issue #11044 tracks built-in IdP storage for Gravitino. Keeping the
implementation in `idp-basic` avoids coupling core to IdP-specific
tables and lets the plugin own mapper registration, meta services, and
legacy garbage collection.
### Does this PR introduce _any_ user-facing change?
No. This is internal storage wiring for built-in IdP metadata. No public
API or configuration keys are added or changed in this PR.
### How was this patch tested?
- [x] `./gradlew :plugins:idp-basic:test -PskipITs
-PskipDockerTests=true` (H2)
- [x] `./gradlew :plugins:idp-basic:test -PskipITs
-PskipDockerTests=false` (H2, MySQL, PostgreSQL via Testcontainers)
---
build.gradle.kts | 4 +-
plugins/idp-basic/build.gradle.kts | 22 +++
.../gravitino/idp/exception/NotFoundException.java | 49 +++++
.../gravitino/idp/storage/IdpStorageBootstrap.java | 57 ++++++
.../idp/storage/gc/IdpLegacyGarbageCollector.java | 137 ++++++++++++++
.../idp/storage/mapper/IdpGroupMetaMapper.java | 10 +-
.../mapper/IdpGroupMetaSQLProviderFactory.java | 29 ++-
.../mapper/IdpUserGroupRelSQLProviderFactory.java | 20 +-
.../idp/storage/mapper/IdpUserMetaMapper.java | 12 +-
.../mapper/IdpUserMetaSQLProviderFactory.java | 32 ++--
.../provider/base/IdpGroupMetaBaseSQLProvider.java | 20 +-
.../provider/base/IdpUserMetaBaseSQLProvider.java | 19 +-
.../provider/h2/IdpUserGroupRelH2Provider.java | 10 +-
.../postgresql/IdpGroupMetaPostgreSQLProvider.java | 10 +-
.../IdpUserGroupRelPostgreSQLProvider.java | 10 +-
.../postgresql/IdpUserMetaPostgreSQLProvider.java | 10 +-
.../apache/gravitino/idp/storage/po/IdpUserPO.java | 2 +-
.../idp/storage/service/IdpGroupMetaService.java | 152 +++++++++++++++
.../idp/storage/service/IdpUserMetaService.java | 157 ++++++++++++++++
.../storage/gc/TestIdpLegacyGarbageCollector.java | 183 +++++++++++++++++++
.../storage/mapper/AbstractIdpMetaStorageTest.java | 8 +-
.../storage/mapper/TestIdpGroupMetaStorage.java | 44 +----
.../storage/mapper/TestIdpUserGroupRelStorage.java | 22 +--
.../idp/storage/mapper/TestIdpUserMetaStorage.java | 100 ++++------
.../gravitino/idp/storage/po/TestIdpUserPO.java | 20 +-
.../service/AbstractIdpMetaServiceTest.java | 203 +++++++++++++++++++++
.../storage/service/TestIdpGroupMetaService.java | 190 +++++++++++++++++++
.../storage/service/TestIdpUserMetaService.java | 156 ++++++++++++++++
28 files changed, 1446 insertions(+), 242 deletions(-)
diff --git a/build.gradle.kts b/build.gradle.kts
index 06113d3940..d630488f12 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -789,7 +789,8 @@ tasks {
":authorizations:copyLibAndConfig",
":iceberg:iceberg-rest-server:copyLibAndConfigs",
":lance:lance-rest-server:copyLibAndConfigs",
- ":maintenance:optimizer:copyLibAndConfigs"
+ ":maintenance:optimizer:copyLibAndConfigs",
+ ":plugins:idp-basic:copyLibAndConfigs"
)
if (!skipWeb) {
dependencies.add(":web:web:build")
@@ -1103,6 +1104,7 @@ tasks {
it.name != "integration-test" &&
it.parent?.name != "bundles" &&
it.parent?.name != "maintenance" &&
+ it.parent?.name != "plugins" &&
it.name != "mcp-server"
) {
from(it.configurations.runtimeClasspath) {
diff --git a/plugins/idp-basic/build.gradle.kts
b/plugins/idp-basic/build.gradle.kts
index 3229f452c8..a977c0d581 100644
--- a/plugins/idp-basic/build.gradle.kts
+++ b/plugins/idp-basic/build.gradle.kts
@@ -26,6 +26,7 @@ plugins {
dependencies {
annotationProcessor(libs.lombok)
+ implementation(project(":common"))
implementation(project(":core"))
implementation(libs.bcprov.jdk18on)
@@ -34,6 +35,7 @@ dependencies {
implementation(libs.mybatis)
compileOnly(libs.lombok)
+ compileOnly(libs.slf4j.api)
testImplementation(project(":common"))
testImplementation(project(":core"))
@@ -53,6 +55,26 @@ dependencies {
}
tasks {
+ val copyLibs by registering(Copy::class) {
+ dependsOn(jar)
+ from(layout.buildDirectory.dir("libs")) {
+ include("gravitino-idp-basic-*.jar")
+ exclude("*-javadoc.jar", "*-sources.jar")
+ }
+ // Argon2id password hashing; not bundled in the server distribution today.
+ from(configurations.runtimeClasspath) {
+ include("bcprov-jdk18on-*.jar")
+ }
+ into("$rootDir/distribution/package/libs")
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ }
+
+ register("copyLibAndConfigs", Copy::class) {
+ group = "gravitino distribution"
+ description = "Copy idp-basic plugin jar into distribution package libs"
+ dependsOn(copyLibs)
+ }
+
test {
environment("GRAVITINO_HOME", rootDir.path)
environment("GRAVITINO_TEST", "true")
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/exception/NotFoundException.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/exception/NotFoundException.java
new file mode 100644
index 0000000000..33e5852a78
--- /dev/null
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/exception/NotFoundException.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.exception;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.FormatString;
+
+/** This exception is thrown when a built-in IdP resource is not found. */
+public class NotFoundException extends RuntimeException {
+
+ /**
+ * Constructs a new NotFoundException with the given message.
+ *
+ * @param message the detail message
+ * @param args the arguments to the message
+ */
+ @FormatMethod
+ public NotFoundException(@FormatString String message, Object... args) {
+ super(String.format(message, args));
+ }
+
+ /**
+ * Constructs a new NotFoundException with the given message and cause.
+ *
+ * @param cause the cause of the exception
+ * @param message the detail message
+ * @param args the arguments to the message
+ */
+ @FormatMethod
+ public NotFoundException(Throwable cause, @FormatString String message,
Object... args) {
+ super(String.format(message, args), cause);
+ }
+}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/IdpStorageBootstrap.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/IdpStorageBootstrap.java
new file mode 100644
index 0000000000..9d2caedd78
--- /dev/null
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/IdpStorageBootstrap.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.idp.storage;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.idp.storage.gc.IdpLegacyGarbageCollector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * One-time initialization for built-in IdP storage components that are not
wired through core
+ * entity-store lifecycle.
+ *
+ * <p>Not invoked from {@link
+ *
org.apache.gravitino.idp.storage.mapper.provider.IdpBasicMapperPackageProvider}
in the current
+ * PR; a future change can wire this through server/plugin lifecycle.
+ */
+public final class IdpStorageBootstrap {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(IdpStorageBootstrap.class);
+
+ private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
+
+ private IdpStorageBootstrap() {}
+
+ /** Initializes IdP storage background tasks once per JVM. */
+ public static void initializeOnce() {
+ if (!INITIALIZED.compareAndSet(false, true)) {
+ return;
+ }
+
+ try {
+
IdpLegacyGarbageCollector.startScheduledCollector(GravitinoEnv.getInstance().config());
+ } catch (Exception e) {
+ INITIALIZED.set(false);
+ LOG.warn("Failed to initialize built-in IdP storage", e);
+ }
+ }
+}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/gc/IdpLegacyGarbageCollector.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/gc/IdpLegacyGarbageCollector.java
new file mode 100644
index 0000000000..0898fe7b30
--- /dev/null
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/gc/IdpLegacyGarbageCollector.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.gc;
+
+import static
org.apache.gravitino.Configs.GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT;
+import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.idp.storage.service.IdpGroupMetaService;
+import org.apache.gravitino.idp.storage.service.IdpUserMetaService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Periodically purges soft-deleted built-in IdP rows after {@link
+ * org.apache.gravitino.Configs#STORE_DELETE_AFTER_TIME}.
+ *
+ * <p>Unlike core {@link
org.apache.gravitino.storage.relational.RelationalGarbageCollector}, which
+ * is started from {@code RelationalEntityStore}, this plugin has no
entity-store lifecycle hook.
+ * {@link org.apache.gravitino.idp.storage.IdpStorageBootstrap} can start it
once server wiring is
+ * added; mapper registration alone does not start the collector.
+ */
+public final class IdpLegacyGarbageCollector {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(IdpLegacyGarbageCollector.class);
+
+ private static volatile IdpLegacyGarbageCollector instance;
+
+ private final long storeDeleteAfterTimeMillis;
+
+ private final ScheduledExecutorService garbageCollectorPool =
+ new ScheduledThreadPoolExecutor(
+ 2,
+ r -> {
+ Thread t = new Thread(r, "IdpBasic-Legacy-Garbage-Collector");
+ t.setDaemon(true);
+ return t;
+ },
+ new ThreadPoolExecutor.AbortPolicy());
+
+ /**
+ * Starts the scheduled legacy garbage collector. Idempotent; only the first
call takes effect.
+ *
+ * @param config Gravitino server configuration
+ */
+ public static void startScheduledCollector(Config config) {
+ if (instance != null) {
+ return;
+ }
+ synchronized (IdpLegacyGarbageCollector.class) {
+ if (instance != null) {
+ return;
+ }
+ IdpLegacyGarbageCollector collector = new
IdpLegacyGarbageCollector(config);
+ collector.start();
+ instance = collector;
+ }
+ }
+
+ public IdpLegacyGarbageCollector(Config config) {
+ storeDeleteAfterTimeMillis = config.get(STORE_DELETE_AFTER_TIME);
+ }
+
+ public void collectAndClean() {
+ long threadId = Thread.currentThread().getId();
+ LOG.debug("Thread {} start to collect garbage...", threadId);
+
+ try {
+ LOG.debug("Start to collect and delete legacy data by thread {}",
threadId);
+ long legacyTimeline = System.currentTimeMillis() -
storeDeleteAfterTimeMillis;
+ long deletedCount = Long.MAX_VALUE;
+ LOG.debug(
+ "Try to physically delete {} legacy data that has been marked
deleted before {}",
+ "idp_user",
+ legacyTimeline);
+ try {
+ while (deletedCount > 0) {
+ deletedCount =
+ IdpUserMetaService.getInstance()
+ .deleteUserMetasByLegacyTimeline(
+ legacyTimeline, GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT);
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to physically delete type of idp_user's legacy data:
", e);
+ }
+
+ deletedCount = Long.MAX_VALUE;
+ LOG.debug(
+ "Try to physically delete {} legacy data that has been marked
deleted before {}",
+ "idp_group",
+ legacyTimeline);
+ try {
+ while (deletedCount > 0) {
+ deletedCount =
+ IdpGroupMetaService.getInstance()
+ .deleteGroupMetasByLegacyTimeline(
+ legacyTimeline, GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT);
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to physically delete type of idp_group's legacy
data: ", e);
+ }
+ } catch (Exception e) {
+ LOG.error("Thread {} failed to collect and clean garbage.", threadId, e);
+ } finally {
+ LOG.debug("Thread {} finish to collect garbage.", threadId);
+ }
+ }
+
+ private void start() {
+ long dateTimelineMinute = storeDeleteAfterTimeMillis / 1000 / 60;
+
+ // We will collect garbage every 10 minutes at least. If the
dateTimelineMinute is larger than
+ // 100 minutes, we would collect garbage every dateTimelineMinute/10
minutes.
+ long frequency = Math.max(dateTimelineMinute / 10, 10);
+ garbageCollectorPool.scheduleAtFixedRate(this::collectAndClean, 5,
frequency, TimeUnit.MINUTES);
+ }
+}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaMapper.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaMapper.java
index 88419f8349..cc4a01e9f3 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaMapper.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaMapper.java
@@ -19,7 +19,6 @@
package org.apache.gravitino.idp.storage.mapper;
-import java.util.List;
import org.apache.gravitino.idp.storage.po.IdpGroupPO;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
@@ -41,18 +40,11 @@ public interface IdpGroupMetaMapper {
@SelectProvider(type = IdpGroupMetaSQLProviderFactory.class, method =
"selectIdpGroup")
IdpGroupPO selectIdpGroup(@Param("groupName") String groupName);
- /**
- * Selects active groups by name. An empty list returns all active groups;
pass null for an
- * explicit error.
- */
- @SelectProvider(type = IdpGroupMetaSQLProviderFactory.class, method =
"selectIdpGroups")
- List<IdpGroupPO> selectIdpGroups(@Param("groupNames") List<String>
groupNames);
-
@InsertProvider(type = IdpGroupMetaSQLProviderFactory.class, method =
"insertIdpGroup")
void insertIdpGroup(@Param("groupMeta") IdpGroupPO groupPO);
@UpdateProvider(type = IdpGroupMetaSQLProviderFactory.class, method =
"softDeleteIdpGroup")
- Integer softDeleteIdpGroup(@Param("groupId") Long groupId);
+ Integer softDeleteIdpGroup(@Param("groupName") String groupName);
@DeleteProvider(
type = IdpGroupMetaSQLProviderFactory.class,
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaSQLProviderFactory.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaSQLProviderFactory.java
index 4241ffc598..ee52b33c36 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaSQLProviderFactory.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpGroupMetaSQLProviderFactory.java
@@ -20,7 +20,6 @@
package org.apache.gravitino.idp.storage.mapper;
import com.google.common.collect.ImmutableMap;
-import java.util.List;
import java.util.Map;
import
org.apache.gravitino.idp.storage.mapper.provider.base.IdpGroupMetaBaseSQLProvider;
import
org.apache.gravitino.idp.storage.mapper.provider.h2.IdpGroupMetaH2Provider;
@@ -46,34 +45,30 @@ public class IdpGroupMetaSQLProviderFactory {
private IdpGroupMetaSQLProviderFactory() {}
- private static IdpGroupMetaBaseSQLProvider currentProvider() {
- return SQLProviderFactoryHelper.currentProvider(
- PROVIDER_MAP, IdpGroupMetaSQLProviderFactory.class);
- }
-
- static IdpGroupMetaBaseSQLProvider getProvider(String databaseId) {
- return SQLProviderFactoryHelper.getProvider(
- databaseId, PROVIDER_MAP, IdpGroupMetaSQLProviderFactory.class);
- }
-
public static String selectIdpGroup(@Param("groupName") String groupName) {
return currentProvider().selectIdpGroup(groupName);
}
- public static String selectIdpGroups(@Param("groupNames") List<String>
groupNames) {
- return currentProvider().selectIdpGroups(groupNames);
- }
-
public static String insertIdpGroup(@Param("groupMeta") IdpGroupPO groupPO) {
return currentProvider().insertIdpGroup(groupPO);
}
- public static String softDeleteIdpGroup(@Param("groupId") Long groupId) {
- return currentProvider().softDeleteIdpGroup(groupId);
+ public static String softDeleteIdpGroup(@Param("groupName") String
groupName) {
+ return currentProvider().softDeleteIdpGroup(groupName);
}
public static String deleteIdpGroupMetasByLegacyTimeline(
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
return
currentProvider().deleteIdpGroupMetasByLegacyTimeline(legacyTimeline, limit);
}
+
+ static IdpGroupMetaBaseSQLProvider getProvider(String databaseId) {
+ return SQLProviderFactoryHelper.getProvider(
+ databaseId, PROVIDER_MAP, IdpGroupMetaSQLProviderFactory.class);
+ }
+
+ private static IdpGroupMetaBaseSQLProvider currentProvider() {
+ return SQLProviderFactoryHelper.currentProvider(
+ PROVIDER_MAP, IdpGroupMetaSQLProviderFactory.class);
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserGroupRelSQLProviderFactory.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserGroupRelSQLProviderFactory.java
index b5f1fab08c..4343043e2c 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserGroupRelSQLProviderFactory.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserGroupRelSQLProviderFactory.java
@@ -46,16 +46,6 @@ public class IdpUserGroupRelSQLProviderFactory {
private IdpUserGroupRelSQLProviderFactory() {}
- private static IdpUserGroupRelBaseSQLProvider currentProvider() {
- return SQLProviderFactoryHelper.currentProvider(
- PROVIDER_MAP, IdpUserGroupRelSQLProviderFactory.class);
- }
-
- static IdpUserGroupRelBaseSQLProvider getProvider(String databaseId) {
- return SQLProviderFactoryHelper.getProvider(
- databaseId, PROVIDER_MAP, IdpUserGroupRelSQLProviderFactory.class);
- }
-
public static String selectGroupNamesByUsername(@Param("username") String
username) {
return currentProvider().selectGroupNamesByUsername(username);
}
@@ -85,4 +75,14 @@ public class IdpUserGroupRelSQLProviderFactory {
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
return
currentProvider().deleteIdpUserGroupRelMetasByLegacyTimeline(legacyTimeline,
limit);
}
+
+ static IdpUserGroupRelBaseSQLProvider getProvider(String databaseId) {
+ return SQLProviderFactoryHelper.getProvider(
+ databaseId, PROVIDER_MAP, IdpUserGroupRelSQLProviderFactory.class);
+ }
+
+ private static IdpUserGroupRelBaseSQLProvider currentProvider() {
+ return SQLProviderFactoryHelper.currentProvider(
+ PROVIDER_MAP, IdpUserGroupRelSQLProviderFactory.class);
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaMapper.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaMapper.java
index 41c3476030..aa61b70d21 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaMapper.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaMapper.java
@@ -41,22 +41,18 @@ public interface IdpUserMetaMapper {
@SelectProvider(type = IdpUserMetaSQLProviderFactory.class, method =
"selectIdpUser")
IdpUserPO selectIdpUser(@Param("username") String username);
- /**
- * Selects active users by name. An empty list returns all active users;
pass null for an explicit
- * error.
- */
- @SelectProvider(type = IdpUserMetaSQLProviderFactory.class, method =
"selectIdpUsers")
- List<IdpUserPO> selectIdpUsers(@Param("usernames") List<String> usernames);
+ @SelectProvider(type = IdpUserMetaSQLProviderFactory.class, method =
"selectIdpUsersByUsernames")
+ List<IdpUserPO> selectIdpUsersByUsernames(@Param("usernames") List<String>
usernames);
@InsertProvider(type = IdpUserMetaSQLProviderFactory.class, method =
"insertIdpUser")
void insertIdpUser(@Param("userMeta") IdpUserPO userPO);
@UpdateProvider(type = IdpUserMetaSQLProviderFactory.class, method =
"updateIdpUserPassword")
Integer updateIdpUserPassword(
- @Param("userId") Long userId, @Param("passwordHash") String
passwordHash);
+ @Param("username") String username, @Param("passwordHash") String
passwordHash);
@UpdateProvider(type = IdpUserMetaSQLProviderFactory.class, method =
"softDeleteIdpUser")
- Integer softDeleteIdpUser(@Param("userId") Long userId);
+ Integer softDeleteIdpUser(@Param("username") String username);
@DeleteProvider(
type = IdpUserMetaSQLProviderFactory.class,
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaSQLProviderFactory.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaSQLProviderFactory.java
index 46057906d9..99cf0c3b4a 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaSQLProviderFactory.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/IdpUserMetaSQLProviderFactory.java
@@ -45,22 +45,12 @@ public class IdpUserMetaSQLProviderFactory {
private IdpUserMetaSQLProviderFactory() {}
- private static IdpUserMetaBaseSQLProvider currentProvider() {
- return SQLProviderFactoryHelper.currentProvider(
- PROVIDER_MAP, IdpUserMetaSQLProviderFactory.class);
- }
-
- static IdpUserMetaBaseSQLProvider getProvider(String databaseId) {
- return SQLProviderFactoryHelper.getProvider(
- databaseId, PROVIDER_MAP, IdpUserMetaSQLProviderFactory.class);
- }
-
public static String selectIdpUser(@Param("username") String username) {
return currentProvider().selectIdpUser(username);
}
- public static String selectIdpUsers(@Param("usernames") List<String>
usernames) {
- return currentProvider().selectIdpUsers(usernames);
+ public static String selectIdpUsersByUsernames(@Param("usernames")
List<String> usernames) {
+ return currentProvider().selectIdpUsersByUsernames(usernames);
}
public static String insertIdpUser(@Param("userMeta") IdpUserPO userPO) {
@@ -68,16 +58,26 @@ public class IdpUserMetaSQLProviderFactory {
}
public static String updateIdpUserPassword(
- @Param("userId") Long userId, @Param("passwordHash") String
passwordHash) {
- return currentProvider().updateIdpUserPassword(userId, passwordHash);
+ @Param("username") String username, @Param("passwordHash") String
passwordHash) {
+ return currentProvider().updateIdpUserPassword(username, passwordHash);
}
- public static String softDeleteIdpUser(@Param("userId") Long userId) {
- return currentProvider().softDeleteIdpUser(userId);
+ public static String softDeleteIdpUser(@Param("username") String username) {
+ return currentProvider().softDeleteIdpUser(username);
}
public static String deleteIdpUserMetasByLegacyTimeline(
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
return
currentProvider().deleteIdpUserMetasByLegacyTimeline(legacyTimeline, limit);
}
+
+ static IdpUserMetaBaseSQLProvider getProvider(String databaseId) {
+ return SQLProviderFactoryHelper.getProvider(
+ databaseId, PROVIDER_MAP, IdpUserMetaSQLProviderFactory.class);
+ }
+
+ private static IdpUserMetaBaseSQLProvider currentProvider() {
+ return SQLProviderFactoryHelper.currentProvider(
+ PROVIDER_MAP, IdpUserMetaSQLProviderFactory.class);
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpGroupMetaBaseSQLProvider.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpGroupMetaBaseSQLProvider.java
index a99edacec2..878aa44f1c 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpGroupMetaBaseSQLProvider.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpGroupMetaBaseSQLProvider.java
@@ -19,7 +19,6 @@
package org.apache.gravitino.idp.storage.mapper.provider.base;
-import java.util.List;
import org.apache.gravitino.idp.storage.mapper.IdpGroupMetaMapper;
import org.apache.gravitino.idp.storage.po.IdpGroupPO;
import org.apache.ibatis.annotations.Param;
@@ -35,21 +34,6 @@ public class IdpGroupMetaBaseSQLProvider {
+ " WHERE group_name = #{groupName} AND deleted_at = 0";
}
- public String selectIdpGroups(@Param("groupNames") List<String> groupNames) {
- return "<script>"
- + "SELECT group_id as groupId, group_name as groupName,"
- + " current_version as currentVersion,"
- + " last_version as lastVersion, deleted_at as deletedAt"
- + " FROM "
- + IdpGroupMetaMapper.IDP_GROUP_TABLE_NAME
- + " WHERE deleted_at = 0 "
- + "<foreach collection='groupNames' item='groupName'"
- + " open='AND group_name IN (' separator=',' close=')'>"
- + "#{groupName}"
- + "</foreach>"
- + "</script>";
- }
-
public String insertIdpGroup(@Param("groupMeta") IdpGroupPO groupPO) {
return "INSERT INTO "
+ IdpGroupMetaMapper.IDP_GROUP_TABLE_NAME
@@ -63,12 +47,12 @@ public class IdpGroupMetaBaseSQLProvider {
+ " )";
}
- public String softDeleteIdpGroup(@Param("groupId") Long groupId) {
+ public String softDeleteIdpGroup(@Param("groupName") String groupName) {
return "UPDATE "
+ IdpGroupMetaMapper.IDP_GROUP_TABLE_NAME
+ " SET deleted_at = "
+ currentTimeMillisExpression()
- + " WHERE group_id = #{groupId} AND deleted_at = 0";
+ + " WHERE group_name = #{groupName} AND deleted_at = 0";
}
public String deleteIdpGroupMetasByLegacyTimeline(
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpUserMetaBaseSQLProvider.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpUserMetaBaseSQLProvider.java
index 5c026d1db5..d89f3f7757 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpUserMetaBaseSQLProvider.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/base/IdpUserMetaBaseSQLProvider.java
@@ -26,7 +26,7 @@ import org.apache.ibatis.annotations.Param;
public class IdpUserMetaBaseSQLProvider {
public String selectIdpUser(@Param("username") String username) {
- return "SELECT user_id as userId, user_name as userName, password_hash as
passwordHash,"
+ return "SELECT user_id as userId, user_name as username, password_hash as
passwordHash,"
+ " current_version as currentVersion,"
+ " last_version as lastVersion, deleted_at as deletedAt"
+ " FROM "
@@ -34,9 +34,9 @@ public class IdpUserMetaBaseSQLProvider {
+ " WHERE user_name = #{username} AND deleted_at = 0";
}
- public String selectIdpUsers(@Param("usernames") List<String> usernames) {
+ public String selectIdpUsersByUsernames(@Param("usernames") List<String>
usernames) {
return "<script>"
- + "SELECT user_id as userId, user_name as userName, password_hash as
passwordHash,"
+ + "SELECT user_id as userId, user_name as username, password_hash as
passwordHash,"
+ " current_version as currentVersion,"
+ " last_version as lastVersion, deleted_at as deletedAt"
+ " FROM "
@@ -55,7 +55,7 @@ public class IdpUserMetaBaseSQLProvider {
+ " (user_id, user_name, password_hash, current_version, last_version,
deleted_at)"
+ " VALUES ("
+ " #{userMeta.userId},"
- + " #{userMeta.userName},"
+ + " #{userMeta.username},"
+ " #{userMeta.passwordHash},"
+ " #{userMeta.currentVersion},"
+ " #{userMeta.lastVersion},"
@@ -64,20 +64,21 @@ public class IdpUserMetaBaseSQLProvider {
}
public String updateIdpUserPassword(
- @Param("userId") Long userId, @Param("passwordHash") String
passwordHash) {
+ @Param("username") String username, @Param("passwordHash") String
passwordHash) {
return "UPDATE "
+ IdpUserMetaMapper.IDP_USER_TABLE_NAME
+ " SET password_hash = #{passwordHash}"
- + " WHERE user_id = #{userId}"
- + " AND deleted_at = 0";
+ + " WHERE user_name = #{username}"
+ + " AND deleted_at = 0"
+ + " AND password_hash <> #{passwordHash}";
}
- public String softDeleteIdpUser(@Param("userId") Long userId) {
+ public String softDeleteIdpUser(@Param("username") String username) {
return "UPDATE "
+ IdpUserMetaMapper.IDP_USER_TABLE_NAME
+ " SET deleted_at = "
+ currentTimeMillisExpression()
- + " WHERE user_id = #{userId} AND deleted_at = 0";
+ + " WHERE user_name = #{username} AND deleted_at = 0";
}
public String deleteIdpUserMetasByLegacyTimeline(
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/h2/IdpUserGroupRelH2Provider.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/h2/IdpUserGroupRelH2Provider.java
index 1f9d1dc7aa..3803d96d12 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/h2/IdpUserGroupRelH2Provider.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/h2/IdpUserGroupRelH2Provider.java
@@ -29,11 +29,6 @@ import org.apache.ibatis.annotations.Param;
/** SQL provider for IdP user-group relation statements on H2 backends. */
public class IdpUserGroupRelH2Provider extends IdpUserGroupRelBaseSQLProvider {
- @Override
- protected String currentTimeMillisExpression() {
- return "DATEDIFF('MILLISECOND', TIMESTAMP '1970-01-01 00:00:00',
CURRENT_TIMESTAMP())";
- }
-
@Override
public String softDeleteRelations(
@Param("groupName") String groupName, @Param("usernames") List<String>
usernames) {
@@ -84,4 +79,9 @@ public class IdpUserGroupRelH2Provider extends
IdpUserGroupRelBaseSQLProvider {
+ " WHEN MATCHED THEN UPDATE SET r.deleted_at = "
+ currentTimeMillisExpression();
}
+
+ @Override
+ protected String currentTimeMillisExpression() {
+ return "DATEDIFF('MILLISECOND', TIMESTAMP '1970-01-01 00:00:00',
CURRENT_TIMESTAMP())";
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpGroupMetaPostgreSQLProvider.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpGroupMetaPostgreSQLProvider.java
index a808bc2472..3bed7f059d 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpGroupMetaPostgreSQLProvider.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpGroupMetaPostgreSQLProvider.java
@@ -25,11 +25,6 @@ import org.apache.ibatis.annotations.Param;
public class IdpGroupMetaPostgreSQLProvider extends
IdpGroupMetaBaseSQLProvider {
- @Override
- protected String currentTimeMillisExpression() {
- return "CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 AS BIGINT)";
- }
-
@Override
public String deleteIdpGroupMetasByLegacyTimeline(
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
@@ -39,4 +34,9 @@ public class IdpGroupMetaPostgreSQLProvider extends
IdpGroupMetaBaseSQLProvider
+ IdpGroupMetaMapper.IDP_GROUP_TABLE_NAME
+ " WHERE deleted_at > 0 AND deleted_at < #{legacyTimeline} LIMIT
#{limit})";
}
+
+ @Override
+ protected String currentTimeMillisExpression() {
+ return "CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 AS BIGINT)";
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserGroupRelPostgreSQLProvider.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserGroupRelPostgreSQLProvider.java
index 03cbeb6635..121aa0e6a3 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserGroupRelPostgreSQLProvider.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserGroupRelPostgreSQLProvider.java
@@ -28,11 +28,6 @@ import org.apache.ibatis.annotations.Param;
public class IdpUserGroupRelPostgreSQLProvider extends
IdpUserGroupRelBaseSQLProvider {
- @Override
- protected String currentTimeMillisExpression() {
- return "CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 AS BIGINT)";
- }
-
@Override
public String softDeleteRelations(
@Param("groupName") String groupName, @Param("usernames") List<String>
usernames) {
@@ -95,4 +90,9 @@ public class IdpUserGroupRelPostgreSQLProvider extends
IdpUserGroupRelBaseSQLPro
+ IdpUserGroupRelMapper.IDP_USER_GROUP_REL_TABLE_NAME
+ " WHERE deleted_at > 0 AND deleted_at < #{legacyTimeline} LIMIT
#{limit})";
}
+
+ @Override
+ protected String currentTimeMillisExpression() {
+ return "CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 AS BIGINT)";
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserMetaPostgreSQLProvider.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserMetaPostgreSQLProvider.java
index c41dd86589..9993b2e041 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserMetaPostgreSQLProvider.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/mapper/provider/postgresql/IdpUserMetaPostgreSQLProvider.java
@@ -25,11 +25,6 @@ import org.apache.ibatis.annotations.Param;
public class IdpUserMetaPostgreSQLProvider extends IdpUserMetaBaseSQLProvider {
- @Override
- protected String currentTimeMillisExpression() {
- return "CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 AS BIGINT)";
- }
-
@Override
public String deleteIdpUserMetasByLegacyTimeline(
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
@@ -39,4 +34,9 @@ public class IdpUserMetaPostgreSQLProvider extends
IdpUserMetaBaseSQLProvider {
+ IdpUserMetaMapper.IDP_USER_TABLE_NAME
+ " WHERE deleted_at > 0 AND deleted_at < #{legacyTimeline} LIMIT
#{limit})";
}
+
+ @Override
+ protected String currentTimeMillisExpression() {
+ return "CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000 AS BIGINT)";
+ }
}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/po/IdpUserPO.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/po/IdpUserPO.java
index cbe03a610b..2da098a481 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/po/IdpUserPO.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/po/IdpUserPO.java
@@ -34,7 +34,7 @@ import lombok.ToString;
@Builder(setterPrefix = "with")
public class IdpUserPO {
private Long userId;
- private String userName;
+ private String username;
private String passwordHash;
private Long currentVersion;
private Long lastVersion;
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
new file mode 100644
index 0000000000..ce51423775
--- /dev/null
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.service;
+
+import static
org.apache.gravitino.metrics.source.MetricsSource.GRAVITINO_RELATIONAL_STORE_METRIC_NAME;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.idp.exception.NotFoundException;
+import org.apache.gravitino.idp.storage.mapper.IdpGroupMetaMapper;
+import org.apache.gravitino.idp.storage.mapper.IdpUserGroupRelMapper;
+import org.apache.gravitino.idp.storage.po.IdpGroupPO;
+import org.apache.gravitino.idp.storage.po.IdpUserGroupRelPO;
+import org.apache.gravitino.metrics.Monitored;
+import org.apache.gravitino.storage.RandomIdGenerator;
+import org.apache.gravitino.storage.relational.utils.SessionUtils;
+
+/**
+ * The service class for built-in IdP group metadata. It provides the basic
database operations for
+ * group.
+ */
+public class IdpGroupMetaService {
+ private static final IdpGroupMetaService INSTANCE = new
IdpGroupMetaService();
+
+ private IdpGroupMetaService() {}
+
+ public static IdpGroupMetaService getInstance() {
+ return INSTANCE;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "getIdpGroupByName")
+ public IdpGroupPO getIdpGroupByName(String groupName) {
+ return getIdpGroupPOByName(groupName);
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "listUsernamesByGroupName")
+ public List<String> listUsernamesByGroupName(String groupName) {
+ return SessionUtils.getWithoutCommit(
+ IdpUserGroupRelMapper.class, mapper ->
mapper.selectUsernamesByGroupName(groupName));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "insertIdpGroup")
+ public void insertIdpGroup(IdpGroupPO groupPO) {
+ SessionUtils.doWithCommit(IdpGroupMetaMapper.class, mapper ->
mapper.insertIdpGroup(groupPO));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "deleteIdpGroup")
+ public boolean deleteIdpGroup(String groupName) {
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ SessionUtils.doWithoutCommit(
+ IdpUserGroupRelMapper.class,
+ mapper -> mapper.softDeleteRelationsByGroupName(groupName)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ IdpGroupMetaMapper.class, mapper ->
mapper.softDeleteIdpGroup(groupName)));
+ return true;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "addUsersToGroup")
+ public void addUsersToGroup(String groupName, List<String> usernames) {
+ IdpGroupPO group = getIdpGroupPOByName(groupName);
+ Map<String, Long> userIds =
+ IdpUserMetaService.getInstance().resolveUserIdsByUsernames(usernames);
+ List<IdpUserGroupRelPO> relations = new ArrayList<>(usernames.size());
+ for (String username : usernames) {
+ relations.add(newUserGroupRelation(group.getGroupId(),
userIds.get(username)));
+ }
+
+ SessionUtils.doWithCommit(
+ IdpUserGroupRelMapper.class, mapper ->
mapper.batchInsertRelations(relations));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "removeUsersFromGroup")
+ public int removeUsersFromGroup(String groupName, List<String> usernames) {
+ return SessionUtils.doWithCommitAndFetchResult(
+ IdpUserGroupRelMapper.class, mapper ->
mapper.softDeleteRelations(groupName, usernames));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "deleteIdpGroupMetasByLegacyTimeline")
+ public int deleteGroupMetasByLegacyTimeline(long legacyTimeline, int limit) {
+ int[] groupDeletedCount = new int[] {0};
+ int[] relDeletedCount = new int[] {0};
+
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ groupDeletedCount[0] =
+ SessionUtils.getWithoutCommit(
+ IdpGroupMetaMapper.class,
+ mapper ->
mapper.deleteIdpGroupMetasByLegacyTimeline(legacyTimeline, limit)),
+ () ->
+ relDeletedCount[0] =
+ SessionUtils.getWithoutCommit(
+ IdpUserGroupRelMapper.class,
+ mapper ->
+
mapper.deleteIdpUserGroupRelMetasByLegacyTimeline(legacyTimeline, limit)));
+
+ return groupDeletedCount[0] + relDeletedCount[0];
+ }
+
+ private static IdpUserGroupRelPO newUserGroupRelation(long groupId, long
userId) {
+ return IdpUserGroupRelPO.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withUserId(userId)
+ .withGroupId(groupId)
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+ }
+
+ private IdpGroupPO getIdpGroupPOByName(String groupName) {
+ IdpGroupPO groupPO =
+ SessionUtils.getWithoutCommit(
+ IdpGroupMetaMapper.class, mapper ->
mapper.selectIdpGroup(groupName));
+ if (groupPO == null) {
+ throw new NotFoundException("IdP group not found: %s", groupName);
+ }
+ return groupPO;
+ }
+}
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpUserMetaService.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpUserMetaService.java
new file mode 100644
index 0000000000..b4f62c74e3
--- /dev/null
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpUserMetaService.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.service;
+
+import static
org.apache.gravitino.metrics.source.MetricsSource.GRAVITINO_RELATIONAL_STORE_METRIC_NAME;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.idp.exception.NotFoundException;
+import org.apache.gravitino.idp.storage.mapper.IdpUserGroupRelMapper;
+import org.apache.gravitino.idp.storage.mapper.IdpUserMetaMapper;
+import org.apache.gravitino.idp.storage.po.IdpUserPO;
+import org.apache.gravitino.metrics.Monitored;
+import org.apache.gravitino.storage.relational.utils.SessionUtils;
+
+/**
+ * The service class for built-in IdP user metadata. It provides the basic
database operations for
+ * user.
+ */
+public class IdpUserMetaService {
+ private static final IdpUserMetaService INSTANCE = new IdpUserMetaService();
+
+ private IdpUserMetaService() {}
+
+ public static IdpUserMetaService getInstance() {
+ return INSTANCE;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "getIdpUserByUsername")
+ public IdpUserPO getIdpUserByUsername(String username) {
+ return getIdpUserPOByUsername(username);
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "listGroupNamesByUsername")
+ public List<String> listGroupNamesByUsername(String username) {
+ return SessionUtils.getWithoutCommit(
+ IdpUserGroupRelMapper.class, mapper ->
mapper.selectGroupNamesByUsername(username));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "insertIdpUser")
+ public void insertIdpUser(IdpUserPO userPO) {
+ SessionUtils.doWithCommit(IdpUserMetaMapper.class, mapper ->
mapper.insertIdpUser(userPO));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "deleteIdpUser")
+ public boolean deleteIdpUser(String username) {
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ SessionUtils.doWithoutCommit(
+ IdpUserGroupRelMapper.class,
+ mapper -> mapper.softDeleteRelationsByUsername(username)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ IdpUserMetaMapper.class, mapper ->
mapper.softDeleteIdpUser(username)));
+ return true;
+ }
+
+ /**
+ * Updates the password hash for an active user, following the core
relational meta update flow:
+ * load the current row, commit the update, and use the affected row count
as the success signal.
+ *
+ * @param username username of the user
+ * @param passwordHash new password hash to store
+ * @return {@code true} if the user exists and the password hash was updated
+ */
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "updateIdpUserPassword")
+ public boolean updateIdpUserPassword(String username, String passwordHash) {
+ Integer updated =
+ SessionUtils.doWithCommitAndFetchResult(
+ IdpUserMetaMapper.class,
+ mapper -> mapper.updateIdpUserPassword(username, passwordHash));
+ return updated > 0;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "deleteIdpUserMetasByLegacyTimeline")
+ public int deleteUserMetasByLegacyTimeline(long legacyTimeline, int limit) {
+ int[] userDeletedCount = new int[] {0};
+ int[] relDeletedCount = new int[] {0};
+
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ userDeletedCount[0] =
+ SessionUtils.getWithoutCommit(
+ IdpUserMetaMapper.class,
+ mapper ->
mapper.deleteIdpUserMetasByLegacyTimeline(legacyTimeline, limit)),
+ () ->
+ relDeletedCount[0] =
+ SessionUtils.getWithoutCommit(
+ IdpUserGroupRelMapper.class,
+ mapper ->
+
mapper.deleteIdpUserGroupRelMetasByLegacyTimeline(legacyTimeline, limit)));
+
+ return userDeletedCount[0] + relDeletedCount[0];
+ }
+
+ /**
+ * Resolves active user ids for the given usernames in one query. Throws if
any username is
+ * missing.
+ *
+ * @param usernames usernames to resolve
+ * @return username to user id map
+ */
+ Map<String, Long> resolveUserIdsByUsernames(List<String> usernames) {
+ List<IdpUserPO> users =
+ SessionUtils.getWithoutCommit(
+ IdpUserMetaMapper.class, mapper ->
mapper.selectIdpUsersByUsernames(usernames));
+ Map<String, Long> userIds = new HashMap<>(users.size());
+ for (IdpUserPO user : users) {
+ userIds.put(user.getUsername(), user.getUserId());
+ }
+ for (String username : usernames) {
+ if (!userIds.containsKey(username)) {
+ throw new NotFoundException("IdP user not found: %s", username);
+ }
+ }
+ return userIds;
+ }
+
+ private IdpUserPO getIdpUserPOByUsername(String username) {
+ IdpUserPO userPO =
+ SessionUtils.getWithoutCommit(
+ IdpUserMetaMapper.class, mapper -> mapper.selectIdpUser(username));
+ if (userPO == null) {
+ throw new NotFoundException("IdP user not found: %s", username);
+ }
+ return userPO;
+ }
+}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/gc/TestIdpLegacyGarbageCollector.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/gc/TestIdpLegacyGarbageCollector.java
new file mode 100644
index 0000000000..599954ed7a
--- /dev/null
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/gc/TestIdpLegacyGarbageCollector.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.gc;
+
+import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.idp.storage.mapper.AbstractIdpMetaStorageTest;
+import org.apache.gravitino.idp.storage.mapper.IdpGroupMetaMapper;
+import org.apache.gravitino.idp.storage.mapper.IdpUserGroupRelMapper;
+import org.apache.gravitino.idp.storage.mapper.IdpUserMetaMapper;
+import org.apache.gravitino.idp.storage.po.IdpGroupPO;
+import org.apache.gravitino.idp.storage.po.IdpUserGroupRelPO;
+import org.apache.gravitino.idp.storage.po.IdpUserPO;
+import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
+import org.apache.ibatis.session.SqlSession;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@Tag("gravitino-docker-test")
+class TestIdpLegacyGarbageCollector extends AbstractIdpMetaStorageTest {
+ private IdpUserMetaMapper idpUserMetaMapper;
+ private IdpGroupMetaMapper idpGroupMetaMapper;
+ private IdpUserGroupRelMapper idpUserGroupRelMapper;
+
+ @Override
+ protected void initializeMappers() {
+ idpUserMetaMapper = sharedSession.getMapper(IdpUserMetaMapper.class);
+ idpGroupMetaMapper = sharedSession.getMapper(IdpGroupMetaMapper.class);
+ idpUserGroupRelMapper =
sharedSession.getMapper(IdpUserGroupRelMapper.class);
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testCollectAndClean(String type) throws Exception {
+ init(type);
+ insertGroups();
+ insertUsers();
+ insertUserGroupRelations();
+
+ markAllSoftDeleted();
+ reopenSession();
+ assertNull(idpUserMetaMapper.selectIdpUser("user1"));
+ assertNull(idpGroupMetaMapper.selectIdpGroup("group1"));
+ assertEquals(4, countUsers());
+ assertEquals(2, countGroups());
+ assertEquals(8, countUserGroupRels());
+
+ Config config = new Config(false) {};
+ config.set(STORE_DELETE_AFTER_TIME, 600000L);
+
+ closeSession();
+ IdpLegacyGarbageCollector garbageCollector = new
IdpLegacyGarbageCollector(config);
+ garbageCollector.collectAndClean();
+ reopenSession();
+
+ assertEquals(0, countUsers());
+ assertEquals(0, countGroups());
+ assertEquals(0, countUserGroupRels());
+ }
+
+ private void reopenSession() {
+ closeSession();
+ sharedSession =
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ initializeMappers();
+ }
+
+ private void insertGroups() {
+ for (long index = 1L; index <= 2L; index++) {
+ idpGroupMetaMapper.insertIdpGroup(
+ IdpGroupPO.builder()
+ .withGroupId(index)
+ .withGroupName("group" + index)
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build());
+ }
+ }
+
+ private void insertUsers() {
+ for (long index = 1L; index <= 4L; index++) {
+ idpUserMetaMapper.insertIdpUser(
+ IdpUserPO.builder()
+ .withUserId(index)
+ .withUsername("user" + index)
+ .withPasswordHash("hash-" + index)
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build());
+ }
+ }
+
+ private void insertUserGroupRelations() {
+ idpUserGroupRelMapper.batchInsertRelations(
+ List.of(
+ userGroupRel(100L, "user1", "group1"),
+ userGroupRel(101L, "user2", "group1"),
+ userGroupRel(102L, "user3", "group1"),
+ userGroupRel(103L, "user4", "group1"),
+ userGroupRel(104L, "user1", "group2"),
+ userGroupRel(105L, "user2", "group2"),
+ userGroupRel(106L, "user3", "group2"),
+ userGroupRel(107L, "user4", "group2")));
+ }
+
+ private IdpUserGroupRelPO userGroupRel(long relationOrdinal, String
username, String groupName) {
+ IdpUserPO user = idpUserMetaMapper.selectIdpUser(username);
+ IdpGroupPO group = idpGroupMetaMapper.selectIdpGroup(groupName);
+ return IdpUserGroupRelPO.builder()
+ .withId(relationOrdinal)
+ .withUserId(user.getUserId())
+ .withGroupId(group.getGroupId())
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+ }
+
+ private void markAllSoftDeleted() throws SQLException {
+ try (SqlSession sqlSession =
+
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ Connection connection = sqlSession.getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute("UPDATE idp_user_meta SET deleted_at = 1 WHERE
deleted_at = 0");
+ statement.execute("UPDATE idp_group_meta SET deleted_at = 1 WHERE
deleted_at = 0");
+ statement.execute("UPDATE idp_user_group_rel SET deleted_at = 1 WHERE
deleted_at = 0");
+ }
+ }
+
+ private Integer countUsers() {
+ return countRows("idp_user_meta");
+ }
+
+ private Integer countGroups() {
+ return countRows("idp_group_meta");
+ }
+
+ private Integer countUserGroupRels() {
+ return countRows("idp_user_group_rel");
+ }
+
+ private Integer countRows(String tableName) {
+ int count = 0;
+ try (SqlSession sqlSession =
+
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ Connection connection = sqlSession.getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet rs = statement.executeQuery("SELECT count(*) FROM " +
tableName)) {
+ while (rs.next()) {
+ count = rs.getInt(1);
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("SQL execution failed", e);
+ }
+ return count;
+ }
+}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/AbstractIdpMetaStorageTest.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/AbstractIdpMetaStorageTest.java
index 047af1bb08..2a2806aaec 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/AbstractIdpMetaStorageTest.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/AbstractIdpMetaStorageTest.java
@@ -43,7 +43,7 @@ import
org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.AfterEach;
-abstract class AbstractIdpMetaStorageTest {
+public abstract class AbstractIdpMetaStorageTest {
private static final String H2_BACKEND = "h2";
private static final String MYSQL_BACKEND = "mysql";
private static final String POSTGRESQL_BACKEND = "postgresql";
@@ -51,12 +51,12 @@ abstract class AbstractIdpMetaStorageTest {
private static final TestDatabaseName POSTGRESQL_TEST_DATABASE =
TestDatabaseName.PG_JDBC_BACKEND;
protected JDBCBackend backend;
- protected SqlSession sharedSession;
+ public SqlSession sharedSession;
private Config config;
private Path h2Path;
- static Stream<String> storageProvider() {
+ public static Stream<String> storageProvider() {
return Stream.of(H2_BACKEND, MYSQL_BACKEND, POSTGRESQL_BACKEND);
}
@@ -77,7 +77,7 @@ abstract class AbstractIdpMetaStorageTest {
}
}
- protected void init(String type) throws IOException {
+ public void init(String type) throws IOException {
config = createBackendConfig(type);
backend = new JDBCBackend();
backend.initialize(config);
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpGroupMetaStorage.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpGroupMetaStorage.java
index 8d34c986ec..40433a46f9 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpGroupMetaStorage.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpGroupMetaStorage.java
@@ -20,15 +20,10 @@
package org.apache.gravitino.idp.storage.mapper;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
-import java.util.Comparator;
-import java.util.List;
import org.apache.gravitino.idp.storage.po.IdpGroupPO;
-import org.apache.ibatis.exceptions.PersistenceException;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -60,38 +55,6 @@ class TestIdpGroupMetaStorage extends
AbstractIdpMetaStorageTest {
assertNull(idpGroupMetaMapper.selectIdpGroup("unknown"));
}
- @ParameterizedTest
- @MethodSource("storageProvider")
- void testSelectIdpGroups(String type) throws IOException {
- init(type);
- IdpGroupPO firstGroup =
- IdpGroupPO.builder()
- .withGroupId(1L)
- .withGroupName("dev")
- .withCurrentVersion(1L)
- .withLastVersion(0L)
- .withDeletedAt(0L)
- .build();
- IdpGroupPO secondGroup =
- IdpGroupPO.builder()
- .withGroupId(2L)
- .withGroupName("ops")
- .withCurrentVersion(1L)
- .withLastVersion(0L)
- .withDeletedAt(0L)
- .build();
- idpGroupMetaMapper.insertIdpGroup(firstGroup);
- idpGroupMetaMapper.insertIdpGroup(secondGroup);
-
- List<IdpGroupPO> groups =
idpGroupMetaMapper.selectIdpGroups(List.of("ops", "dev"));
- groups.sort(Comparator.comparing(IdpGroupPO::getGroupId));
- assertIterableEquals(List.of(firstGroup, secondGroup), groups);
- List<IdpGroupPO> groupsWithEmptyFilter =
idpGroupMetaMapper.selectIdpGroups(List.of());
- groupsWithEmptyFilter.sort(Comparator.comparing(IdpGroupPO::getGroupId));
- assertIterableEquals(List.of(firstGroup, secondGroup),
groupsWithEmptyFilter);
- assertThrows(PersistenceException.class, () ->
idpGroupMetaMapper.selectIdpGroups(null));
- }
-
@ParameterizedTest
@MethodSource("storageProvider")
void testSelectIdpGroupIgnoresDeletedGroups(String type) throws IOException {
@@ -114,8 +77,7 @@ class TestIdpGroupMetaStorage extends
AbstractIdpMetaStorageTest {
.withDeletedAt(10L)
.build());
- assertIterableEquals(
- List.of(activeGroup),
idpGroupMetaMapper.selectIdpGroups(List.of("dev", "ops")));
+ assertEquals(activeGroup, idpGroupMetaMapper.selectIdpGroup("dev"));
assertNull(idpGroupMetaMapper.selectIdpGroup("ops"));
}
@@ -132,9 +94,9 @@ class TestIdpGroupMetaStorage extends
AbstractIdpMetaStorageTest {
.withDeletedAt(0L)
.build());
- assertEquals(1, idpGroupMetaMapper.softDeleteIdpGroup(1L));
+ assertEquals(1, idpGroupMetaMapper.softDeleteIdpGroup("dev"));
assertNull(idpGroupMetaMapper.selectIdpGroup("dev"));
- assertEquals(0, idpGroupMetaMapper.softDeleteIdpGroup(1L));
+ assertEquals(0, idpGroupMetaMapper.softDeleteIdpGroup("dev"));
assertEquals(1,
idpGroupMetaMapper.deleteIdpGroupMetasByLegacyTimeline(Long.MAX_VALUE, 10));
}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserGroupRelStorage.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserGroupRelStorage.java
index 3572eb8f41..80d0a47ede 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserGroupRelStorage.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserGroupRelStorage.java
@@ -67,7 +67,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -113,7 +113,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -122,7 +122,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(2L)
- .withUserName("bob")
+ .withUsername("bob")
.withPasswordHash("hash-b")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -168,7 +168,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -177,7 +177,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(2L)
- .withUserName("bob")
+ .withUsername("bob")
.withPasswordHash("hash-b")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -226,7 +226,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -235,7 +235,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(2L)
- .withUserName("bob")
+ .withUsername("bob")
.withPasswordHash("hash-b")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -280,7 +280,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(3L)
- .withUserName("carol")
+ .withUsername("carol")
.withPasswordHash("hash-c")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -333,7 +333,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("legacy-user")
+ .withUsername("legacy-user")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -342,7 +342,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(2L)
- .withUserName("new-user")
+ .withUsername("new-user")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -351,7 +351,7 @@ class TestIdpUserGroupRelStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(3L)
- .withUserName("active-user")
+ .withUsername("active-user")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(0L)
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserMetaStorage.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserMetaStorage.java
index d7901ad747..189d93aaca 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserMetaStorage.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/mapper/TestIdpUserMetaStorage.java
@@ -20,15 +20,11 @@
package org.apache.gravitino.idp.storage.mapper;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
-import java.util.Comparator;
import java.util.List;
import org.apache.gravitino.idp.storage.po.IdpUserPO;
-import org.apache.ibatis.exceptions.PersistenceException;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -49,7 +45,7 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
IdpUserPO firstUser =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -63,82 +59,51 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
@ParameterizedTest
@MethodSource("storageProvider")
- void testSelectIdpUsers(String type) throws IOException {
+ void testSelectIdpUsersByUsernames(String type) throws IOException {
init(type);
- IdpUserPO firstUser =
- IdpUserPO.builder()
- .withUserId(1L)
- .withUserName("alice")
- .withPasswordHash("hash-a")
- .withCurrentVersion(1L)
- .withLastVersion(0L)
- .withDeletedAt(0L)
- .build();
- IdpUserPO secondUser =
- IdpUserPO.builder()
- .withUserId(2L)
- .withUserName("bob")
- .withPasswordHash("hash-b")
- .withCurrentVersion(1L)
- .withLastVersion(0L)
- .withDeletedAt(0L)
- .build();
- idpUserMetaMapper.insertIdpUser(firstUser);
- idpUserMetaMapper.insertIdpUser(secondUser);
-
- List<IdpUserPO> users = idpUserMetaMapper.selectIdpUsers(List.of("bob",
"alice"));
- users.sort(Comparator.comparing(IdpUserPO::getUserId));
- assertIterableEquals(List.of(firstUser, secondUser), users);
- List<IdpUserPO> usersWithEmptyFilter =
idpUserMetaMapper.selectIdpUsers(List.of());
- usersWithEmptyFilter.sort(Comparator.comparing(IdpUserPO::getUserId));
- assertIterableEquals(List.of(firstUser, secondUser), usersWithEmptyFilter);
- assertThrows(PersistenceException.class, () ->
idpUserMetaMapper.selectIdpUsers(null));
- }
-
- @ParameterizedTest
- @MethodSource("storageProvider")
- void testSelectIdpUsersIgnoresDeletedUsers(String type) throws IOException {
- init(type);
- IdpUserPO activeUser =
+ idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
.withDeletedAt(0L)
- .build();
- idpUserMetaMapper.insertIdpUser(activeUser);
+ .build());
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(2L)
- .withUserName("bob")
+ .withUsername("bob")
.withPasswordHash("hash-b")
.withCurrentVersion(1L)
.withLastVersion(0L)
- .withDeletedAt(10L)
+ .withDeletedAt(0L)
.build());
- assertIterableEquals(
- List.of(activeUser), idpUserMetaMapper.selectIdpUsers(List.of("alice",
"bob")));
- assertNull(idpUserMetaMapper.selectIdpUser("bob"));
+ assertEquals(
+ List.of("alice", "bob"),
+ idpUserMetaMapper.selectIdpUsersByUsernames(List.of("alice", "bob",
"alice")).stream()
+ .map(IdpUserPO::getUsername)
+ .sorted()
+ .toList());
+ assertEquals(List.of(),
idpUserMetaMapper.selectIdpUsersByUsernames(List.of("unknown")));
}
@ParameterizedTest
@MethodSource("storageProvider")
void testUpdateIdpUserPassword(String type) throws IOException {
init(type);
- idpUserMetaMapper.insertIdpUser(
+ IdpUserPO oldUserPO =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
.withDeletedAt(0L)
- .build());
-
- assertEquals(1, idpUserMetaMapper.updateIdpUserPassword(1L, "hash-a-2"));
+ .build();
+ idpUserMetaMapper.insertIdpUser(oldUserPO);
+ assertEquals(1, idpUserMetaMapper.updateIdpUserPassword("alice",
"hash-a-2"));
assertEquals("hash-a-2",
idpUserMetaMapper.selectIdpUser("alice").getPasswordHash());
assertEquals(1L,
idpUserMetaMapper.selectIdpUser("alice").getCurrentVersion());
assertEquals(0L,
idpUserMetaMapper.selectIdpUser("alice").getLastVersion());
@@ -151,38 +116,39 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(3L)
.withLastVersion(2L)
.withDeletedAt(0L)
.build());
- assertEquals(1, idpUserMetaMapper.updateIdpUserPassword(1L, "hash-a-2"));
+ assertEquals(1, idpUserMetaMapper.updateIdpUserPassword("alice",
"hash-a-2"));
assertEquals("hash-a-2",
idpUserMetaMapper.selectIdpUser("alice").getPasswordHash());
assertEquals(3L,
idpUserMetaMapper.selectIdpUser("alice").getCurrentVersion());
assertEquals(2L,
idpUserMetaMapper.selectIdpUser("alice").getLastVersion());
+ assertEquals(0, idpUserMetaMapper.updateIdpUserPassword("alice",
"hash-a-2"));
}
@ParameterizedTest
@MethodSource("storageProvider")
void testSoftDeleteIdpUser(String type) throws IOException {
init(type);
- idpUserMetaMapper.insertIdpUser(
+ IdpUserPO oldUserPO =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash-a")
.withCurrentVersion(1L)
.withLastVersion(0L)
.withDeletedAt(0L)
- .build());
+ .build();
+ idpUserMetaMapper.insertIdpUser(oldUserPO);
- assertEquals(1, idpUserMetaMapper.softDeleteIdpUser(1L));
+ assertEquals(1, idpUserMetaMapper.softDeleteIdpUser("alice"));
assertNull(idpUserMetaMapper.selectIdpUser("alice"));
- assertIterableEquals(List.of(),
idpUserMetaMapper.selectIdpUsers(List.of("alice")));
- assertEquals(0, idpUserMetaMapper.softDeleteIdpUser(1L));
- assertEquals(0, idpUserMetaMapper.updateIdpUserPassword(1L, "hash-a-2"));
+ assertEquals(0, idpUserMetaMapper.softDeleteIdpUser("alice"));
+ assertEquals(0, idpUserMetaMapper.updateIdpUserPassword("alice",
"hash-a-2"));
assertEquals(1,
idpUserMetaMapper.deleteIdpUserMetasByLegacyTimeline(Long.MAX_VALUE, 10));
assertEquals(0,
idpUserMetaMapper.deleteIdpUserMetasByLegacyTimeline(Long.MAX_VALUE, 10));
}
@@ -194,7 +160,7 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("legacy-user")
+ .withUsername("legacy-user")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -203,7 +169,7 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(2L)
- .withUserName("new-user")
+ .withUsername("new-user")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -212,7 +178,7 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
idpUserMetaMapper.insertIdpUser(
IdpUserPO.builder()
.withUserId(3L)
- .withUserName("active-user")
+ .withUsername("active-user")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(0L)
@@ -223,7 +189,7 @@ class TestIdpUserMetaStorage extends
AbstractIdpMetaStorageTest {
assertEquals(0, idpUserMetaMapper.deleteIdpUserMetasByLegacyTimeline(20L,
10));
assertEquals(1, idpUserMetaMapper.deleteIdpUserMetasByLegacyTimeline(40L,
10));
assertEquals(0,
idpUserMetaMapper.deleteIdpUserMetasByLegacyTimeline(Long.MAX_VALUE, 10));
- assertEquals("active-user",
idpUserMetaMapper.selectIdpUser("active-user").getUserName());
+ assertEquals("active-user",
idpUserMetaMapper.selectIdpUser("active-user").getUsername());
assertNull(idpUserMetaMapper.selectIdpUser("legacy-user"));
assertNull(idpUserMetaMapper.selectIdpUser("new-user"));
}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/po/TestIdpUserPO.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/po/TestIdpUserPO.java
index 8bdfa0f16c..51b62170aa 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/po/TestIdpUserPO.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/po/TestIdpUserPO.java
@@ -28,7 +28,7 @@ public class TestIdpUserPO {
IdpUserPO userPO =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(1L)
@@ -36,7 +36,7 @@ public class TestIdpUserPO {
.build();
Assertions.assertEquals(1L, userPO.getUserId());
- Assertions.assertEquals("alice", userPO.getUserName());
+ Assertions.assertEquals("alice", userPO.getUsername());
Assertions.assertEquals("hash", userPO.getPasswordHash());
Assertions.assertEquals(1L, userPO.getCurrentVersion());
Assertions.assertEquals(1L, userPO.getLastVersion());
@@ -48,14 +48,14 @@ public class TestIdpUserPO {
IdpUserPO userPO =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withCurrentVersion(1L)
.withLastVersion(1L)
.withDeletedAt(0L)
.build();
Assertions.assertEquals(1L, userPO.getUserId());
- Assertions.assertEquals("alice", userPO.getUserName());
+ Assertions.assertEquals("alice", userPO.getUsername());
Assertions.assertNull(userPO.getPasswordHash());
}
@@ -64,7 +64,7 @@ public class TestIdpUserPO {
IdpUserPO userPO1 =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(1L)
@@ -74,7 +74,7 @@ public class TestIdpUserPO {
IdpUserPO userPO2 =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(1L)
@@ -90,16 +90,16 @@ public class TestIdpUserPO {
var builder =
IdpUserPO.builder()
.withUserId(1L)
- .withUserName("alice")
+ .withUsername("alice")
.withPasswordHash("hash")
.withCurrentVersion(1L)
.withLastVersion(1L)
.withDeletedAt(0L);
IdpUserPO firstUser = builder.build();
- IdpUserPO secondUser = builder.withUserName("bob").build();
+ IdpUserPO secondUser = builder.withUsername("bob").build();
- Assertions.assertEquals("alice", firstUser.getUserName());
- Assertions.assertEquals("bob", secondUser.getUserName());
+ Assertions.assertEquals("alice", firstUser.getUsername());
+ Assertions.assertEquals("bob", secondUser.getUsername());
}
}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/AbstractIdpMetaServiceTest.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/AbstractIdpMetaServiceTest.java
new file mode 100644
index 0000000000..f640f3ec28
--- /dev/null
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/AbstractIdpMetaServiceTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.service;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.time.Instant;
+import java.util.List;
+import org.apache.gravitino.idp.storage.mapper.AbstractIdpMetaStorageTest;
+import org.apache.gravitino.idp.storage.mapper.IdpGroupMetaMapper;
+import org.apache.gravitino.idp.storage.mapper.IdpUserGroupRelMapper;
+import org.apache.gravitino.idp.storage.mapper.IdpUserMetaMapper;
+import org.apache.gravitino.idp.storage.po.IdpGroupPO;
+import org.apache.gravitino.idp.storage.po.IdpUserGroupRelPO;
+import org.apache.gravitino.idp.storage.po.IdpUserPO;
+import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
+import org.apache.ibatis.session.SqlSession;
+
+/** Base class for IdP meta service tests. */
+public abstract class AbstractIdpMetaServiceTest extends
AbstractIdpMetaStorageTest {
+ protected static final long LEGACY_TIMELINE = Instant.now().toEpochMilli() +
1000;
+
+ protected IdpUserMetaMapper idpUserMetaMapper;
+ protected IdpGroupMetaMapper idpGroupMetaMapper;
+ protected IdpUserGroupRelMapper idpUserGroupRelMapper;
+
+ @Override
+ protected void initializeMappers() {
+ idpUserMetaMapper = sharedSession.getMapper(IdpUserMetaMapper.class);
+ idpGroupMetaMapper = sharedSession.getMapper(IdpGroupMetaMapper.class);
+ idpUserGroupRelMapper =
sharedSession.getMapper(IdpUserGroupRelMapper.class);
+ }
+
+ /** Reopens the shared session after service-layer commits or direct SQL
updates. */
+ protected void refreshSession() {
+ closeSession();
+ sharedSession =
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ initializeMappers();
+ }
+
+ /**
+ * Runs a service call outside the shared session (services use their own
sessions), then
+ * refreshes mapper state for assertions.
+ */
+ protected void runServiceCall(ServiceCall serviceCall) throws IOException {
+ closeSession();
+ serviceCall.run();
+ refreshSession();
+ }
+
+ @FunctionalInterface
+ protected interface ServiceCall {
+ void run() throws IOException;
+ }
+
+ /** Asserts the service call fails with a runtime exception (e.g. unique
constraint violation). */
+ protected void assertThrowsRuntimeException(ServiceCall insertCall) throws
IOException {
+ closeSession();
+ try {
+ assertThrows(RuntimeException.class, insertCall::run);
+ } finally {
+ refreshSession();
+ }
+ }
+
+ protected void insertGroups(long count) {
+ for (long index = 1L; index <= count; index++) {
+ idpGroupMetaMapper.insertIdpGroup(
+ IdpGroupPO.builder()
+ .withGroupId(index)
+ .withGroupName("group" + index)
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build());
+ }
+ }
+
+ protected void insertUsers(long count) {
+ for (long index = 1L; index <= count; index++) {
+ idpUserMetaMapper.insertIdpUser(
+ IdpUserPO.builder()
+ .withUserId(index)
+ .withUsername("user" + index)
+ .withPasswordHash("hash-" + index)
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build());
+ }
+ }
+
+ protected void insertDefaultUserGroupRelations() {
+ idpUserGroupRelMapper.batchInsertRelations(
+ List.of(
+ userGroupRel(100L, "user1", "group1"),
+ userGroupRel(101L, "user2", "group1"),
+ userGroupRel(102L, "user3", "group1"),
+ userGroupRel(103L, "user4", "group1"),
+ userGroupRel(104L, "user1", "group2"),
+ userGroupRel(105L, "user2", "group2"),
+ userGroupRel(106L, "user3", "group2"),
+ userGroupRel(107L, "user4", "group2")));
+ }
+
+ protected void insertGroupUserGroupRelations() {
+ idpUserGroupRelMapper.batchInsertRelations(
+ List.of(
+ userGroupRel(100L, "user1", "group1"),
+ userGroupRel(101L, "user2", "group1"),
+ userGroupRel(102L, "user1", "group2"),
+ userGroupRel(103L, "user2", "group2"),
+ userGroupRel(104L, "user3", "group3"),
+ userGroupRel(105L, "user4", "group3"),
+ userGroupRel(106L, "user3", "group4"),
+ userGroupRel(107L, "user4", "group4")));
+ }
+
+ protected IdpUserGroupRelPO userGroupRel(
+ long relationOrdinal, String username, String groupName) {
+ IdpUserPO user = idpUserMetaMapper.selectIdpUser(username);
+ IdpGroupPO group = idpGroupMetaMapper.selectIdpGroup(groupName);
+ return IdpUserGroupRelPO.builder()
+ .withId(relationOrdinal)
+ .withUserId(user.getUserId())
+ .withGroupId(group.getGroupId())
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+ }
+
+ protected void softDeleteAllUsersAndRelations() throws SQLException {
+ executeUpdate(
+ "UPDATE idp_user_meta SET deleted_at = 1 WHERE deleted_at = 0",
+ "UPDATE idp_user_group_rel SET deleted_at = 1 WHERE deleted_at = 0");
+ }
+
+ protected void softDeleteAllGroups() throws SQLException {
+ executeUpdate(
+ "UPDATE idp_group_meta SET deleted_at = 1 WHERE deleted_at = 0",
+ "UPDATE idp_user_group_rel SET deleted_at = 1 WHERE deleted_at = 0");
+ }
+
+ protected int countUsers() {
+ return countTableRows("idp_user_meta");
+ }
+
+ protected int countGroups() {
+ return countTableRows("idp_group_meta");
+ }
+
+ protected int countUserGroupRels() {
+ return countTableRows("idp_user_group_rel");
+ }
+
+ private void executeUpdate(String... sqlStatements) throws SQLException {
+ try (SqlSession sqlSession =
+
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ Connection connection = sqlSession.getConnection();
+ Statement statement = connection.createStatement()) {
+ for (String sql : sqlStatements) {
+ statement.execute(sql);
+ }
+ }
+ }
+
+ private int countTableRows(String tableName) {
+ try (SqlSession sqlSession =
+
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+ Connection connection = sqlSession.getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet rs = statement.executeQuery("SELECT count(*) FROM " +
tableName)) {
+ if (rs.next()) {
+ return rs.getInt(1);
+ }
+ throw new IllegalStateException("Count query returned no rows for table:
" + tableName);
+ } catch (SQLException e) {
+ throw new RuntimeException("SQL execution failed", e);
+ }
+ }
+}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
new file mode 100644
index 0000000000..4b7f8e1f5b
--- /dev/null
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.gravitino.idp.exception.NotFoundException;
+import org.apache.gravitino.idp.storage.po.IdpGroupPO;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@Tag("gravitino-docker-test")
+class TestIdpGroupMetaService extends AbstractIdpMetaServiceTest {
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testGetIdpGroupByName(String type) throws IOException {
+ init(type);
+ insertGroups(1);
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+
+ assertThrows(NotFoundException.class, () ->
groupMetaService.getIdpGroupByName("missing"));
+ assertEquals("group1",
groupMetaService.getIdpGroupByName("group1").getGroupName());
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testInsertIdpGroup(String type) throws IOException {
+ init(type);
+ insertUsers(4);
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+
+ IdpGroupPO group1 =
+ IdpGroupPO.builder()
+ .withGroupId(1L)
+ .withGroupName("group1")
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+
+ assertThrows(NotFoundException.class, () ->
groupMetaService.getIdpGroupByName("group1"));
+ runServiceCall(() -> groupMetaService.insertIdpGroup(group1));
+ runServiceCall(() -> groupMetaService.addUsersToGroup("group1",
List.of("user1")));
+ assertEquals("group1",
groupMetaService.getIdpGroupByName("group1").getGroupName());
+ assertIterableEquals(List.of("user1"),
groupMetaService.listUsernamesByGroupName("group1"));
+
+ IdpGroupPO duplicateGroup =
+ IdpGroupPO.builder()
+ .withGroupId(2L)
+ .withGroupName("group1")
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+ assertThrowsRuntimeException(() ->
groupMetaService.insertIdpGroup(duplicateGroup));
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testDeleteIdpGroupCascadesMemberships(String type) throws IOException {
+ init(type);
+ insertUsers(4);
+ insertGroups(4);
+ insertGroupUserGroupRelations();
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+
+ runServiceCall(() ->
assertTrue(groupMetaService.deleteIdpGroup("group1")));
+ assertNull(idpGroupMetaMapper.selectIdpGroup("group1"));
+ assertEquals(4, countGroups());
+ assertEquals(8, countUserGroupRels());
+ assertIterableEquals(List.of(),
groupMetaService.listUsernamesByGroupName("group1"));
+ assertEquals("group2",
idpGroupMetaMapper.selectIdpGroup("group2").getGroupName());
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testAddAndRemoveUsersFromGroup(String type) throws IOException {
+ init(type);
+ insertUsers(4);
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+
+ IdpGroupPO group1 =
+ IdpGroupPO.builder()
+ .withGroupId(1L)
+ .withGroupName("engineering")
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+ runServiceCall(() -> groupMetaService.insertIdpGroup(group1));
+
+ runServiceCall(
+ () -> groupMetaService.addUsersToGroup("engineering", List.of("user1",
"user2")));
+ assertIterableEquals(
+ List.of("user1", "user2"),
groupMetaService.listUsernamesByGroupName("engineering"));
+
+ runServiceCall(
+ () ->
+ assertEquals(
+ 1, groupMetaService.removeUsersFromGroup("engineering",
List.of("user1"))));
+ assertIterableEquals(
+ List.of("user2"),
groupMetaService.listUsernamesByGroupName("engineering"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testAddUsersToGroupThrowsWhenUserMissing(String type) throws
IOException {
+ init(type);
+ insertUsers(1);
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+ runServiceCall(
+ () ->
+ groupMetaService.insertIdpGroup(
+ IdpGroupPO.builder()
+ .withGroupId(1L)
+ .withGroupName("engineering")
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build()));
+
+ assertThrows(
+ NotFoundException.class,
+ () ->
+ runServiceCall(
+ () -> groupMetaService.addUsersToGroup("engineering",
List.of("missing-user"))));
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testDeleteGroupMetasByLegacyTimeline(String type) throws Exception {
+ init(type);
+ insertUsers(4);
+ insertGroups(4);
+ insertGroupUserGroupRelations();
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+
+ assertEquals(0,
groupMetaService.deleteGroupMetasByLegacyTimeline(LEGACY_TIMELINE, 4));
+ assertEquals("group1",
idpGroupMetaMapper.selectIdpGroup("group1").getGroupName());
+ assertEquals(4, countGroups());
+ assertEquals(8, countUserGroupRels());
+
+ softDeleteAllGroups();
+ refreshSession();
+ assertNull(idpGroupMetaMapper.selectIdpGroup("group1"));
+ assertEquals(4, countGroups());
+ assertEquals(8, countUserGroupRels());
+
+ assertEquals(6,
groupMetaService.deleteGroupMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ refreshSession();
+ assertEquals(1, countGroups());
+ assertEquals(5, countUserGroupRels());
+
+ assertEquals(4,
groupMetaService.deleteGroupMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ refreshSession();
+ assertEquals(0, countGroups());
+ assertEquals(2, countUserGroupRels());
+
+ assertEquals(2,
groupMetaService.deleteGroupMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ refreshSession();
+ assertEquals(0, countGroups());
+ assertEquals(0, countUserGroupRels());
+
+ assertEquals(0,
groupMetaService.deleteGroupMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ }
+}
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpUserMetaService.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpUserMetaService.java
new file mode 100644
index 0000000000..dbc71999d5
--- /dev/null
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpUserMetaService.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp.storage.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.gravitino.idp.exception.NotFoundException;
+import org.apache.gravitino.idp.storage.po.IdpUserPO;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@Tag("gravitino-docker-test")
+class TestIdpUserMetaService extends AbstractIdpMetaServiceTest {
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testGetIdpUserByUsername(String type) throws IOException {
+ init(type);
+ insertUsers(1);
+ IdpUserMetaService userMetaService = IdpUserMetaService.getInstance();
+
+ assertThrows(NotFoundException.class, () ->
userMetaService.getIdpUserByUsername("missing"));
+ assertEquals("user1",
userMetaService.getIdpUserByUsername("user1").getUsername());
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testInsertIdpUser(String type) throws IOException {
+ init(type);
+ insertGroups(2);
+ IdpUserMetaService userMetaService = IdpUserMetaService.getInstance();
+ IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
+
+ IdpUserPO user1 =
+ IdpUserPO.builder()
+ .withUserId(1L)
+ .withUsername("user1")
+ .withPasswordHash("hash-1")
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+
+ assertThrows(NotFoundException.class, () ->
userMetaService.getIdpUserByUsername("user1"));
+ runServiceCall(() -> userMetaService.insertIdpUser(user1));
+ runServiceCall(() -> groupMetaService.addUsersToGroup("group1",
List.of("user1")));
+ assertEquals("user1",
userMetaService.getIdpUserByUsername("user1").getUsername());
+ assertIterableEquals(List.of("group1"),
userMetaService.listGroupNamesByUsername("user1"));
+
+ IdpUserPO duplicateUser =
+ IdpUserPO.builder()
+ .withUserId(2L)
+ .withUsername("user1")
+ .withPasswordHash("hash-2")
+ .withCurrentVersion(1L)
+ .withLastVersion(0L)
+ .withDeletedAt(0L)
+ .build();
+ assertThrowsRuntimeException(() ->
userMetaService.insertIdpUser(duplicateUser));
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testUpdateIdpUserPassword(String type) throws IOException {
+ init(type);
+ insertUsers(1);
+ IdpUserMetaService userMetaService = IdpUserMetaService.getInstance();
+
+ runServiceCall(() ->
assertFalse(userMetaService.updateIdpUserPassword("missing", "hash-2")));
+
+ runServiceCall(() ->
assertTrue(userMetaService.updateIdpUserPassword("user1", "hash-2")));
+ assertEquals("hash-2",
userMetaService.getIdpUserByUsername("user1").getPasswordHash());
+ assertEquals(1L,
userMetaService.getIdpUserByUsername("user1").getCurrentVersion());
+
+ runServiceCall(() ->
assertFalse(userMetaService.updateIdpUserPassword("user1", "hash-2")));
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testDeleteIdpUser(String type) throws IOException {
+ init(type);
+ insertGroups(2);
+ insertUsers(4);
+ insertDefaultUserGroupRelations();
+ IdpUserMetaService userMetaService = IdpUserMetaService.getInstance();
+
+ runServiceCall(() -> assertTrue(userMetaService.deleteIdpUser("user1")));
+ assertNull(idpUserMetaMapper.selectIdpUser("user1"));
+ assertEquals(4, countUsers());
+ assertEquals(8, countUserGroupRels());
+ assertIterableEquals(List.of(),
userMetaService.listGroupNamesByUsername("user1"));
+ assertEquals("user2",
idpUserMetaMapper.selectIdpUser("user2").getUsername());
+ }
+
+ @ParameterizedTest
+ @MethodSource("storageProvider")
+ void testDeleteUserMetasByLegacyTimeline(String type) throws Exception {
+ init(type);
+ insertGroups(2);
+ insertUsers(4);
+ insertDefaultUserGroupRelations();
+ IdpUserMetaService userMetaService = IdpUserMetaService.getInstance();
+
+ assertEquals(0,
userMetaService.deleteUserMetasByLegacyTimeline(LEGACY_TIMELINE, 4));
+ assertEquals("user1",
idpUserMetaMapper.selectIdpUser("user1").getUsername());
+ assertEquals(4, countUsers());
+ assertEquals(8, countUserGroupRels());
+
+ softDeleteAllUsersAndRelations();
+ refreshSession();
+ assertNull(idpUserMetaMapper.selectIdpUser("user1"));
+ assertEquals(4, countUsers());
+ assertEquals(8, countUserGroupRels());
+
+ assertEquals(6,
userMetaService.deleteUserMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ refreshSession();
+ assertEquals(1, countUsers());
+ assertEquals(5, countUserGroupRels());
+
+ assertEquals(4,
userMetaService.deleteUserMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ refreshSession();
+ assertEquals(0, countUsers());
+ assertEquals(2, countUserGroupRels());
+
+ assertEquals(2,
userMetaService.deleteUserMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ refreshSession();
+ assertEquals(0, countUsers());
+ assertEquals(0, countUserGroupRels());
+
+ assertEquals(0,
userMetaService.deleteUserMetasByLegacyTimeline(LEGACY_TIMELINE, 3));
+ }
+}