package edu.washington.escience.myria;
import java.io.Serializable;
import java.util.Objects;
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
/**
* This class holds the key that identifies a relation. The notation is user.program.relation.
*/
public final class RelationKey implements Serializable {
/** Required for Java serialization. */
private static final long serialVersionUID = 1L;
/** The user who owns/creates this relation. */
@JsonProperty private final String userName;
/** The user's program that owns/creates this relation. */
@JsonProperty private final String programName;
/** The name of the relation. */
@JsonProperty private final String relationName;
/**
* Static function to create a RelationKey object.
*
* @param userName the user who owns/creates this relation.
* @param programName the user's program that owns/creates this relation.
* @param relationName the name of the relation.
* @return a new RelationKey reference to the specified relation.
*/
@JsonCreator
public static RelationKey of(
@JsonProperty("userName") final String userName,
@JsonProperty("programName") final String programName,
@JsonProperty("relationName") final String relationName) {
return new RelationKey(userName, programName, relationName);
}
/** The regular expression specifying what names are valid. */
public static final String VALID_NAME_REGEX = "^[a-zA-Z_]\\w*$";
/** The regular expression matcher for {@link #VALID_NAME_REGEX}. */
private static final Pattern VALID_NAME_PATTERN = Pattern.compile(VALID_NAME_REGEX);
/**
* Validate a potential user, program, relation name for use in a Relation Key. Valid names are given by
* {@link #VALID_NAME_REGEX}.
*
* @param name the candidate user, program, or relation name.
* @param whichName 'user', 'program', or 'relation'.
* @return the supplied name, if it is valid.
* @throws IllegalArgumentException if the name does not match the regex {@link #VALID_NAME_REGEX}.
*/
private static String checkName(final String name, final String whichName) {
Objects.requireNonNull(name, whichName);
Preconditions.checkArgument(
VALID_NAME_PATTERN.matcher(name).matches(),
"supplied %s %s does not match the valid name regex %s",
whichName,
name,
VALID_NAME_REGEX);
return name;
}
/**
* Private constructor to create a RelationKey object.
*
* @param userName the user who owns/creates this relation.
* @param programName the user's program that owns/creates this relation.
* @param relationName the name of the relation.
*/
public RelationKey(final String userName, final String programName, final String relationName) {
checkName(userName, "userName");
checkName(programName, "programName");
checkName(relationName, "relationName");
this.userName = userName;
this.programName = programName;
this.relationName = relationName;
}
/**
* @return the name of this relation.
*/
public String getRelationName() {
return relationName;
}
/**
* @return the name of the program containing this relation.
*/
public String getProgramName() {
return programName;
}
/**
* @return the name of the user who owns the program containing this relation.
*/
public String getUserName() {
return userName;
}
@Override
public String toString() {
return Joiner.on(':').join(userName, programName, relationName);
}
/**
* Helper function for computing strings of different types.
*
* @param leftEscape the left escape character, e.g., '\"'.
* @param separate the separating character, e.g., ':'.
* @param rightEscape the right escape character, e.g., '\"'.
* @return <code>"user:program:relation"</code>, for example.
*/
private String toString(final char leftEscape, final char separate, final char rightEscape) {
StringBuilder sb = new StringBuilder();
sb.append(leftEscape)
.append(Joiner.on(separate).join(userName, programName, relationName))
.append(rightEscape);
return sb.toString();
}
/** The maximum length of a postgres identifier is 63 chars. Ugh. */
private static final int MAX_POSTGRESQL_IDENTIFIER_LENGTH = 63;
/**
* Helper function for computing strings of different types.
*
* @param dbms the DBMS, e.g., {@link MyriaConstants.STORAGE_SYSTEM_MYSQL}.
* @return <code>"user:program:relation"</code>, for example.
*/
public String toString(final String dbms) {
switch (dbms) {
case MyriaConstants.STORAGE_SYSTEM_POSTGRESQL:
String ret = toString('\"', ':', '\"');
/* Subtract 2 because of the open and close quotes. */
Preconditions.checkArgument(
(ret.length() - 2) <= MAX_POSTGRESQL_IDENTIFIER_LENGTH,
"PostgreSQL does not allow relation names longer than %s characters: %s",
MAX_POSTGRESQL_IDENTIFIER_LENGTH,
ret);
return ret;
case MyriaConstants.STORAGE_SYSTEM_SQLITE:
return toString('\"', ':', '\"');
case MyriaConstants.STORAGE_SYSTEM_MONETDB:
/* TODO: can we switch the other DBMS to : as well? */
return toString('\"', ' ', '\"');
case MyriaConstants.STORAGE_SYSTEM_MYSQL:
/* TODO: can we switch the other DBMS to : as well? */
return toString('`', ' ', '`');
default:
throw new IllegalArgumentException("Unsupported dbms " + dbms);
}
}
@Override
public int hashCode() {
return Objects.hash(userName, programName, relationName);
}
@Override
public boolean equals(final Object other) {
if (other == null || !(other instanceof RelationKey)) {
return false;
}
RelationKey o = (RelationKey) other;
return Objects.equals(userName, o.userName)
&& Objects.equals(programName, o.programName)
&& Objects.equals(relationName, o.relationName);
}
/**
* Return the default relation key for a temporary table created for the given query.
*
* @param queryId the query that will generate this temporary table
* @param table the name of the table
* @return the default relation key for this temp table created for the given query
*/
public static RelationKey ofTemp(final long queryId, final String table) {
return RelationKey.of("q_" + queryId, "temp", table);
}
/**
* @return the query ID if the relation is a temp relation, null otherwise
*/
public Long tempRelationQueryId() {
if (isTemp()) {
return Long.parseLong(userName.substring(2));
}
return null;
}
/**
* @return if it is a temp relation.
*/
public boolean isTemp() {
return programName.equals("temp");
}
}