mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#323 - DDL Generation support for foreign key cascade options
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class Oracle10DdlTest {
|
||||
|
||||
Oracle10Ddl create() {
|
||||
return new Oracle10Ddl(new OraclePlatform());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendForeignKeyOnDelete_expectEmtpy_when_nullRestrictSetDefault() {
|
||||
|
||||
Oracle10Ddl oracle = create();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
oracle.appendForeignKeyOnDelete(sb, oracle.withDefault(null));
|
||||
assertThat(sb.toString()).isEqualTo("");
|
||||
|
||||
sb = new StringBuilder();
|
||||
oracle.appendForeignKeyOnDelete(sb, ConstraintMode.RESTRICT);
|
||||
assertThat(sb.toString()).isEqualTo("");
|
||||
|
||||
|
||||
sb = new StringBuilder();
|
||||
oracle.appendForeignKeyOnDelete(sb, ConstraintMode.SET_DEFAULT);
|
||||
assertThat(sb.toString()).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendForeignKeyOnDelete_setNull() {
|
||||
|
||||
Oracle10Ddl oracle = create();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
oracle.appendForeignKeyOnDelete(sb, ConstraintMode.SET_NULL);
|
||||
assertThat(sb.toString()).isEqualTo(" on delete set null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendForeignKeyOnDelete_cascade() {
|
||||
|
||||
Oracle10Ddl oracle = create();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
oracle.appendForeignKeyOnDelete(sb, ConstraintMode.CASCADE);
|
||||
assertThat(sb.toString()).isEqualTo(" on delete cascade");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,12 @@ public class TestOrderedList extends BaseTestCase {
|
||||
List<String> sql = LoggedSqlCollector.current();
|
||||
assertThat(sql.size()).isGreaterThan(1);
|
||||
assertThat(sql.get(0)).contains("insert into om_ordered_master");
|
||||
assertThat(sql.get(1)).contains("insert into om_ordered_detail (name, version, sort_order, master_id) values (?,?,?,?)");
|
||||
boolean hasId = sql.get(1).contains(" (id, name");
|
||||
if (hasId) {
|
||||
assertThat(sql.get(1)).contains("insert into om_ordered_detail (id, name, version, sort_order, master_id) values (?,?,?,?,?)");
|
||||
} else {
|
||||
assertThat(sql.get(1)).contains("insert into om_ordered_detail (name, version, sort_order, master_id) values (?,?,?,?)");
|
||||
}
|
||||
|
||||
// update without any changes
|
||||
Ebean.save(master);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.tests.ddl;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.annotation.DbForeignKey;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class DfkCascade {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
|
||||
String name;
|
||||
|
||||
@ManyToOne
|
||||
@DbForeignKey(onDelete = ConstraintMode.CASCADE)
|
||||
DfkCascadeOne one;
|
||||
|
||||
public DfkCascade(String name, DfkCascadeOne one) {
|
||||
this.name = name;
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DfkCascadeOne getOne() {
|
||||
return one;
|
||||
}
|
||||
|
||||
public void setOne(DfkCascadeOne one) {
|
||||
this.one = one;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.tests.ddl;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
public class DfkCascadeOne {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
|
||||
String name;
|
||||
|
||||
@OneToMany(mappedBy = "one", cascade = CascadeType.ALL)
|
||||
List<DfkCascade> details;
|
||||
|
||||
public DfkCascadeOne(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<DfkCascade> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(List<DfkCascade> details) {
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.tests.ddl;
|
||||
|
||||
import io.ebean.annotation.DbForeignKey;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class DfkNone {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
|
||||
String name;
|
||||
|
||||
@ManyToOne
|
||||
@DbForeignKey(noConstraint = true)
|
||||
DfkOne one;
|
||||
|
||||
public DfkNone(String name, DfkOne one) {
|
||||
this.name = name;
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DfkOne getOne() {
|
||||
return one;
|
||||
}
|
||||
|
||||
public void setOne(DfkOne one) {
|
||||
this.one = one;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.tests.ddl;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class DfkOne {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
|
||||
String name;
|
||||
|
||||
public DfkOne(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.tests.ddl;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.annotation.DbForeignKey;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class DfkSetNull {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
|
||||
String name;
|
||||
|
||||
@ManyToOne
|
||||
@DbForeignKey(onDelete = ConstraintMode.SET_NULL)
|
||||
DfkOne one;
|
||||
|
||||
public DfkSetNull(String name, DfkOne one) {
|
||||
this.name = name;
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DfkOne getOne() {
|
||||
return one;
|
||||
}
|
||||
|
||||
public void setOne(DfkOne one) {
|
||||
this.one = one;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.tests.ddl;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestForeignKeyModes extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void none() {
|
||||
|
||||
DfkOne one = new DfkOne("one");
|
||||
Ebean.save(one);
|
||||
|
||||
DfkNone none = new DfkNone("none", one);
|
||||
Ebean.save(none);
|
||||
|
||||
// fails unless there is no Foreign key ...
|
||||
Ebean.delete(one);
|
||||
|
||||
DfkNone found = Ebean.find(DfkNone.class, none.getId());
|
||||
assertThat(found).isNotNull();
|
||||
// we still reference one ... even though it does not exist anymore
|
||||
assertThat(found.getOne()).isNotNull();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void setNullOnDelete() {
|
||||
|
||||
DfkOne one = new DfkOne("one2");
|
||||
Ebean.save(one);
|
||||
|
||||
DfkSetNull other = new DfkSetNull("none", one);
|
||||
Ebean.save(other);
|
||||
|
||||
// success with ... fkey value set to null
|
||||
Ebean.delete(one);
|
||||
|
||||
DfkSetNull found = Ebean.find(DfkSetNull.class, other.getId());
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getOne()).isNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeleteCascade() {
|
||||
|
||||
DfkCascadeOne one = new DfkCascadeOne("one3");
|
||||
|
||||
|
||||
DfkCascade other = new DfkCascade("cascade1", one);
|
||||
one.getDetails().add(other);
|
||||
one.getDetails().add(new DfkCascade("cascade2", one));
|
||||
one.getDetails().add(new DfkCascade("cascade3", one));
|
||||
|
||||
Ebean.save(one);
|
||||
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
Ebean.delete(one);
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("delete from dfk_cascade_one where id=?");
|
||||
|
||||
DfkCascade found = Ebean.find(DfkCascade.class, other.getId());
|
||||
assertThat(found).isNull();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class TestOneToOnePrimaryKeyJoin extends BaseTestCase {
|
||||
OtoPrime found = query.findOne();
|
||||
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_prime t0 where t0.pid = ?")
|
||||
assertThat(sqlOf(query, 10)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_prime t0 where t0.pid = ?")
|
||||
.as("we don't join to oto_prime_extra");
|
||||
|
||||
assertThat(found.getName()).isEqualTo("p" + desc);
|
||||
@@ -47,7 +47,7 @@ public class TestOneToOnePrimaryKeyJoin extends BaseTestCase {
|
||||
OtoPrime oneWith = queryWithFetch.findOne();
|
||||
|
||||
assertThat(oneWith).isNotNull();
|
||||
assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_prime t0 join oto_prime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
|
||||
assertThat(sqlOf(queryWithFetch, 10)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_prime t0 join oto_prime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
|
||||
.as("we join to oto_prime_extra");
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class TestOneToOnePrimaryKeyJoinBidi extends BaseTestCase {
|
||||
OtoUBPrime found = query.findOne();
|
||||
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_ubprime t0 where t0.pid = ?")
|
||||
assertThat(sqlOf(query, 10)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_ubprime t0 where t0.pid = ?")
|
||||
.as("we don't join to oto_ubprime_extra");
|
||||
|
||||
assertThat(found.getName()).isEqualTo("u" + desc);
|
||||
@@ -47,7 +47,7 @@ public class TestOneToOnePrimaryKeyJoinBidi extends BaseTestCase {
|
||||
OtoUBPrime oneWith = queryWithFetch.findOne();
|
||||
|
||||
assertThat(oneWith).isNotNull();
|
||||
assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 left join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
|
||||
assertThat(sqlOf(queryWithFetch, 10)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 left join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
|
||||
.as("we join to oto_prime_extra");
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class TestOneToOnePrimaryKeyJoinOptional extends BaseTestCase {
|
||||
OtoUPrime found = query.findOne();
|
||||
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_uprime t0 where t0.pid = ?")
|
||||
assertThat(sqlOf(query, 4)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_uprime t0 where t0.pid = ?")
|
||||
.as("we don't join to oto_uprime_extra");
|
||||
|
||||
assertThat(found.getName()).isEqualTo("u" + desc);
|
||||
@@ -47,7 +47,7 @@ public class TestOneToOnePrimaryKeyJoinOptional extends BaseTestCase {
|
||||
OtoUPrime oneWith = queryWithFetch.findOne();
|
||||
|
||||
assertThat(oneWith).isNotNull();
|
||||
assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
|
||||
assertThat(sqlOf(queryWithFetch, 6)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
|
||||
.as("we join to oto_prime_extra");
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@@ -21,7 +20,7 @@ public class EPersonOnline {
|
||||
@Size(max=127)
|
||||
String email;
|
||||
|
||||
boolean online;
|
||||
boolean onlineStatus;
|
||||
|
||||
@WhenModified
|
||||
Instant whenUpdated;
|
||||
@@ -42,12 +41,12 @@ public class EPersonOnline {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public boolean isOnline() {
|
||||
return online;
|
||||
public boolean isOnlineStatus() {
|
||||
return onlineStatus;
|
||||
}
|
||||
|
||||
public void setOnline(boolean online) {
|
||||
this.online = online;
|
||||
public void setOnlineStatus(boolean onlineStatus) {
|
||||
this.onlineStatus = onlineStatus;
|
||||
}
|
||||
|
||||
public Instant getWhenUpdated() {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
@Test
|
||||
public void h2Merge() {
|
||||
|
||||
String sql = "merge into e_person_online (email, online, when_updated) key(email) values (?, ?, now())";
|
||||
String sql = "merge into e_person_online (email, online_status, when_updated) key(email) values (?, ?, now())";
|
||||
|
||||
String email = "baz@one.com";
|
||||
|
||||
@@ -28,9 +28,9 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
EPersonOnline found = Ebean.find(EPersonOnline.class, key);
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getEmail()).isEqualTo(email);
|
||||
assertThat(found.isOnline()).isTrue();
|
||||
assertThat(found.isOnlineStatus()).isTrue();
|
||||
|
||||
String sqlNamed = "merge into e_person_online (email, online, when_updated) key(email) values (:email, :online, now())";
|
||||
String sqlNamed = "merge into e_person_online (email, online_status, when_updated) key(email) values (:email, :online, now())";
|
||||
|
||||
SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed)
|
||||
.setGetGeneratedKeys(true)
|
||||
@@ -45,7 +45,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
assertThat(found2).isNotNull();
|
||||
assertThat(found2.getId()).isEqualTo(key);
|
||||
assertThat(found2.getEmail()).isEqualTo(email);
|
||||
assertThat(found2.isOnline()).isFalse();
|
||||
assertThat(found2.isOnlineStatus()).isFalse();
|
||||
assertThat(found2.getWhenUpdated()).isGreaterThan(found.getWhenUpdated());
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
@Test
|
||||
public void postgresUpsert() {
|
||||
|
||||
String sql = "insert into e_person_online (email, online, when_updated) values (?, ?, now()) on conflict (email) do update set when_updated=now(), online = ?";
|
||||
String sql = "insert into e_person_online (email, online_status, when_updated) values (?, ?, now()) on conflict (email) do update set when_updated=now(), online_status = ?";
|
||||
|
||||
String email = "foo@one.com";
|
||||
|
||||
@@ -67,10 +67,10 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
EPersonOnline found = Ebean.find(EPersonOnline.class, key);
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getEmail()).isEqualTo("foo@one.com");
|
||||
assertThat(found.isOnline()).isTrue();
|
||||
assertThat(found.isOnlineStatus()).isTrue();
|
||||
|
||||
|
||||
String sqlNamed = "insert into e_person_online (email, online, when_updated) values (:email, :online, now()) on conflict (email) do update set when_updated=now(), online = :online";
|
||||
String sqlNamed = "insert into e_person_online (email, online_status, when_updated) values (:email, :online, now()) on conflict (email) do update set when_updated=now(), online_status = :online";
|
||||
SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed)
|
||||
.setGetGeneratedKeys(true)
|
||||
.setParameter("email", email)
|
||||
@@ -82,7 +82,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
assertThat(found2).isNotNull();
|
||||
assertThat(found2.getId()).isEqualTo(key);
|
||||
assertThat(found2.getEmail()).isEqualTo("foo@one.com");
|
||||
assertThat(found2.isOnline()).isFalse();
|
||||
assertThat(found2.isOnlineStatus()).isFalse();
|
||||
assertThat(found2.getWhenUpdated()).isGreaterThan(found.getWhenUpdated());
|
||||
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
|
||||
String email = "bar@one.com";
|
||||
|
||||
String sql = "insert into e_person_online (email, online, when_updated) values (?, ?, current_time) on duplicate key update when_updated=current_time, online = ?";
|
||||
String sql = "insert into e_person_online (email, online_status, when_updated) values (?, ?, current_time) on duplicate key update when_updated=current_time, online_status = ?";
|
||||
SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql)
|
||||
.setGetGeneratedKeys(true)
|
||||
.setParameter(1, email)
|
||||
@@ -106,10 +106,10 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
EPersonOnline found = Ebean.find(EPersonOnline.class, key);
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getEmail()).isEqualTo("bar@one.com");
|
||||
assertThat(found.isOnline()).isTrue();
|
||||
assertThat(found.isOnlineStatus()).isTrue();
|
||||
|
||||
|
||||
String sqlNamed = "insert into e_person_online (email, online, when_updated) values (:email, :online, current_time) on duplicate key update when_updated=current_time, online = :online";
|
||||
String sqlNamed = "insert into e_person_online (email, online_status, when_updated) values (:email, :online, current_time) on duplicate key update when_updated=current_time, online_status = :online";
|
||||
SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed)
|
||||
.setGetGeneratedKeys(true)
|
||||
.setParameter("email", email)
|
||||
@@ -122,7 +122,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase {
|
||||
assertThat(found2).isNotNull();
|
||||
assertThat(found2.getId()).isEqualTo(key);
|
||||
assertThat(found2.getEmail()).isEqualTo("bar@one.com");
|
||||
assertThat(found2.isOnline()).isFalse();
|
||||
assertThat(found2.isOnlineStatus()).isFalse();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user