![JAR search and dependency download from the Maven repository](/logo.png)
org.molgenis.data.postgresql.PostgreSqlQueryGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of molgenis-data-postgresql Show documentation
Show all versions of molgenis-data-postgresql Show documentation
Data layer persistence in PostgreSQL.
package org.molgenis.data.postgresql;
import static com.google.common.collect.Streams.stream;
import static java.lang.String.format;
import static java.time.ZoneOffset.UTC;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.IntStream.range;
import static org.molgenis.data.QueryRule.Operator.GREATER;
import static org.molgenis.data.QueryRule.Operator.GREATER_EQUAL;
import static org.molgenis.data.QueryRule.Operator.IN;
import static org.molgenis.data.QueryRule.Operator.LESS;
import static org.molgenis.data.QueryRule.Operator.LESS_EQUAL;
import static org.molgenis.data.QueryRule.Operator.LIKE;
import static org.molgenis.data.QueryRule.Operator.NESTED;
import static org.molgenis.data.QueryRule.Operator.RANGE;
import static org.molgenis.data.meta.AttributeType.BOOL;
import static org.molgenis.data.meta.AttributeType.ENUM;
import static org.molgenis.data.meta.AttributeType.ONE_TO_MANY;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getCheckConstraintName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getColumnName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getFilterColumnName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getForeignKeyName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getFunctionValidateUpdateName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getJunctionTableIndexName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getJunctionTableName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getJunctionTableOrderColumnName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getPrimaryKeyName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getTableName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getUniqueKeyName;
import static org.molgenis.data.postgresql.PostgreSqlNameGenerator.getUpdateTriggerName;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.ColumnMode.INCLUDE_DEFAULT_CONSTRAINT;
import static org.molgenis.data.postgresql.PostgreSqlQueryUtils.getPersistedAttributes;
import static org.molgenis.data.postgresql.PostgreSqlQueryUtils.getTableAttributes;
import static org.molgenis.data.postgresql.PostgreSqlQueryUtils.isPersistedInPostgreSql;
import static org.molgenis.data.util.EntityTypeUtils.isMultipleReferenceType;
import static org.molgenis.data.util.EntityTypeUtils.isSingleReferenceType;
import static org.molgenis.data.util.EntityTypeUtils.isStringType;
import static org.molgenis.data.util.EntityTypeUtils.isTextType;
import com.google.common.collect.Lists;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.molgenis.data.Entity;
import org.molgenis.data.MolgenisDataException;
import org.molgenis.data.Query;
import org.molgenis.data.QueryRule;
import org.molgenis.data.QueryRule.Operator;
import org.molgenis.data.Sort;
import org.molgenis.data.UnknownAttributeException;
import org.molgenis.data.meta.AttributeType;
import org.molgenis.data.meta.IllegalAttributeTypeException;
import org.molgenis.data.meta.model.Attribute;
import org.molgenis.data.meta.model.EntityType;
import org.molgenis.data.support.QueryImpl;
import org.molgenis.data.util.AttributeUtils;
import org.molgenis.data.util.EntityTypeUtils;
import org.molgenis.util.UnexpectedEnumException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class that generates the SQL used by {@link PostgreSqlRepository} and {@link
* PostgreSqlRepositoryCollection}
*/
class PostgreSqlQueryGenerator {
private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlQueryGenerator.class);
private static final String UNSPECIFIED_ATTRIBUTE_MSG =
"Can't use %s without specifying an attribute";
static final String ERR_CODE_READONLY_VIOLATION = "23506";
private PostgreSqlQueryGenerator() {}
private static String getSqlConstraintPrimaryKey(EntityType entityType, Attribute attr) {
return "CONSTRAINT "
+ getPrimaryKeyName(entityType, attr)
+ " PRIMARY KEY ("
+ getColumnName(attr)
+ ')';
}
private static String getSqlForeignKey(EntityType entityType, Attribute attr) {
StringBuilder strBuilder =
new StringBuilder("CONSTRAINT ")
.append(getForeignKeyName(entityType, attr))
.append(" FOREIGN KEY (")
.append(getColumnName(attr))
.append(") REFERENCES ")
.append(getTableName(attr.getRefEntity()))
.append('(')
.append(getColumnName(attr.getRefEntity().getIdAttribute()))
.append(") DEFERRABLE INITIALLY DEFERRED");
return strBuilder.toString();
}
private static String getSqlUniqueKey(EntityType entityType, Attribute attr) {
return "CONSTRAINT "
+ getUniqueKeyName(entityType, attr)
+ " UNIQUE ("
+ getColumnName(attr)
+ ')';
}
private static String getSqlCheckConstraint(EntityType entityType, Attribute attr) {
if (attr.getDataType() != ENUM) {
throw new MolgenisDataException(
format("Check constraint only allowed for attribute type [%s]", ENUM.toString()));
}
return "CONSTRAINT "
+ getCheckConstraintName(entityType, attr)
+ " CHECK ("
+ getColumnName(attr)
+ " IN ("
+ attr.getEnumOptions().stream()
.map(enumOption -> '\'' + enumOption + '\'')
.collect(joining(","))
+ "))";
}
static String getSqlCreateForeignKey(EntityType entityType, Attribute attr) {
return "ALTER TABLE " + getTableName(entityType) + " ADD " + getSqlForeignKey(entityType, attr);
}
static String getSqlDropForeignKey(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " DROP CONSTRAINT "
+ getForeignKeyName(entityType, attr);
}
static String getSqlCreateUniqueKey(EntityType entityType, Attribute attr) {
return "ALTER TABLE " + getTableName(entityType) + " ADD " + getSqlUniqueKey(entityType, attr);
}
static String getSqlDropUniqueKey(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " DROP CONSTRAINT "
+ getUniqueKeyName(entityType, attr);
}
static String getSqlCreateCheckConstraint(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " ADD "
+ getSqlCheckConstraint(entityType, attr);
}
static String getSqlDropCheckConstraint(EntityType entityType, Attribute attr) {
if (attr.getDataType() != ENUM) {
throw new MolgenisDataException(
format("Check constraint only allowed for attribute type [%s]", ENUM.toString()));
}
return "ALTER TABLE "
+ getTableName(entityType)
+ " DROP CONSTRAINT "
+ getCheckConstraintName(entityType, attr);
}
static String getSqlSetNotNull(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " ALTER COLUMN "
+ getColumnName(attr)
+ " SET NOT NULL";
}
static String getSqlDropNotNull(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " ALTER COLUMN "
+ getColumnName(attr)
+ " DROP NOT NULL";
}
static String getSqlSetDataType(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " ALTER COLUMN "
+ getColumnName(attr)
+ " SET DATA TYPE "
+ getPostgreSqlType(attr)
+ " USING "
+ getColumnName(attr)
+ "::"
+ getPostgreSqlType(attr);
}
/**
* Returns SQL string to add a column to an existing table.
*
* @param entityType entity meta data
* @param attr attribute
* @param columnMode column mode
* @return SQL string
*/
static String getSqlAddColumn(EntityType entityType, Attribute attr, ColumnMode columnMode) {
StringBuilder sql = new StringBuilder("ALTER TABLE ");
String columnSql = getSqlColumn(entityType, attr, columnMode);
sql.append(getTableName(entityType)).append(" ADD ").append(columnSql);
List sqlTableConstraints = getSqlTableConstraints(entityType, attr);
if (!sqlTableConstraints.isEmpty()) {
sqlTableConstraints.forEach(
sqlTableConstraint -> sql.append(",ADD ").append(sqlTableConstraint));
}
return sql.toString();
}
/**
* Returns SQL string to remove the default value from an existing column.
*
* @param entityType entity meta data
* @param attr attribute
* @return SQL string
*/
static String getSqlDropColumnDefault(EntityType entityType, Attribute attr) {
return "ALTER TABLE "
+ getTableName(entityType)
+ " ALTER COLUMN "
+ getColumnName(attr)
+ " DROP DEFAULT";
}
static String getSqlCreateTable(EntityType entityType) {
List persistedTableAttrs = getTableAttributes(entityType).collect(toList());
StringBuilder sql =
new StringBuilder("CREATE TABLE ").append(getTableName(entityType)).append('(');
// add columns
for (Iterator it = persistedTableAttrs.iterator(); it.hasNext(); ) {
Attribute attr = it.next();
sql.append(getSqlColumn(entityType, attr, ColumnMode.EXCLUDE_DEFAULT_CONSTRAINT));
if (it.hasNext()) {
sql.append(',');
}
}
// add table constraints
for (Attribute persistedTableAttr : persistedTableAttrs) {
List sqlTableConstraints = getSqlTableConstraints(entityType, persistedTableAttr);
if (!sqlTableConstraints.isEmpty()) {
sqlTableConstraints.forEach(
sqlTableConstraint -> sql.append(',').append(sqlTableConstraint));
}
}
sql.append(')');
return sql.toString();
}
static String getSqlCreateFunctionValidateUpdate(
EntityType entityType, Collection readonlyTableAttrs) {
StringBuilder strBuilder =
new StringBuilder(512)
.append("CREATE FUNCTION ")
.append(getFunctionValidateUpdateName(entityType))
.append("() RETURNS TRIGGER AS $$\nBEGIN\n");
String tableName = getTableName(entityType);
String idColName = getColumnName(entityType.getIdAttribute());
readonlyTableAttrs.forEach(
attr -> {
String colName = getColumnName(attr);
strBuilder
.append(" IF OLD.")
.append(colName)
.append(" <> NEW.")
.append(colName)
.append(" THEN\n");
strBuilder
.append(" RAISE EXCEPTION 'Updating read-only column ")
.append(colName)
.append(" of table ")
.append(tableName)
.append(" with id [%] is not allowed', OLD.")
.append(idColName)
.append(" USING ERRCODE = '")
.append(ERR_CODE_READONLY_VIOLATION)
.append("';\n");
strBuilder.append(" END IF;\n");
});
strBuilder.append(" RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;");
return strBuilder.toString();
}
static String getSqlDropFunctionValidateUpdate(EntityType entityType) {
return "DROP FUNCTION " + getFunctionValidateUpdateName(entityType) + "();";
}
static String getSqlCreateUpdateTrigger(
EntityType entityType, Collection readonlyTableAttrs) {
StringBuilder strBuilder =
new StringBuilder(512)
.append("CREATE TRIGGER ")
.append(getUpdateTriggerName(entityType))
.append(" AFTER UPDATE ON ")
.append(getTableName(entityType))
.append(" FOR EACH ROW WHEN (");
strBuilder.append(
readonlyTableAttrs.stream()
.map(
attr ->
"OLD." + getColumnName(attr) + " IS DISTINCT FROM NEW." + getColumnName(attr))
.collect(joining(" OR ")));
strBuilder
.append(") EXECUTE PROCEDURE ")
.append(getFunctionValidateUpdateName(entityType))
.append("();");
return strBuilder.toString();
}
static String getSqlDropUpdateTrigger(EntityType entityType) {
return "DROP TRIGGER " + getUpdateTriggerName(entityType) + " ON " + getTableName(entityType);
}
static String getSqlCreateJunctionTable(EntityType entityType, Attribute attr) {
Attribute idAttr = entityType.getIdAttribute();
StringBuilder sql =
new StringBuilder("CREATE TABLE ")
.append(getJunctionTableName(entityType, attr))
.append(" (")
.append(getJunctionTableOrderColumnName())
.append(" INT,")
.append(getColumnName(idAttr))
.append(' ')
.append(getPostgreSqlType(idAttr))
.append(" NOT NULL, ")
.append(getColumnName(attr))
.append(' ')
.append(getPostgreSqlType(attr.getRefEntity().getIdAttribute()))
.append(" NOT NULL")
.append(", FOREIGN KEY (")
.append(getColumnName(idAttr))
.append(") REFERENCES ")
.append(getTableName(entityType))
.append('(')
.append(getColumnName(idAttr))
.append(") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED");
if (isPersistedInPostgreSql(attr.getRefEntity())) {
sql.append(", FOREIGN KEY (")
.append(getColumnName(attr))
.append(") REFERENCES ")
.append(getTableName(attr.getRefEntity()))
.append('(')
.append(getColumnName(attr.getRefEntity().getIdAttribute()))
.append(") DEFERRABLE INITIALLY DEFERRED");
}
AttributeType attrType = attr.getDataType();
switch (attrType) {
case CATEGORICAL_MREF:
case MREF:
sql.append(", UNIQUE (")
.append(getColumnName(idAttr))
.append(',')
.append(getColumnName(attr))
.append(')');
break;
default:
throw new IllegalAttributeTypeException(attrType);
}
sql.append(", UNIQUE (")
.append(getJunctionTableOrderColumnName())
.append(',')
.append(getColumnName(idAttr))
.append(')');
sql.append(')');
return sql.toString();
}
static String getSqlCreateJunctionTableIndex(EntityType entityType, Attribute attr) {
Attribute idAttr = entityType.getIdAttribute();
String junctionTableName = getJunctionTableName(entityType, attr);
String junctionTableIndexName = getJunctionTableIndexName(entityType, attr, idAttr);
String idxColumnName = getColumnName(idAttr);
return "CREATE INDEX "
+ junctionTableIndexName
+ " ON "
+ junctionTableName
+ " ("
+ idxColumnName
+ ')';
}
static String getSqlDropJunctionTable(EntityType entityType, Attribute attr) {
return getSqlDropTable(getJunctionTableName(entityType, attr));
}
static String getSqlDropTable(EntityType entityType) {
return getSqlDropTable(getTableName(entityType));
}
static String getSqlDropColumn(EntityType entityType, Attribute attr) {
return "ALTER TABLE " + getTableName(entityType) + " DROP COLUMN " + getColumnName(attr);
}
static String getSqlInsert(EntityType entityType) {
StringBuilder sql =
new StringBuilder("INSERT INTO ").append(getTableName(entityType)).append(" (");
StringBuilder params = new StringBuilder();
getTableAttributes(entityType)
.forEach(
attr -> {
sql.append(getColumnName(attr)).append(", ");
params.append("?, ");
});
if (sql.charAt(sql.length() - 1) == ' ' && sql.charAt(sql.length() - 2) == ',') {
sql.setLength(sql.length() - 2);
params.setLength(params.length() - 2);
}
sql.append(") VALUES (").append(params).append(')');
return sql.toString();
}
static String getSqlInsertJunction(EntityType entityType, Attribute attr) {
String junctionTableName = getJunctionTableName(entityType, attr);
return "INSERT INTO "
+ junctionTableName
+ " ("
+ getJunctionTableOrderColumnName()
+ ','
+ getColumnName(entityType.getIdAttribute())
+ ','
+ getColumnName(attr)
+ ") VALUES (?,?,?)";
}
static String getSqlDeleteAll(EntityType entityType) {
return "DELETE FROM " + getTableName(entityType);
}
static String getSqlDelete(EntityType entityType) {
return getSqlDelete(getTableName(entityType), entityType.getIdAttribute());
}
static String getSqlDelete(String tableName, Attribute attr) {
return "DELETE FROM " + tableName + " WHERE " + getColumnName(attr) + " = ?";
}
/**
* Returns whether this attribute is stored in the entity table or another table such as a
* junction table or referenced entity table.
*
* @param attr attribute
* @return whether this attribute is stored in another table than the entity table
*/
private static boolean isPersistedInOtherTable(Attribute attr) {
boolean bidirectionalOneToMany = attr.getDataType() == ONE_TO_MANY && attr.isMappedBy();
return isMultipleReferenceType(attr) || bidirectionalOneToMany;
}
static String getSqlJunctionTableSelect(EntityType entityType, Attribute attr, int numOfIds) {
String idColName = getColumnName(entityType.getIdAttribute());
String refIdColName = getColumnName(attr);
return "SELECT "
+ idColName
+ ","
+ getJunctionTableOrderColumnName()
+ ","
+ refIdColName
+ " FROM "
+ getJunctionTableName(entityType, attr)
+ " WHERE "
+ idColName
+ " in ("
+ range(0, numOfIds).mapToObj(x -> "?").collect(joining(", "))
+ ") ORDER BY "
+ idColName
+ ","
+ getJunctionTableOrderColumnName();
}
/**
* Determines whether a distinct select is required based on a given query.
*
* @param entityType entity meta data
* @param q query
* @param entity type
* @return true
if a distinct select is required for SQL queries based on the given
* query
* @throws UnknownAttributeException if query field refers to an attribute that does not exist in
* entity meta
*/
private static boolean isDistinctSelectRequired(
EntityType entityType, Query q) {
return isDistinctSelectRequiredRec(entityType, q.getRules());
}
private static boolean isDistinctSelectRequiredRec(
EntityType entityType, List queryRules) {
if (queryRules.isEmpty()) {
return false;
}
for (QueryRule queryRule : queryRules) {
if (queryRule.getOperator() == NESTED) {
if (isDistinctSelectRequiredRec(entityType, queryRule.getNestedRules())) {
return true;
}
} else {
String queryRuleField = queryRule.getField();
if (queryRuleField != null) {
String attrName = StringUtils.split(queryRuleField, '.')[0];
Attribute attr = entityType.getAttribute(attrName);
if (attr == null) {
throw new UnknownAttributeException(entityType, attrName);
}
if (isPersistedInOtherTable(attr)) {
return true;
}
}
}
}
return false;
}
static String getSqlSelect(
EntityType entityType, Query q, List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy