mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Merge branch 'tmp2' into ebean-new
This commit is contained in:
@@ -3,8 +3,8 @@ name: JDK 18-ea
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '30 6 * * *'
|
||||
schedule:
|
||||
- cron: '30 6 * * 1,3,5'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
name: MariaDB 10.6
|
||||
|
||||
on: [workflow_dispatch]
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '10 7 * * 1,4'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
name: SqlServer 2017 latest
|
||||
|
||||
on: [workflow_dispatch]
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '10 7 * * 2,5'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-config</artifactId>
|
||||
<version>1.5</version>
|
||||
<version>1.7</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package io.ebean.config.dbplatform.mariadb;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.mysql.BaseMySqlPlatform;
|
||||
|
||||
/**
|
||||
@@ -11,6 +15,14 @@ public class MariaDbPlatform extends BaseMySqlPlatform {
|
||||
public MariaDbPlatform() {
|
||||
super();
|
||||
this.platform = Platform.MARIADB;
|
||||
this.sequenceBatchMode = false;
|
||||
this.historySupport = new MariaDbHistorySupport();
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return new MariaDbSequence(be, ds, seqName, stepSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.config.dbplatform.mariadb;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.dbplatform.SequenceStepIdGenerator;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class MariaDbSequence extends SequenceStepIdGenerator {
|
||||
|
||||
private final String nextSql;
|
||||
|
||||
/**
|
||||
* Construct where batchSize is the sequence step size.
|
||||
*/
|
||||
public MariaDbSequence(BackgroundExecutor be, DataSource ds, String seqName, int stepSize) {
|
||||
super(be, ds, seqName, stepSize);
|
||||
this.nextSql = "select next value for " + seqName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql(int batchSize) {
|
||||
return nextSql;
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,20 @@ public class AnnotationUtil {
|
||||
return metaFindAllFor(element, Collections.singleton(annotationType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is annotated with an annotation of given type searching meta-annotations.
|
||||
*/
|
||||
public static boolean metaHas(AnnotatedElement element, Class<?> annotationType) {
|
||||
return !metaFindAll(element, annotationType).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the annotations of a given type searching meta-annotations.
|
||||
*/
|
||||
public static Set<Annotation> metaFindAll(AnnotatedElement element, Class<?> annotationType) {
|
||||
return metaFindAllFor(element, Collections.singleton(annotationType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the annotations for the filter searching meta-annotations.
|
||||
*/
|
||||
|
||||
@@ -333,6 +333,13 @@ public final class DefaultPersister implements Persister {
|
||||
* Recursively delete the bean. This calls back to the EbeanServer.
|
||||
*/
|
||||
private int deleteRecurse(EntityBean detailBean, Transaction t, DeleteMode deleteMode) {
|
||||
return deleteRequest(createDeleteCascade(detailBean, t, deleteMode.persistType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete without being a cascade.
|
||||
*/
|
||||
private int delete(EntityBean detailBean, Transaction t, DeleteMode deleteMode) {
|
||||
return deleteRequest(createDeleteRequest(detailBean, t, deleteMode.persistType()));
|
||||
}
|
||||
|
||||
@@ -560,7 +567,7 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteList(List<?> beanList, SpiTransaction t, DeleteMode deleteMode, boolean children) {
|
||||
private void deleteCascade(List<?> beanList, SpiTransaction t, DeleteMode deleteMode, boolean children) {
|
||||
if (children) {
|
||||
t.depth(-1);
|
||||
t.checkBatchEscalationOnCollection();
|
||||
@@ -604,7 +611,7 @@ public final class DefaultPersister implements Persister {
|
||||
for (Object id : ids) {
|
||||
EntityBean bean = descriptor.createEntityBean();
|
||||
descriptor.convertSetId(id, bean);
|
||||
int rowCount = deleteRecurse(bean, transaction, deleteMode);
|
||||
int rowCount = delete(bean, transaction, deleteMode);
|
||||
if (rowCount == -1) {
|
||||
total = -1;
|
||||
} else if (total != -1) {
|
||||
@@ -656,7 +663,7 @@ public final class DefaultPersister implements Persister {
|
||||
t.logSummary("-- DeleteById of " + descriptor.name() + " ids[" + idList + "] requires fetch of foreign key values");
|
||||
}
|
||||
List<?> beanList = server.findList(q, t);
|
||||
deleteList(beanList, t, deleteMode, false);
|
||||
deleteCascade(beanList, t, deleteMode, false);
|
||||
return beanList.size();
|
||||
|
||||
} else {
|
||||
@@ -994,6 +1001,8 @@ public final class DefaultPersister implements Persister {
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
|
||||
} else {
|
||||
// TODO: Review first checking if many property is loaded and using the loaded beans
|
||||
// ... and only using findIdsByParentId() when the many property isn't loaded
|
||||
// Delete recurse using the Id values of the children
|
||||
Object parentId = desc.getId(parentBean);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds, deleteMode.isHard());
|
||||
@@ -1017,7 +1026,7 @@ public final class DefaultPersister implements Persister {
|
||||
for (Object id : childIds) {
|
||||
refList.add(targetDesc.createReference(id, null));
|
||||
}
|
||||
deleteList(refList, t, deleteMode, true);
|
||||
deleteCascade(refList, t, deleteMode, true);
|
||||
} else {
|
||||
// perform delete by statement if possible
|
||||
delete(targetDesc, null, childIds, t, deleteMode);
|
||||
@@ -1157,6 +1166,10 @@ public final class DefaultPersister implements Persister {
|
||||
return createDeleteRequest(bean, t, type, Flags.ZERO);
|
||||
}
|
||||
|
||||
private <T> PersistRequestBean<T> createDeleteCascade(EntityBean bean, Transaction t, Type type) {
|
||||
return createDeleteRequest(bean, t, type, Flags.RECURSE);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <T> PersistRequestBean<T> createDeleteRequest(Object bean, Transaction t, PersistRequest.Type type, int flags) {
|
||||
BeanManager<T> mgr = beanManager(bean.getClass());
|
||||
@@ -1167,7 +1180,7 @@ public final class DefaultPersister implements Persister {
|
||||
type = Type.DELETE_SOFT;
|
||||
}
|
||||
|
||||
PersistRequestBean<T> request = new PersistRequestBean<>(server, (T)bean, null, mgr, (SpiTransaction) t, persistExecute, type, flags);
|
||||
PersistRequestBean<T> request = new PersistRequestBean<>(server, (T) bean, null, mgr, (SpiTransaction) t, persistExecute, type, flags);
|
||||
request.initForSoftDelete();
|
||||
return request;
|
||||
}
|
||||
@@ -1186,7 +1199,7 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> BeanManager<T> beanManager(Class<?> cls) {
|
||||
BeanManager<T> mgr = (BeanManager<T>)beanDescriptorManager.beanManager(cls);
|
||||
BeanManager<T> mgr = (BeanManager<T>) beanDescriptorManager.beanManager(cls);
|
||||
if (mgr == null) {
|
||||
throw new PersistenceException(errNotRegistered(cls));
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>3.6.3</version>
|
||||
<version>3.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+1
-1
@@ -226,7 +226,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.2.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebean.DB;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.tests.model.basic.TWithPreInsert;
|
||||
import org.tests.model.basic.TWithPreInsertChild;
|
||||
import org.tests.model.basic.event.TWithPreInsertPersistAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@@ -27,6 +28,7 @@ class TestPreInsertValidation extends BaseTestCase {
|
||||
assertThat(e.requestCascadeState()).isEqualTo(2);
|
||||
|
||||
TWithPreInsert e1 = DB.find(TWithPreInsert.class, e.getId());
|
||||
assert e1 != null;
|
||||
|
||||
e1.setTitle("Missus");
|
||||
DB.save(e1);
|
||||
@@ -54,11 +56,21 @@ class TestPreInsertValidation extends BaseTestCase {
|
||||
assert e1 != null;
|
||||
|
||||
e1.setTitle("ParentCascading-changed");
|
||||
e1.children().get(0).setName("Child0-changed");
|
||||
TWithPreInsertChild childBean = e1.children().get(0);
|
||||
childBean.setName("Child0-changed");
|
||||
DB.save(e1);
|
||||
|
||||
assertThat(e1.requestCascadeState()).isEqualTo(12);
|
||||
assertThat(e1.children().get(0).requestCascadeState()).isEqualTo(11);
|
||||
assertThat(childBean.requestCascadeState()).isEqualTo(11);
|
||||
|
||||
DB.delete(e1);
|
||||
|
||||
assertThat(e1.requestCascadeState()).isEqualTo(22);
|
||||
|
||||
// assert that isCascade() was true for the child bean
|
||||
assertThat(TWithPreInsertPersistAdapter.cascadeDelete).hasSize(1);
|
||||
String deleteCascade = TWithPreInsertPersistAdapter.cascadeDelete.get(0);
|
||||
assertThat(deleteCascade).isEqualTo("class org.tests.model.basic.TWithPreInsertChild:1");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -14,6 +14,8 @@ import org.tests.idkeys.db.GenKeySeqB;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
@@ -24,6 +26,7 @@ public class TestGeneratedKeys extends BaseTestCase {
|
||||
@ForPlatform(Platform.H2) // readSequenceValue is H2 specific
|
||||
public void testGenKeySeqA() throws SQLException {
|
||||
assumeTrue(idType() == IdType.SEQUENCE);
|
||||
|
||||
SpiEbeanServer server = spiEbeanServer();
|
||||
|
||||
try (Transaction tx = server.beginTransaction()) {
|
||||
@@ -69,20 +72,34 @@ public class TestGeneratedKeys extends BaseTestCase {
|
||||
}
|
||||
|
||||
private long readSequenceValue(Transaction tx, String sequence) throws SQLException {
|
||||
Statement stm = null;
|
||||
try {
|
||||
stm = tx.connection().createStatement();
|
||||
ResultSet rs = stm.executeQuery("select currval('" + sequence + "')");
|
||||
rs.next();
|
||||
String sql;
|
||||
switch (spiEbeanServer().databasePlatform().getPlatform().base()) {
|
||||
case H2 :
|
||||
sql = "select currval('" + sequence + "')";
|
||||
break;
|
||||
|
||||
case DB2 :
|
||||
sql = "values previous value for " + sequence;
|
||||
|
||||
break;
|
||||
case SQLSERVER :
|
||||
sql = "select current_value from sys.sequences where name = '" + sequence + "'";
|
||||
break;
|
||||
|
||||
case MARIADB :
|
||||
throw new UnsupportedOperationException("reading sequence value outside of the current connection is not supported. "
|
||||
+ "See https://mariadb.com/kb/en/previous-value-for-sequence_name/#description");
|
||||
|
||||
default :
|
||||
throw new UnsupportedOperationException("reading sequence value from "
|
||||
+ spiEbeanServer().databasePlatform().getPlatform()
|
||||
+ " is not supported.");
|
||||
|
||||
}
|
||||
try (Statement stm = tx.connection().createStatement()) {
|
||||
ResultSet rs = stm.executeQuery(sql);
|
||||
rs.next();
|
||||
return rs.getLong(1);
|
||||
} finally {
|
||||
if (stm != null) {
|
||||
try {
|
||||
stm.close();
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,5 +126,25 @@ public class TestGeneratedKeys extends BaseTestCase {
|
||||
assertNotNull(al.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForPlatform({Platform.H2, Platform.MARIADB, Platform.SQLSERVER, Platform.DB2})
|
||||
public void testGeneratedKeys() throws SQLException {
|
||||
assumeTrue(idType() == IdType.SEQUENCE);
|
||||
|
||||
SpiEbeanServer server = spiEbeanServer();
|
||||
List<Long> idList = new ArrayList<>(52);
|
||||
|
||||
try (Transaction tx = server.beginTransaction()) {
|
||||
// bigger than increment
|
||||
for (int i = 1; i < 52; i++) {
|
||||
GenKeySeqA gks = new GenKeySeqA();
|
||||
gks.setDescription("my description " + i);
|
||||
server.save(gks);
|
||||
assertFalse(idList.contains(gks.getId()));
|
||||
idList.add(gks.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static javax.persistence.CascadeType.PERSIST;
|
||||
import static javax.persistence.CascadeType.ALL;
|
||||
|
||||
@Entity
|
||||
public class TWithPreInsert implements TWithPreInsertCommon {
|
||||
@@ -20,7 +20,7 @@ public class TWithPreInsert implements TWithPreInsertCommon {
|
||||
|
||||
private String title;
|
||||
|
||||
@OneToMany(cascade = PERSIST)
|
||||
@OneToMany(cascade = ALL)
|
||||
private List<TWithPreInsertChild> children = new ArrayList<>();
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.tests.model.basic;
|
||||
|
||||
public interface TWithPreInsertCommon {
|
||||
|
||||
Integer getId();
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
+15
@@ -4,8 +4,13 @@ import io.ebean.event.BeanPersistAdapter;
|
||||
import io.ebean.event.BeanPersistRequest;
|
||||
import org.tests.model.basic.TWithPreInsertCommon;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TWithPreInsertPersistAdapter extends BeanPersistAdapter {
|
||||
|
||||
public static List<String> cascadeDelete = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
return TWithPreInsertCommon.class.isAssignableFrom(cls);
|
||||
@@ -32,4 +37,14 @@ public class TWithPreInsertPersistAdapter extends BeanPersistAdapter {
|
||||
return super.preUpdate(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preDelete(BeanPersistRequest<?> request) {
|
||||
TWithPreInsertCommon bean = (TWithPreInsertCommon) request.bean();
|
||||
if (request.isCascade()) {
|
||||
cascadeDelete.add(bean.getClass() + ":" + bean.getId());
|
||||
} else {
|
||||
bean.requestCascadeState(22);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.tests.model.basic.ResetBasicData;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
public class TestCsvReader extends TransactionalTestCase {
|
||||
|
||||
@@ -32,7 +33,7 @@ public class TestCsvReader extends TransactionalTestCase {
|
||||
// csvReader.addProperty("id");
|
||||
csvReader.addProperty("status");
|
||||
csvReader.addProperty("name");
|
||||
csvReader.addDateTime("anniversary", "dd-MMM-yyyy");
|
||||
csvReader.addDateTime("anniversary", "dd-MMM-yyyy", Locale.ENGLISH);
|
||||
csvReader.addProperty("billingAddress.line1");
|
||||
csvReader.addProperty("billingAddress.city");
|
||||
csvReader.addProperty("billingAddress.country.code");
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.tests.model.basic.Customer;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class TestCsvReaderWithCallback extends TransactionalTestCase {
|
||||
// csvReader.addProperty("id");
|
||||
csvReader.addProperty("status");
|
||||
csvReader.addProperty("name");
|
||||
csvReader.addDateTime("anniversary", "dd-MMM-yyyy");
|
||||
csvReader.addDateTime("anniversary", "dd-MMM-yyyy", Locale.ENGLISH);
|
||||
csvReader.addProperty("billingAddress.line1");
|
||||
csvReader.addProperty("billingAddress.city");
|
||||
// processor.addReference("billingAddress.country.code");
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<annotationProcessorPath>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>12.8.2</version>
|
||||
<version>12.13.0</version>
|
||||
</annotationProcessorPath>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.2.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -20,19 +20,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.2.9</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<path>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
||||
@@ -36,21 +36,21 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.13.2-SNAPSHOT</version>
|
||||
<version>12.14.1-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.2.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user