/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.datastore.neo4j.impl;
import static org.hibernate.ogm.datastore.neo4j.query.parsing.cypherdsl.impl.CypherDSL.escapeIdentifier;
import static org.neo4j.graphdb.DynamicLabel.label;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Constraint;
import org.hibernate.mapping.ForeignKey;
import org.hibernate.mapping.PrimaryKey;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.ogm.datastore.neo4j.logging.impl.Log;
import org.hibernate.ogm.datastore.neo4j.logging.impl.LoggerFactory;
import org.hibernate.ogm.datastore.spi.BaseSchemaDefiner;
import org.hibernate.ogm.datastore.spi.DatastoreProvider;
import org.hibernate.ogm.model.key.spi.IdSourceKeyMetadata;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy;
import org.jboss.logging.Logger.Level;
import org.neo4j.graphdb.Label;
/**
* Allows the reuse of the logic for the creation of unique constraints when the schema is created.
*
* @author Davide D'Alto
*/
public abstract class BaseNeo4jSchemaDefiner extends BaseSchemaDefiner {
public static class UniqueConstraintDetails {
private final Label label;
private final String property;
public UniqueConstraintDetails(Label label, String property) {
this.label = label;
this.property = property;
}
public Label getLabel() {
return label;
}
public String getProperty() {
return property;
}
/**
* @return the cypher query for the creation of the constraint
*/
public String asCypherQuery() {
StringBuilder queryBuilder = new StringBuilder( "CREATE CONSTRAINT ON (n:" );
escapeIdentifier( queryBuilder, label.name() );
queryBuilder.append( ") ASSERT n." );
escapeIdentifier( queryBuilder, property );
queryBuilder.append( " IS UNIQUE" );
String query = queryBuilder.toString();
return query;
}
@Override
public String toString() {
return "UniqueConstraintDetails [label=" + label + ", property=" + property + "]";
}
}
private static final Log log = LoggerFactory.getLogger();
protected void createUniqueConstraints(DatastoreProvider provider, Database database) {
List<UniqueConstraintDetails> constraints = new ArrayList<>();
for ( Namespace namespace : database.getNamespaces() ) {
for ( Table table : namespace.getTables() ) {
if ( table.isPhysicalTable() ) {
Label label = label( table.getName() );
PrimaryKey primaryKey = table.getPrimaryKey();
addConstraint( constraints, table, label, primaryKey );
@SuppressWarnings("unchecked")
Iterator<Column> columnIterator = table.getColumnIterator();
while ( columnIterator.hasNext() ) {
Column column = columnIterator.next();
if ( column.isUnique() ) {
constraints.add( new UniqueConstraintDetails( label, column.getName() ) );
}
}
Iterator<UniqueKey> uniqueKeyIterator = table.getUniqueKeyIterator();
while ( uniqueKeyIterator.hasNext() ) {
addConstraint( constraints, table, label, uniqueKeyIterator.next() );
}
}
}
}
createUniqueConstraintsIfMissing( provider, constraints );
}
private void addConstraint(List<UniqueConstraintDetails> constraints, Table table, Label label, Constraint constraint) {
if ( constraint != null ) {
// Neo4j does not store properties representing foreign key columns, so we don't need to create unique
// constraints for them
if ( !isAppliedToForeignColumns( table, constraint ) ) {
if ( constraint.getColumnSpan() == 1 ) {
String propertyName = constraint.getColumn( 0 ).getName();
constraints.add( new UniqueConstraintDetails( label, propertyName ) );
}
else if ( log.isEnabled( Level.WARN ) ) {
logMultipleColumnsWarning( table, constraint );
}
}
}
}
private boolean isAppliedToForeignColumns(Table table, Constraint constraint) {
List<?> constraintColumns = constraint.getColumns();
for ( Iterator<?> iterator = table.getForeignKeyIterator(); iterator.hasNext(); ) {
ForeignKey foreignKey = (ForeignKey) iterator.next();
List<?> foreignKeyColumns = foreignKey.getColumns();
for ( Object object : foreignKeyColumns ) {
if ( constraintColumns.contains( object ) ) {
// This constraint requires a foreign column
return true;
}
}
}
return false;
}
private void logMultipleColumnsWarning(Table table, Constraint constraint) {
StringBuilder builder = new StringBuilder();
for ( Iterator<Column> columnIterator = constraint.getColumnIterator(); columnIterator.hasNext(); ) {
Column column = columnIterator.next();
builder.append( ", " );
builder.append( column.getName() );
}
String columns = "[" + builder.substring( 2 ) + "]";
log.constraintSpanningMultipleColumns( constraint.getName(), table.getName(), columns );
}
protected List<Sequence> sequences(Database database) {
List<Sequence> sequences = new ArrayList<>();
for ( Namespace namespace : database.getNamespaces() ) {
for ( Sequence sequence : namespace.getSequences() ) {
sequences.add( sequence );
}
}
return sequences;
}
protected void createEntityConstraints(DatastoreProvider provider, Database database, Properties properties) {
UniqueConstraintSchemaUpdateStrategy constraintMethod = UniqueConstraintSchemaUpdateStrategy
.interpret( properties.get( Environment.UNIQUE_CONSTRAINT_SCHEMA_UPDATE_STRATEGY ) );
log.debugf( "%1$s property set to %2$s", Environment.UNIQUE_CONSTRAINT_SCHEMA_UPDATE_STRATEGY, constraintMethod );
if ( constraintMethod == UniqueConstraintSchemaUpdateStrategy.SKIP ) {
log.tracef( "Skipping generation of unique constraints" );
}
else {
createUniqueConstraints( provider, database );
}
}
@Override
public void initializeSchema(SchemaDefinitionContext context) {
SessionFactoryImplementor sessionFactoryImplementor = context.getSessionFactory();
ServiceRegistryImplementor registry = sessionFactoryImplementor.getServiceRegistry();
DatastoreProvider provider = registry.getService( DatastoreProvider.class );
List<Sequence> sequences = sequences( context.getDatabase() );
createSequences( sequences, context.getAllIdSourceKeyMetadata(), provider );
createEntityConstraints( provider, context.getDatabase(), sessionFactoryImplementor.getProperties() );
}
protected abstract void createSequences(List<Sequence> sequences, Set<IdSourceKeyMetadata> allIdSourceKeyMetadata, DatastoreProvider provider);
protected abstract void createUniqueConstraintsIfMissing( DatastoreProvider provider, List<UniqueConstraintDetails> constraints );
}