Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.molgenis.data.postgresql.PostgreSqlRepositoryCollection Maven / Gradle / Ivy
package org.molgenis.data.postgresql;
import static com.google.common.collect.Sets.immutableEnumSet;
import static java.lang.String.format;
import static java.util.EnumSet.of;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static org.molgenis.data.RepositoryCollectionCapability.META_DATA_PERSISTABLE;
import static org.molgenis.data.RepositoryCollectionCapability.UPDATABLE;
import static org.molgenis.data.RepositoryCollectionCapability.WRITABLE;
import static org.molgenis.data.meta.AttributeType.COMPOUND;
import static org.molgenis.data.meta.AttributeType.ENUM;
import static org.molgenis.data.meta.AttributeType.ONE_TO_MANY;
import static org.molgenis.data.meta.model.EntityTypeMetadata.BACKEND;
import static org.molgenis.data.meta.model.EntityTypeMetadata.ENTITY_TYPE_META_DATA;
import static org.molgenis.data.meta.model.EntityTypeMetadata.ID;
import static org.molgenis.data.meta.model.EntityTypeMetadata.IS_ABSTRACT;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.ColumnMode;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.generateSqlColumnDefaultConstraint;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlAddColumn;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateCheckConstraint;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateForeignKey;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateFunctionValidateUpdate;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateJunctionTable;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateJunctionTableIndex;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateTable;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateUniqueKey;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlCreateUpdateTrigger;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropCheckConstraint;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropColumn;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropColumnDefault;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropForeignKey;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropFunctionValidateUpdate;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropJunctionTable;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropNotNull;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropTable;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropUniqueKey;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlDropUpdateTrigger;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlSetDataType;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlSetNotNull;
import static org.molgenis.data.postgresql.PostgreSqlQueryGenerator.getSqlUpdate;
import static org.molgenis.data.postgresql.PostgreSqlQueryUtils.getJunctionTableAttributes;
import static org.molgenis.data.postgresql.PostgreSqlQueryUtils.getTableAttributesReadonly;
import static org.molgenis.data.postgresql.PostgreSqlQueryUtils.isTableAttribute;
import static org.molgenis.data.postgresql.PostgreSqlRepository.BATCH_SIZE;
import static org.molgenis.data.postgresql.PostgreSqlRepository.createJunctionTableRowData;
import static org.molgenis.data.postgresql.PostgreSqlUtils.getPostgreSqlValue;
import static org.molgenis.data.util.EntityTypeUtils.getEntityTypeFetch;
import static org.molgenis.data.util.EntityTypeUtils.isMultipleReferenceType;
import static org.molgenis.data.util.EntityTypeUtils.isReferenceType;
import static org.molgenis.data.util.EntityTypeUtils.isSingleReferenceType;
import static org.molgenis.util.stream.MapCollectors.toLinkedMap;
import static org.springframework.jdbc.support.JdbcUtils.closeConnection;
import com.google.common.collect.Iterables;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.molgenis.data.AttributeValueConversionException;
import org.molgenis.data.DataService;
import org.molgenis.data.Entity;
import org.molgenis.data.Fetch;
import org.molgenis.data.MolgenisDataException;
import org.molgenis.data.Repository;
import org.molgenis.data.RepositoryCollectionCapability;
import org.molgenis.data.UnknownAttributeException;
import org.molgenis.data.UnknownRepositoryException;
import org.molgenis.data.meta.model.Attribute;
import org.molgenis.data.meta.model.EntityType;
import org.molgenis.data.support.AbstractRepositoryCollection;
import org.molgenis.data.util.AttributeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
public class PostgreSqlRepositoryCollection extends AbstractRepositoryCollection {
private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlRepositoryCollection.class);
public static final String POSTGRESQL = "PostgreSQL";
private final PostgreSqlEntityFactory postgreSqlEntityFactory;
private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;
private final DataService dataService;
PostgreSqlRepositoryCollection(
PostgreSqlEntityFactory postgreSqlEntityFactory,
DataSource dataSource,
JdbcTemplate jdbcTemplate,
DataService dataService) {
this.postgreSqlEntityFactory = requireNonNull(postgreSqlEntityFactory);
this.dataSource = requireNonNull(dataSource);
this.jdbcTemplate = requireNonNull(jdbcTemplate);
this.dataService = requireNonNull(dataService);
}
@Override
public String getName() {
return POSTGRESQL;
}
@Override
public Set getCapabilities() {
return immutableEnumSet(of(WRITABLE, UPDATABLE, META_DATA_PERSISTABLE));
}
@Override
public boolean hasRepository(String name) {
throw new UnsupportedOperationException();
}
@Override
public Repository createRepository(EntityType entityType) {
PostgreSqlRepository repository = createPostgreSqlRepository(entityType);
if (!isTableExists(entityType)) {
createTable(entityType);
}
return repository;
}
@Override
public boolean hasRepository(EntityType entityType) {
return isTableExists(entityType);
}
@Override
public Iterable getEntityTypeIds() {
return dataService
.query(ENTITY_TYPE_META_DATA, EntityType.class)
.eq(BACKEND, POSTGRESQL)
.fetch(getEntityTypeFetch())
.findAll()
.map(EntityType::getId)
::iterator;
}
@Override
public Repository getRepository(String id) {
EntityType entityType =
dataService
.query(ENTITY_TYPE_META_DATA, EntityType.class)
.eq(BACKEND, POSTGRESQL)
.and()
.eq(ID, id)
.and()
.eq(IS_ABSTRACT, false)
.fetch(getEntityTypeFetch())
.findOne();
return getRepository(entityType);
}
@Override
public Repository getRepository(EntityType entityType) {
return createPostgreSqlRepository(entityType);
}
@Override
public Iterator> iterator() {
return dataService
.query(ENTITY_TYPE_META_DATA, EntityType.class)
.eq(BACKEND, POSTGRESQL)
.and()
.eq(IS_ABSTRACT, false)
.fetch(getEntityTypeFetch())
.findAll()
.map(this::getRepository)
.iterator();
}
@Override
public void deleteRepository(EntityType entityType) {
if (entityType.isAbstract()) {
throw new UnknownRepositoryException(entityType.getId());
}
dropTables(entityType);
}
@Override
public void updateRepository(EntityType entityType, EntityType updatedEntityType) {
// no actions needed
}
private void dropTables(EntityType entityType) {
getJunctionTableAttributes(entityType)
.forEach(mrefAttr -> dropJunctionTable(entityType, mrefAttr));
String sqlDropTable = getSqlDropTable(entityType);
if (LOG.isDebugEnabled()) {
LOG.debug("Dropping table for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlDropTable);
}
}
jdbcTemplate.execute(sqlDropTable);
if (getTableAttributesReadonly(entityType).findAny().isPresent()) {
String sqlDropFunctionValidateUpdate = getSqlDropFunctionValidateUpdate(entityType);
if (LOG.isDebugEnabled()) {
LOG.debug("Dropping trigger function for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlDropFunctionValidateUpdate);
}
}
jdbcTemplate.execute(sqlDropFunctionValidateUpdate);
}
}
@Override
public void addAttribute(EntityType entityType, Attribute attr) {
if (entityType.isAbstract()) {
throw new MolgenisDataException(
format(
"Cannot add attribute [%s] to abstract entity type [%s].",
attr.getName(), entityType.getId()));
}
if (entityType.getAttribute(attr.getName()) != null) {
throw new MolgenisDataException(
format(
"Adding attribute operation failed. Attribute already exists [%s]", attr.getName()));
}
addAttributeInternal(entityType, attr);
}
@Override
public void updateAttribute(EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (entityType.isAbstract()) {
throw new MolgenisDataException(
format(
"Cannot update attribute [%s] for abstract entity type [%s].",
attr.getName(), entityType.getId()));
}
if (!isPersisted(attr) && !isPersisted(updatedAttr)) {
return;
}
if (isPersisted(attr) && !isPersisted(updatedAttr)) {
deleteAttribute(entityType, attr);
} else if (!isPersisted(attr) && isPersisted(updatedAttr)) {
addAttributeInternal(entityType, updatedAttr);
} else {
updateColumn(entityType, attr, updatedAttr);
}
}
@Override
public void deleteAttribute(EntityType entityType, Attribute attr) {
if (entityType.isAbstract()) {
throw new MolgenisDataException(
format(
"Cannot delete attribute [%s] from abstract entity type [%s].",
attr.getName(), entityType.getId()));
}
if (entityType.getAttribute(attr.getName()) == null) {
throw new UnknownAttributeException(entityType, attr.getName());
}
if (!isPersisted(attr)) {
return;
}
if (isMultipleReferenceType(attr)) {
dropJunctionTable(entityType, attr);
} else {
dropColumn(entityType, attr);
}
}
/**
* Add attribute to entityType.
*
* @param entityType the {@link EntityType} to add attribute to
* @param attr attribute to add
*/
private void addAttributeInternal(EntityType entityType, Attribute attr) {
if (!isPersisted(attr)) {
return;
}
if (isMultipleReferenceType(attr)) {
createJunctionTable(entityType, attr);
if (attr.getDefaultValue() != null && !attr.isNillable()) {
@SuppressWarnings("unchecked")
Iterable defaultRefEntities =
(Iterable) AttributeUtils.getDefaultTypedValue(attr);
if (!Iterables.isEmpty(defaultRefEntities)) {
createJunctionTableRows(entityType, attr, defaultRefEntities);
}
}
} else {
createColumn(entityType, attr);
}
}
private void createJunctionTableRows(
EntityType entityType, Attribute attr, Iterable defaultRefEntities) {
int nrRefEntities = Iterables.size(defaultRefEntities);
PostgreSqlRepository postgreSqlRepository = createPostgreSqlRepository(entityType);
Attribute idAttribute = entityType.getIdAttribute();
String idAttributeName = idAttribute.getName();
postgreSqlRepository.forEachBatched(
new Fetch().field(idAttributeName),
entities -> {
List> mrefs = new ArrayList<>(entities.size() * nrRefEntities);
entities.forEach(
entity -> {
AtomicInteger seqNr = new AtomicInteger(0);
defaultRefEntities.forEach(
defaultRefEntity ->
mrefs.add(
createJunctionTableRowData(
seqNr.getAndIncrement(),
idAttribute,
defaultRefEntity,
attr,
entity)));
});
postgreSqlRepository.addMrefs(mrefs, attr);
},
BATCH_SIZE);
}
/**
* Indicates if the attribute is persisted in the database. Compound attributes, computed
* attributes with an expression and one-to-many mappedBy attributes are not persisted.
*
* @param attr the attribute to check
* @return boolean indicating if the entity is persisted in the database.
*/
private static boolean isPersisted(Attribute attr) {
return !attr.hasExpression()
&& attr.getDataType() != COMPOUND
&& !(attr.getDataType() == ONE_TO_MANY && attr.isMappedBy());
}
/**
* Updates database column based on attribute changes.
*
* @param entityType entity meta data
* @param attr current attribute
* @param updatedAttr updated attribute
*/
private void updateColumn(EntityType entityType, Attribute attr, Attribute updatedAttr) {
// nullable changes
if (!Objects.equals(attr.isNillable(), updatedAttr.isNillable())) {
updateNillable(entityType, attr, updatedAttr);
}
// unique changes
if (!Objects.equals(attr.isUnique(), updatedAttr.isUnique())) {
updateUnique(entityType, attr, updatedAttr);
}
// readonly changes
if (!Objects.equals(attr.isReadOnly(), updatedAttr.isReadOnly())) {
updateReadonly(entityType, attr, updatedAttr);
}
// data type changes
if (!Objects.equals(attr.getDataType(), updatedAttr.getDataType())) {
if (updatedAttr.isReadOnly()) {
dropTableTriggers(entityType);
}
updateDataType(entityType, attr, updatedAttr);
if (updatedAttr.isReadOnly()) {
createTableTriggers(entityType);
}
}
// ref entity changes
if (attr.hasRefEntity()
&& updatedAttr.hasRefEntity()
&& !attr.getRefEntity().getId().equals(updatedAttr.getRefEntity().getId())) {
updateRefEntity(entityType, attr, updatedAttr);
}
// enum option changes
if (!Objects.equals(attr.getEnumOptions(), updatedAttr.getEnumOptions())) {
updateEnumOptions(entityType, attr, updatedAttr);
}
}
/**
* Updates foreign keys based on referenced entity changes.
*
* @param entityType entity meta data
* @param attr current attribute
* @param updatedAttr updated attribute
*/
private void updateRefEntity(EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (isSingleReferenceType(attr) && isSingleReferenceType(updatedAttr)) {
dropForeignKey(entityType, attr);
if (attr.getRefEntity().getIdAttribute().getDataType()
!= updatedAttr.getRefEntity().getIdAttribute().getDataType()) {
updateColumnDataType(entityType, updatedAttr);
}
createForeignKey(entityType, updatedAttr);
} else if (isMultipleReferenceType(attr) && isMultipleReferenceType(updatedAttr)) {
throw new MolgenisDataException(
format(
"Updating entity [%s] attribute [%s] referenced entity from [%s] to [%s] not allowed for type [%s]",
entityType.getId(),
attr.getName(),
attr.getRefEntity().getId(),
updatedAttr.getRefEntity().getId(),
updatedAttr.getDataType().toString()));
}
}
/**
* Updates check constraint based on enum value changes.
*
* @param entityType entity meta data
* @param attr current attribute
* @param updatedAttr updated attribute
*/
private void updateEnumOptions(EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (attr.getDataType() == ENUM) {
if (updatedAttr.getDataType() == ENUM) {
// update check constraint
dropCheckConstraint(entityType, attr);
createCheckConstraint(entityType, updatedAttr);
} else {
// drop check constraint
dropCheckConstraint(entityType, attr);
}
} else {
if (updatedAttr.getDataType() == ENUM) {
createCheckConstraint(entityType, updatedAttr);
}
}
}
private void dropColumnDefaultValue(EntityType entityType, Attribute attr) {
String dropColumnDefaultValueSql = getSqlDropColumnDefault(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Dropping column default constraint for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", dropColumnDefaultValueSql);
}
}
jdbcTemplate.execute(dropColumnDefaultValueSql);
}
/**
* Updates column data type and foreign key constraints based on data type update.
*
* @param entityType entity meta data
* @param attr current attribute
* @param updatedAttr updated attribute
*/
private void updateDataType(EntityType entityType, Attribute attr, Attribute updatedAttr) {
Attribute idAttr = entityType.getIdAttribute();
if (idAttr != null && idAttr.getName().equals(attr.getName())) {
throw new MolgenisDataException(
format(
"Data type of entity [%s] attribute [%s] cannot be modified, because [%s] is an ID attribute.",
entityType.getId(), attr.getName(), attr.getName()));
}
// do nothing on representation changes XREF --> CATEGORICAL
if (isSingleReferenceType(attr) && isSingleReferenceType(updatedAttr)) {
return;
}
// do nothing on representation changes MREF --> CATEGORICAL_MREF
if (isMultipleReferenceType(attr) && isMultipleReferenceType(updatedAttr)) {
return;
}
// remove foreign key on data type updates such as XREF --> STRING
if (isSingleReferenceType(attr) && !isReferenceType(updatedAttr)) {
dropForeignKey(entityType, attr);
}
if (isSingleReferenceType(attr) && isMultipleReferenceType(updatedAttr)) {
updateManyToOneToManyToMany(entityType, attr, updatedAttr);
} else if (isMultipleReferenceType(attr) && isSingleReferenceType(updatedAttr)) {
updateManyToManyToManyToOne(entityType, attr, updatedAttr);
} else {
updateColumnDataType(entityType, updatedAttr);
}
// add foreign key on data type updates such as STRING --> XREF
if (!isReferenceType(attr) && isSingleReferenceType(updatedAttr)) {
createForeignKey(entityType, updatedAttr);
}
}
private void updateManyToManyToManyToOne(
EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (!isMultipleReferenceType(attr)) {
throw new IllegalArgumentException();
}
if (!isSingleReferenceType(updatedAttr)) {
throw new IllegalArgumentException();
}
if (entityType.isAbstract()) {
throw new IllegalArgumentException();
}
// 1. add table column
boolean previousNillable = updatedAttr.isNillable();
updatedAttr.setNillable(true);
createColumn(entityType, updatedAttr);
updatedAttr.setNillable(previousNillable);
// 2. move data from junction table to table column
Attribute idAttribute = entityType.getIdAttribute();
Attribute refIdAttribute = attr.getRefEntity().getIdAttribute();
Fetch fetch =
new Fetch()
.field(idAttribute.getName())
.field(attr.getName(), new Fetch().field(refIdAttribute.getName()));
String updateSql = getSqlUpdate(entityType, updatedAttr);
PostgreSqlRepository postgreSqlRepository = createPostgreSqlRepository(entityType);
postgreSqlRepository.forEachBatched(
fetch,
entitiesBatch -> {
// validate
entitiesBatch.forEach(
entity -> {
if (Iterables.size(entity.getEntities(attr.getName())) > 1) {
throw new AttributeValueConversionException(
format(
"Entity type '%s' attribute '%s' type '%s' can't be updated to '%s' because one or more entities refer to multiple referenced entities.",
entityType.getLabel(),
attr.getName(),
attr.getDataType(),
updatedAttr.getDataType()));
}
});
// update
if (LOG.isDebugEnabled()) {
LOG.debug("Updating {} [{}] entities", entitiesBatch.size(), getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", updateSql);
}
}
jdbcTemplate.batchUpdate(
updateSql,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Entity entity = entitiesBatch.get(i);
Entity refEntity = Iterables.getFirst(entity.getEntities(attr.getName()), null);
Object postgreSqlValue =
refEntity != null
? getPostgreSqlValue(
refEntity, refEntity.getEntityType().getIdAttribute())
: null;
ps.setObject(1, postgreSqlValue);
ps.setObject(2, getPostgreSqlValue(entity, idAttribute));
}
@Override
public int getBatchSize() {
return entitiesBatch.size();
}
});
},
BATCH_SIZE);
// 3. set nillable
if (!previousNillable) {
updateNillable(entityType, attr, updatedAttr);
}
// 4. remove junction table
dropJunctionTable(entityType, attr);
}
private void updateManyToOneToManyToMany(
EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (!isSingleReferenceType(attr)) {
throw new IllegalArgumentException();
}
if (!isMultipleReferenceType(updatedAttr)) {
throw new IllegalArgumentException();
}
if (entityType.isAbstract()) {
throw new IllegalArgumentException();
}
if (attr.isInversedBy()) {
throw new IllegalArgumentException();
}
// 1. create junction table
createJunctionTable(entityType, updatedAttr);
// 2. move data from table column to junction table
Attribute idAttribute = entityType.getIdAttribute();
Attribute refIdAttribute = attr.getRefEntity().getIdAttribute();
Fetch fetch =
new Fetch()
.field(idAttribute.getName())
.field(attr.getName(), new Fetch().field(refIdAttribute.getName()));
PostgreSqlRepository postgreSqlRepository = createPostgreSqlRepository(entityType);
postgreSqlRepository.forEachBatched(
fetch,
entities -> {
List> mrefs = createMrefs(attr, updatedAttr, idAttribute, entities);
postgreSqlRepository.addMrefs(mrefs, updatedAttr);
},
BATCH_SIZE);
// 3. remove table column
dropColumn(entityType, attr);
}
private List> createMrefs(
Attribute attr, Attribute updatedAttr, Attribute idAttribute, List entities) {
return entities.stream()
.map(
entity -> {
Entity refEntity = entity.getEntity(attr.getName());
return refEntity != null
? createJunctionTableRowData(0, idAttribute, refEntity, updatedAttr, entity)
: null;
})
.filter(Objects::nonNull)
.collect(toList());
}
/**
* Updates unique constraint based on attribute unique changes.
*
* @param entityType entity meta data
* @param attr current attribute
* @param updatedAttr updated attribute
*/
private void updateUnique(EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (attr.isUnique() && !updatedAttr.isUnique()) {
Attribute idAttr = entityType.getIdAttribute();
if (idAttr != null && idAttr.getName().equals(attr.getName())) {
throw new MolgenisDataException(
format(
"ID attribute [%s] of entity [%s] must be unique",
attr.getName(), entityType.getId()));
}
dropUniqueKey(entityType, updatedAttr);
} else if (!attr.isUnique() && updatedAttr.isUnique()) {
createUniqueKey(entityType, updatedAttr);
}
}
/**
* Updates triggers and functions based on attribute readonly changes.
*
* @param entityType entity meta data
* @param attr current attribute
* @param updatedAttr updated attribute
*/
private void updateReadonly(EntityType entityType, Attribute attr, Attribute updatedAttr) {
Map readonlyTableAttrs =
getTableAttributesReadonly(entityType)
.collect(toLinkedMap(Attribute::getName, Function.identity()));
if (!readonlyTableAttrs.isEmpty()) {
dropTableTriggers(entityType);
}
if (attr.isReadOnly() && !updatedAttr.isReadOnly()) {
readonlyTableAttrs.remove(attr.getName());
} else if (!attr.isReadOnly() && updatedAttr.isReadOnly()) {
readonlyTableAttrs.put(updatedAttr.getName(), updatedAttr);
}
if (!readonlyTableAttrs.isEmpty()) {
createTableTriggers(entityType, readonlyTableAttrs.values());
}
}
/** Return a new PostgreSQL repository */
private PostgreSqlRepository createPostgreSqlRepository(EntityType entityType) {
return new PostgreSqlRepository(postgreSqlEntityFactory, jdbcTemplate, dataSource, entityType);
}
private boolean isTableExists(EntityType entityType) {
return isTableExists(PostgreSqlNameGenerator.getTableName(entityType, false));
}
private boolean isTableExists(String tableName) {
Connection conn = null;
try {
conn = dataSource.getConnection();
DatabaseMetaData dbm = conn.getMetaData();
// DatabaseMetaData.getTables() requires table name without double quotes, only search TABLE
// table type to
// avoid matches with system tables
ResultSet tables = dbm.getTables(null, null, tableName, new String[] {"TABLE"});
return tables.next();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
closeConnection(conn);
}
}
private void updateNillable(EntityType entityType, Attribute attr, Attribute updatedAttr) {
if (attr.isNillable() && !updatedAttr.isNillable()) {
String sqlSetNotNull = getSqlSetNotNull(entityType, updatedAttr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating not null constraint for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlSetNotNull);
}
}
jdbcTemplate.execute(sqlSetNotNull);
} else if (!attr.isNillable() && updatedAttr.isNillable()) {
Attribute idAttr = entityType.getIdAttribute();
if (idAttr != null && idAttr.getName().equals(attr.getName())) {
throw new MolgenisDataException(
format(
"ID attribute [%s] of entity [%s] cannot be nullable",
attr.getName(), entityType.getId()));
}
String sqlDropNotNull = getSqlDropNotNull(entityType, updatedAttr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Removing not null constraint for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlDropNotNull);
}
}
jdbcTemplate.execute(sqlDropNotNull);
}
}
private void createTable(EntityType entityType) {
// create table
String createTableSql = getSqlCreateTable(entityType);
if (LOG.isDebugEnabled()) {
LOG.debug("Creating table for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createTableSql);
}
}
jdbcTemplate.execute(createTableSql);
createTableTriggers(entityType);
// create junction tables for attributes referencing multiple entities
createJunctionTables(entityType);
}
private void createTableTriggers(EntityType entityType) {
List readonlyTableAttrs = getTableAttributesReadonly(entityType).collect(toList());
if (!readonlyTableAttrs.isEmpty()) {
createTableTriggers(entityType, readonlyTableAttrs);
}
}
private void createTableTriggers(
EntityType entityType, Collection readonlyTableAttrs) {
String createFunctionSql = getSqlCreateFunctionValidateUpdate(entityType, readonlyTableAttrs);
if (LOG.isDebugEnabled()) {
LOG.debug("Creating update trigger function for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createFunctionSql);
}
}
jdbcTemplate.execute(createFunctionSql);
String createUpdateTriggerSql = getSqlCreateUpdateTrigger(entityType, readonlyTableAttrs);
if (LOG.isDebugEnabled()) {
LOG.debug("Creating update trigger for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createUpdateTriggerSql);
}
}
jdbcTemplate.execute(createUpdateTriggerSql);
}
private void updateTableTriggers(
EntityType entityType, Collection readonlyTableAttrs) {
dropTableTriggers(entityType);
createTableTriggers(entityType, readonlyTableAttrs);
}
private void dropTableTriggers(EntityType entityType) {
String dropUpdateTriggerSql = getSqlDropUpdateTrigger(entityType);
if (LOG.isDebugEnabled()) {
LOG.debug("Deleting update trigger for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", dropUpdateTriggerSql);
}
}
jdbcTemplate.execute(dropUpdateTriggerSql);
String dropFunctionValidateUpdateSql = getSqlDropFunctionValidateUpdate(entityType);
if (LOG.isDebugEnabled()) {
LOG.debug("Deleting update trigger function for entity [{}]", entityType.getId());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", dropFunctionValidateUpdateSql);
}
}
jdbcTemplate.execute(dropFunctionValidateUpdateSql);
}
private void createJunctionTables(EntityType entityType) {
getJunctionTableAttributes(entityType).forEach(attr -> createJunctionTable(entityType, attr));
}
private void createForeignKey(EntityType entityType, Attribute attr) {
String createForeignKeySql = getSqlCreateForeignKey(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating foreign key for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createForeignKeySql);
}
}
jdbcTemplate.execute(createForeignKeySql);
}
private void dropForeignKey(EntityType entityType, Attribute attr) {
String dropForeignKeySql = getSqlDropForeignKey(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Dropping foreign key for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", dropForeignKeySql);
}
}
jdbcTemplate.execute(dropForeignKeySql);
}
private void createUniqueKey(EntityType entityType, Attribute attr) {
String createUniqueKeySql = getSqlCreateUniqueKey(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating unique key for entity [{}] attribute [{}]", entityType.getId(), attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createUniqueKeySql);
}
}
jdbcTemplate.execute(createUniqueKeySql);
}
private void dropUniqueKey(EntityType entityType, Attribute attr) {
String dropUniqueKeySql = getSqlDropUniqueKey(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Dropping unique key for entity [{}] attribute [{}]", entityType.getId(), attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", dropUniqueKeySql);
}
}
jdbcTemplate.execute(dropUniqueKeySql);
}
private void createCheckConstraint(EntityType entityType, Attribute attr) {
String sqlCreateCheckConstraint = getSqlCreateCheckConstraint(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating check constraint for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlCreateCheckConstraint);
}
}
jdbcTemplate.execute(sqlCreateCheckConstraint);
}
private void dropCheckConstraint(EntityType entityType, Attribute attr) {
String sqlDropCheckConstraint = getSqlDropCheckConstraint(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Dropping check constraint for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlDropCheckConstraint);
}
}
jdbcTemplate.execute(sqlDropCheckConstraint);
}
private void createColumn(EntityType entityType, Attribute attr) {
String addColumnSql = getSqlAddColumn(entityType, attr, ColumnMode.INCLUDE_DEFAULT_CONSTRAINT);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating column for entity [{}] attribute [{}]", entityType.getId(), attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", addColumnSql);
}
}
jdbcTemplate.execute(addColumnSql);
if (generateSqlColumnDefaultConstraint(attr)) {
dropColumnDefaultValue(entityType, attr);
}
if (attr.isReadOnly()) {
Stream updatedTableAttrsReadonly;
Stream tableAttrsReadonly = getTableAttributesReadonly(entityType);
if (isTableAttribute(attr)) {
updatedTableAttrsReadonly = Stream.concat(tableAttrsReadonly, Stream.of(attr));
} else {
updatedTableAttrsReadonly = tableAttrsReadonly;
}
updateTableTriggers(entityType, updatedTableAttrsReadonly.collect(toList()));
}
}
private void dropColumn(EntityType entityType, Attribute attr) {
if (attr.isReadOnly()) {
Map updatedReadonlyTableAttrs =
getTableAttributesReadonly(entityType)
.collect(toLinkedMap(Attribute::getName, Function.identity()));
updatedReadonlyTableAttrs.remove(attr.getName());
updateTableTriggers(entityType, updatedReadonlyTableAttrs.values());
}
String dropColumnSql = getSqlDropColumn(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Dropping column for entity [{}] attribute [{}]", entityType.getId(), attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", dropColumnSql);
}
}
jdbcTemplate.execute(dropColumnSql);
}
private void updateColumnDataType(EntityType entityType, Attribute attr) {
String sqlSetDataType = getSqlSetDataType(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Changing data type of entity [{}] attribute [{}] to [{}]",
entityType.getId(),
attr.getName(),
attr.getDataType());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlSetDataType);
}
}
jdbcTemplate.execute(sqlSetDataType);
}
private void createJunctionTable(EntityType entityType, Attribute attr) {
String createJunctionTableSql = getSqlCreateJunctionTable(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating junction table for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createJunctionTableSql);
}
}
jdbcTemplate.execute(createJunctionTableSql);
String createJunctionTableIndexSql = getSqlCreateJunctionTableIndex(entityType, attr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Creating junction table index for entity [{}] attribute [{}]",
entityType.getId(),
attr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", createJunctionTableIndexSql);
}
}
jdbcTemplate.execute(createJunctionTableIndexSql);
}
private void dropJunctionTable(EntityType entityType, Attribute mrefAttr) {
String sqlDropJunctionTable = getSqlDropJunctionTable(entityType, mrefAttr);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Dropping junction table for entity [{}] attribute [{}]",
entityType.getId(),
mrefAttr.getName());
if (LOG.isTraceEnabled()) {
LOG.trace("SQL: {}", sqlDropJunctionTable);
}
}
jdbcTemplate.execute(sqlDropJunctionTable);
}
}