Reputation: 43
In my database I have 3 tables: problems
, users
and roles
. The roles
table has 2 values: client and coach.
Database rules:
The problems
entity has two foreign keys to the users
table: coach_id and client_id. Can I add check constraints using Android Room to be sure that a problem respects this rule ?
I want to avoid the cases where a problem has two clients or two coaches.
Database Conceptual Diagram
Upvotes: 1
Views: 1101
Reputation: 57043
Currently ROOM doesn't support the addition of CHECK constraints.
However, you could introduce a CHECK constraint by DROPPING the table and then CREATEing the table (probably not desirable as that could introduce ongoing complications e.g. when migrating).
However, CHECK constraints are limited and cannot include sub queries which, I believe, would complicate matters and perhaps even rule out there being a suitable/usable CHECK constraint.
coach INTEGER CHECK((SELECT authority FROM user JOIN role ON role.roleid = user.role) = 0) REFERENCES user(userid) ON DELETE CASCADE ON UPDATE CASCADE,
(assuming, as it's unclear what column denotes the role type, that a coach has to have a role who's authority is 0). However, the CHECK cannot be used because it would result in a subqueries prohibited in CHECK constraints error.As such it might be simpler to programatically do such a check as part of the process of inserting a Problem. That is for each type (coach/client) get the role from the user and reject the actual insertion into the database should the incorrect role be specified. This being more akin to Room's OO stance (i.e. tables support and are created according to Objects).
Perhaps consider the following based upon your question:-
@Entity(tableName = "role")
public class Role {
public static final int AUTHORITY_COACH = 0;
public static final int AUTHORITY_CLIENT = 1;
@PrimaryKey
Long roleid;
String description;
int authority;
public Role(String description, int authority) {
this.description = description;
if ( authority >= AUTHORITY_CLIENT) {
this.authority = AUTHORITY_CLIENT;
} else {
this.authority = AUTHORITY_COACH;
}
}
public Long getRoleid() {
return roleid;
}
public void setRoleid(Long roleid) {
this.roleid = roleid;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getAuthority() {
return authority;
}
public void setAuthority(int authority) {
this.authority = authority;
}
}
@Entity(
tableName = "user",
foreignKeys = {
@ForeignKey(
entity = Role.class,
parentColumns = {"roleid"},
childColumns = {"role"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {
@Index("role")
}
)
public class User {
@PrimaryKey
Long userid;
String name;
String surname;
String username;
String email;
String password;
long role;
public User(String name, String surname, String username, String email, String password, long role) {
this.name = name;
this.surname = surname;
this.username = username;
this.email = email;
this.password = password;
this.role = role;
}
public Long getUserid() {
return userid;
}
public void setUserid(Long userid) {
this.userid = userid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRole() {
return role;
}
public void setRole(long role) {
this.role = role;
}
}
@Entity(
tableName = "problem",
foreignKeys = {
@ForeignKey(
entity = User.class,
parentColumns = {"userid"},
childColumns = {"coach"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
@ForeignKey(
entity = User.class,
parentColumns = {"userid"},
childColumns = {"client"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {
@Index(value = "coach"),
@Index(value = "client")
}
)
public class Problem {
@PrimaryKey
Long problemid;
String title;
String status;
long coach;
long client;
public Problem(){}
public Problem(String title, String status, User coach, User client) {
this.title = title;
this.status = status;
this.coach = coach.getUserid();
this.client = client.getUserid();
}
public Long getProblemid() {
return problemid;
}
public void setProblemid(Long problemid) {
this.problemid = problemid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public long getCoach() {
return coach;
}
public void setCoach(long coach) {
this.coach = coach;
}
public long getClient() {
return client;
}
public void setClient(long client) {
this.client = client;
}
}
public class UserWithRole {
@Embedded
User user;
@Relation(entity = Role.class,entityColumn = "roleid",parentColumn = "role")
Role userrole;
public UserWithRole(){}
public User getUser() {
return user;
}
public Role getUserrole() {
return userrole;
}
}
@Dao
public interface AllDao {
@Insert
long insertRole(Role role);
@Insert
long insertUser(User user);
@Insert
long insertProblem(Problem problem);
@Query("SELECT (authority = " + Role.AUTHORITY_COACH + ") FROM user JOIN role ON role.roleid = user.role WHERE userid = :userid ")
boolean isUserACoach(long userid);
@Query("SELECT * FROM user WHERE userid = :userid")
User getUserById(long userid);
}
@androidx.room.Database(version = 1,entities = {Role.class,User.class,Problem.class})
public abstract class Database extends RoomDatabase {
abstract AllDao allDao();
}
public class MainActivity extends AppCompatActivity {
Database database;
AllDao allDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
database = Room.databaseBuilder(
this,
Database.class,
"mydb"
)
.allowMainThreadQueries()
.build();
allDao = database.allDao();
long coachRole = allDao.insertRole(new Role("Coach",Role.AUTHORITY_COACH));
long newClientRole = allDao.insertRole(new Role("New Client",Role.AUTHORITY_CLIENT));
long oldClientRole = allDao.insertRole(new Role("Old Client",Role.AUTHORITY_CLIENT));
long fredBloggs = allDao.insertUser(new User("Fred","Bloggs","fblog","[email protected]","password",coachRole));
long marySmith = allDao.insertUser(new User("Mary","Smith","msmith","[email protected]","password",newClientRole));
long anneWalker = allDao.insertUser(new User("Anne","Walker","awalker","[email protected]","password",oldClientRole));
//<<<<<<<<<< SHOULD BE OK TO ADD >>>>>>>>>>
if (verifyAndAddProblem("Problem 1","new",allDao.getUserById(fredBloggs),allDao.getUserById(marySmith)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
//<<<<<<<<<< SHOULD NOT BE ADDED (annWalker is NOT a coach) >>>>>>>>>>
if (verifyAndAddProblem("Problem 2","new",allDao.getUserById(anneWalker),allDao.getUserById(marySmith)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
//<<<<<<<<<< SHOULD NOT BE ADDED (fredBloggs is a coach BUT is NOT a client) >>>>>>>>>>
if (verifyAndAddProblem("Problem 3","new",allDao.getUserById(fredBloggs),allDao.getUserById(fredBloggs)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
}
//ALTERNATIVE TO CHECK CONSTRAINT DONE HERE
private long verifyAndAddProblem(String title, String status, User coach, User client) {
long rv = -1;
if (!allDao.isUserACoach(coach.getUserid()) || allDao.isUserACoach(client.getUserid())) return rv;
return allDao.insertProblem(new Problem(title,status,coach,client));
}
}
When run log contains :-
2020-01-02 08:47:33.647 D/PROBLEMADDRESULT: Problem Added
2020-01-02 08:47:33.658 D/PROBLEMADDRESULT: Problem not added
2020-01-02 08:47:33.661 D/PROBLEMADDRESULT: Problem not added
Upvotes: 2