/*
* 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.dialect.impl;
import static org.hibernate.ogm.datastore.neo4j.dialect.impl.NodeLabel.EMBEDDED;
import static org.hibernate.ogm.datastore.neo4j.dialect.impl.NodeLabel.ENTITY;
import static org.hibernate.ogm.datastore.neo4j.query.parsing.cypherdsl.impl.CypherDSL.escapeIdentifier;
import static org.hibernate.ogm.util.impl.EmbeddedHelper.isPartOfEmbedded;
import static org.hibernate.ogm.util.impl.EmbeddedHelper.split;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.ogm.dialect.spi.TupleTypeContext;
import org.hibernate.ogm.model.key.spi.AssociatedEntityKeyMetadata;
import org.hibernate.ogm.model.key.spi.EntityKey;
import org.hibernate.ogm.model.key.spi.EntityKeyMetadata;
import org.hibernate.ogm.util.impl.EmbeddedHelper;
/**
* @author Davide D'Alto
*/
public abstract class BaseNeo4jEntityQueries extends BaseNeo4jQueries {
/**
* The alias used when a query returns an entity as result.
*/
public static final String ENTITY_ALIAS = "owner";
public static final String EMBEDDED_ALIAS = "emb";
public static final String EMBEDDED_REL = "r";
private static final int CACHE_CAPACITY = 1000;
private static final int CACHE_CONCURRENCY_LEVEL = 20;
/**
* true if we the keys are mapped with a single property
*/
protected final boolean singlePropertyKey;
protected final String[] keyColumns;
protected final String multiGetQuery;
private final Map<String, String> findEmbeddedNodeQueries;
private final Map<String, String> removeEmbeddedPropertyQuery;
private final Map<String, String> removePropertyQueries;
private final BoundedConcurrentHashMap<String, String> updateEmbeddedPropertyQueryCache;
private final BoundedConcurrentHashMap<String, String> findAssociationQueryCache;
private final BoundedConcurrentHashMap<Integer, String> multiGetQueryCache;
private final String removeToOneAssociation;
private final String createEmbeddedNodeQuery;
private final String findEntityQuery;
private final String findEntityWithEmbeddedEndNodeQuery;
private final String findEntitiesQuery;
private final String findAssociationPartialQuery;
private final String createEntityQuery;
private final String createEntityWithPropertiesQuery;
private final String updateEntityProperties;
private final String removeEntityQuery;
private final String updateEmbeddedNodeQuery;
private final Map<String, String> updateToOneQuery;
private final Map<String, String> findAssociatedEntityQuery;
/**
* if {@code true} we are going to return the embedded nodes when an entity node is returned.
* This is used by the remote dialect to avoid a Rest call every time we need to get an embedded property.
*/
private final boolean includeEmbedded;
private final EntityKeyMetadata entityKeyMetadata;
public BaseNeo4jEntityQueries(EntityKeyMetadata entityKeyMetadata, TupleTypeContext tupleTypeContext, boolean includeEmbedded) {
this.entityKeyMetadata = entityKeyMetadata;
this.includeEmbedded = includeEmbedded;
this.updateEmbeddedPropertyQueryCache = new BoundedConcurrentHashMap<String, String>( CACHE_CAPACITY, CACHE_CONCURRENCY_LEVEL, BoundedConcurrentHashMap.Eviction.LIRS );
this.findAssociationQueryCache = new BoundedConcurrentHashMap<String, String>( CACHE_CAPACITY, CACHE_CONCURRENCY_LEVEL, BoundedConcurrentHashMap.Eviction.LIRS );
this.multiGetQueryCache = new BoundedConcurrentHashMap<Integer, String>( CACHE_CAPACITY, CACHE_CONCURRENCY_LEVEL, BoundedConcurrentHashMap.Eviction.LIRS );
this.findAssociationPartialQuery = initMatchOwnerEntityNode( entityKeyMetadata );
this.createEmbeddedNodeQuery = initCreateEmbeddedNodeQuery( entityKeyMetadata );
this.findEntityQuery = initFindEntityQuery( entityKeyMetadata, includeEmbedded );
this.findEntityWithEmbeddedEndNodeQuery = initFindEntityQueryWithEmbeddedEndNode( entityKeyMetadata );
this.findEntitiesQuery = initFindEntitiesQuery( entityKeyMetadata, includeEmbedded );
this.createEntityQuery = initCreateEntityQuery( entityKeyMetadata );
this.updateEntityProperties = initMatchOwnerEntityNode( entityKeyMetadata );
this.createEntityWithPropertiesQuery = initCreateEntityWithPropertiesQuery( entityKeyMetadata );
this.removeEntityQuery = initRemoveEntityQuery( entityKeyMetadata );
this.updateEmbeddedNodeQuery = initUpdateEmbeddedNodeQuery( entityKeyMetadata );
this.updateToOneQuery = initUpdateToOneQuery( entityKeyMetadata, tupleTypeContext );
this.findAssociatedEntityQuery = initFindAssociatedEntityQuery( entityKeyMetadata, tupleTypeContext );
this.findEmbeddedNodeQueries = initFindEmbeddedNodeQuery( entityKeyMetadata, tupleTypeContext );
this.multiGetQuery = initMultiGetEntitiesQuery( entityKeyMetadata, includeEmbedded );
this.removeEmbeddedPropertyQuery = initRemoveEmbeddedPropertyQuery( entityKeyMetadata, tupleTypeContext );
this.removePropertyQueries = initRemovePropertyQueries( entityKeyMetadata, tupleTypeContext );
this.removeToOneAssociation = initRemoveToOneAssociation( entityKeyMetadata, tupleTypeContext );
this.singlePropertyKey = entityKeyMetadata.getColumnNames().length == 1;
this.keyColumns = entityKeyMetadata.getColumnNames();
}
private String initRemoveToOneAssociation(EntityKeyMetadata entityKeyMetadata, TupleTypeContext tupleTypeContext) {
StringBuilder queryBuilder = new StringBuilder();
appendMatchOwnerEntityNode( queryBuilder, entityKeyMetadata );
queryBuilder.append( " -[r]-> (:" );
queryBuilder.append( NodeLabel.ENTITY );
queryBuilder.append( ") WHERE type(r) = {" );
queryBuilder.append( entityKeyMetadata.getColumnNames().length );
queryBuilder.append( "} DELETE r" );
return queryBuilder.toString();
}
private Map<String, String> initRemovePropertyQueries(EntityKeyMetadata entityKeyMetadata, TupleTypeContext tupleTypeContext) {
if ( tupleTypeContext == null ) {
return Collections.emptyMap();
}
Map<String, String> removeColumn = new HashMap<>();
for ( String column : tupleTypeContext.getSelectableColumns() ) {
if ( !column.contains( "." ) ) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append( "MATCH " );
appendEntityNode( "n", entityKeyMetadata, queryBuilder );
queryBuilder.append( " REMOVE n." );
escapeIdentifier( queryBuilder, column );
removeColumn.put( column, queryBuilder.toString() );
}
}
return Collections.unmodifiableMap( removeColumn );
}
/*
* This method will initialize the query string for a multi get.
* The query is different in the two scenarios:
* 1) the id is mapped on a single property:
*
* MATCH (n:ENTITY:table)
* WHERE n.id IN {0}
* RETURN n
*
* 2) id is mapped on multiple columns:
*
* MATCH (n:ENTITY:table)
* WHERE
*
* In this case the query depends on how many id we are retrieving and it will be completed later
*/
private static String initMultiGetEntitiesQuery(EntityKeyMetadata entityKeyMetadata, boolean includeEmbedded) {
StringBuilder queryBuilder = new StringBuilder( "MATCH " );
queryBuilder.append( "(" );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ":" );
queryBuilder.append( ENTITY );
queryBuilder.append( ":" );
appendLabel( entityKeyMetadata, queryBuilder );
queryBuilder.append( ") " );
queryBuilder.append( " WHERE " );
if ( entityKeyMetadata.getColumnNames().length == 1 ) {
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( "." );
escapeIdentifier( queryBuilder, entityKeyMetadata.getColumnNames()[0] );
queryBuilder.append( " IN {0}" );
appendGetEmbeddedNodesIfNeeded( includeEmbedded, queryBuilder );
if ( includeEmbedded ) {
queryBuilder.append( ", " );
queryBuilder.append( EMBEDDED_ALIAS );
}
}
return queryBuilder.toString();
}
/*
* MATCH (owner:ENTITY:Account {login: {0}}) -[:type]-> (e:EMBEDDED)
* REMOVE e.property
*/
private Map<String, String> initRemoveEmbeddedPropertyQuery(EntityKeyMetadata entityKeyMetadata, TupleTypeContext tupleTypeContext) {
if ( tupleTypeContext == null ) {
return Collections.emptyMap();
}
Map<String, String> removeColumn = new HashMap<>();
for ( String column : tupleTypeContext.getSelectableColumns() ) {
if ( EmbeddedHelper.isPartOfEmbedded( column ) ) {
if ( !removeColumn.containsKey( column ) ) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append( "MATCH " );
appendEntityNode( "n", entityKeyMetadata, queryBuilder );
String[] path = EmbeddedHelper.split( column );
for ( int i = 0; i < path.length - 1; i++ ) {
queryBuilder.append( "-[:" );
appendRelationshipType( queryBuilder, path[i] );
queryBuilder.append( "]->" );
if ( i == path.length - 2 ) {
queryBuilder.append( "(e:EMBEDDED) " );
}
else {
queryBuilder.append( "(:EMBEDDED) " );
}
}
queryBuilder.append( "REMOVE e." );
escapeIdentifier( queryBuilder, path[path.length - 1] );
queryBuilder.append( " WITH e " );
queryBuilder.append( "MATCH (e)<-[erel]-(a) " );
queryBuilder.append( "WHERE length(keys(e))=0 AND NOT ((e)-->()) " );
queryBuilder.append( "DELETE e, erel " );
queryBuilder.append( "WITH a " );
queryBuilder.append( "OPTIONAL MATCH path=(a)<-[r*]-(b:EMBEDDED), (b)<-[brel]-(), (x) " );
queryBuilder.append( "WHERE a:EMBEDDED AND length(keys(a))=0 AND NOT((a)<-[*]-(:EMBEDDED)-->()) AND NOT ((a)<-[*]-(x)<-[*]-(b)) AND length(keys(b))>0 " );
queryBuilder.append( "FOREACH (r in relationships(path) | DELETE r) " );
queryBuilder.append( "FOREACH (n in nodes(path) | DELETE n) " );
queryBuilder.append( "WITH a " );
queryBuilder.append( "MATCH (a)<-[arel]-() " );
queryBuilder.append( "WHERE length(keys(a))=0 AND a:EMBEDDED " );
queryBuilder.append( "DELETE arel, a " );
removeColumn.put( column, queryBuilder.toString() );
}
}
}
return Collections.unmodifiableMap( removeColumn );
}
private Map<String, String> initUpdateToOneQuery(EntityKeyMetadata ownerEntityKeyMetadata, TupleTypeContext tupleTypeContext) {
if ( tupleTypeContext != null ) {
Map<String, AssociatedEntityKeyMetadata> allAssociatedEntityKeyMetadata = tupleTypeContext.getAllAssociatedEntityKeyMetadata();
Map<String, String> queries = new HashMap<>( allAssociatedEntityKeyMetadata.size() );
for ( Entry<String, AssociatedEntityKeyMetadata> entry : allAssociatedEntityKeyMetadata.entrySet() ) {
String associationRole = tupleTypeContext.getRole( entry.getKey() );
AssociatedEntityKeyMetadata associatedEntityKeyMetadata = entry.getValue();
EntityKeyMetadata targetKeyMetadata = associatedEntityKeyMetadata.getEntityKeyMetadata();
StringBuilder queryBuilder = new StringBuilder( "MATCH " );
appendEntityNode( ENTITY_ALIAS, ownerEntityKeyMetadata, queryBuilder );
queryBuilder.append( ", " );
appendEntityNode( "target", targetKeyMetadata, queryBuilder, ownerEntityKeyMetadata.getColumnNames().length );
queryBuilder.append( " OPTIONAL MATCH (" );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ")" );
queryBuilder.append( " -[r:" );
appendRelationshipType( queryBuilder, associationRole );
queryBuilder.append( "]-> () DELETE r " );
queryBuilder.append( "CREATE (" );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ") -[:" );
appendRelationshipType( queryBuilder, associationRole );
queryBuilder.append( "]-> (target)" );
queries.put( associationRole, queryBuilder.toString() );
}
return queries;
}
return Collections.emptyMap();
}
private Map<String, String> initFindAssociatedEntityQuery(EntityKeyMetadata ownerEntityKeyMetadata, TupleTypeContext tupleTypeContext) {
if ( tupleTypeContext != null ) {
Map<String, AssociatedEntityKeyMetadata> allAssociatedEntityKeyMetadata = tupleTypeContext.getAllAssociatedEntityKeyMetadata();
Map<String, String> queries = new HashMap<>( allAssociatedEntityKeyMetadata.size() );
for ( Entry<String, AssociatedEntityKeyMetadata> entry : allAssociatedEntityKeyMetadata.entrySet() ) {
String associationRole = tupleTypeContext.getRole( entry.getKey() );
StringBuilder queryBuilder = new StringBuilder( "MATCH " );
appendEntityNode( ENTITY_ALIAS, ownerEntityKeyMetadata, queryBuilder );
queryBuilder.append( " -[r:" );
appendRelationshipType( queryBuilder, associationRole );
queryBuilder.append( "]-> (target)" );
queryBuilder.append( "RETURN target" );
queries.put( associationRole, queryBuilder.toString() );
}
return queries;
}
return Collections.emptyMap();
}
private Map<String, String> initFindEmbeddedNodeQuery(EntityKeyMetadata ownerEntityKeyMetadata, TupleTypeContext tupleTypeContext) {
if ( tupleTypeContext != null ) {
Map<String, String> queries = new HashMap<>();
List<String> selectableColumns = tupleTypeContext.getSelectableColumns();
for ( String column : selectableColumns ) {
if ( isPartOfEmbedded( column ) ) {
String embeddedPath = column.substring( 0, column.lastIndexOf( "." ) );
if ( !queries.containsKey( column ) ) {
String[] columnPath = EmbeddedHelper.split( column );
StringBuilder queryBuilder = new StringBuilder( "MATCH " );
appendEntityNode( ENTITY_ALIAS, ownerEntityKeyMetadata, queryBuilder );
for ( int i = 0; i < columnPath.length - 1; i++ ) {
queryBuilder.append( " -[:" );
appendRelationshipType( queryBuilder, columnPath[i] );
queryBuilder.append( "]-> (" );
if ( i == columnPath.length - 2 ) {
queryBuilder.append( "e" );
}
queryBuilder.append( ":" );
queryBuilder.append( EMBEDDED );
queryBuilder.append( ")" );
}
queryBuilder.append( " RETURN e" );
queries.put( embeddedPath, queryBuilder.toString() );
}
}
}
return Collections.unmodifiableMap( queries );
}
return Collections.emptyMap();
}
/*
* Example:
* MATCH (owner:ENTITY:table {id: {0}})
*/
private static String initMatchOwnerEntityNode(EntityKeyMetadata ownerEntityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder();
appendMatchOwnerEntityNode( queryBuilder, ownerEntityKeyMetadata );
return queryBuilder.toString();
}
/*
* Example:
*
* MATCH (owner:ENTITY:Car {`carId.maker`: {0}, `carId.model`: {1}}) -[r:tires]- (target)
* RETURN r, owner, target
*
* or for embedded associations:
*
* MATCH (owner:ENTITY:StoryGame {id: {0}}) -[:evilBranch]-> (:EMBEDDED) -[r:additionalEndings]-> (target:EMBEDDED)
* RETURN id(target), r, owner, target ORDER BY id(target)
*/
private String completeFindAssociationQuery(String relationshipType) {
StringBuilder queryBuilder = findAssociationPartialQuery( relationshipType );
queryBuilder.append( "RETURN id(target), r, " );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ", target ORDER BY id(target) " );
return queryBuilder.toString();
}
/*
* Example:
*
* MATCH (owner:ENTITY:Car {`carId.maker`: {0}, `carId.model`: {1}}) -[r:tires]- (target)
* OPTIONAL MATCH (target) -[x*1..]->(e:EMBEDDED)
* RETURN id(target), extract(n IN x| type(n)), x, e ORDER BY id(target)
*
* or for embedded associations:
*
* MATCH (owner:ENTITY:StoryGame {id: {0}}) -[:evilBranch]-> (:EMBEDDED) -[r:additionalEndings]-> (target:EMBEDDED)
* OPTIONAL MATCH (target) -[x*1..]->(e:EMBEDDED)
* RETURN id(target), extract(n IN x| type(n)), x, e ORDER BY id(target)
*/
protected String getFindAssociationTargetEmbeddedValues(String relationshipType) {
StringBuilder queryBuilder = findAssociationPartialQuery( relationshipType );
queryBuilder.append( "OPTIONAL MATCH (target) -[x*1..]->(e:EMBEDDED) " );
// Should we split this in two Queries?
queryBuilder.append( "RETURN id(target), extract(n IN x| type(n)), x, e ORDER BY id(target)" );
return queryBuilder.toString();
}
private StringBuilder findAssociationPartialQuery(String relationshipType) {
StringBuilder queryBuilder = new StringBuilder( findAssociationPartialQuery );
if ( isPartOfEmbedded( relationshipType ) ) {
String[] path = split( relationshipType );
int index = 0;
for ( String embeddedRelationshipType : path ) {
queryBuilder.append( " -[" );
if ( index == path.length - 1 ) {
queryBuilder.append( "r" );
}
queryBuilder.append( ":" );
appendRelationshipType( queryBuilder, embeddedRelationshipType );
queryBuilder.append( "]-> (" );
index++;
if ( index == path.length ) {
queryBuilder.append( "target" );
}
queryBuilder.append( ":" );
queryBuilder.append( EMBEDDED );
queryBuilder.append( ") " );
}
}
else {
queryBuilder.append( " -[r" );
queryBuilder.append( ":" );
appendRelationshipType( queryBuilder, relationshipType );
queryBuilder.append( "]- (target) " );
}
return queryBuilder;
}
/*
* Example: CREATE (n:EMBEDDED:table {id: {0}}) RETURN n
*/
private static String initCreateEmbeddedNodeQuery(EntityKeyMetadata entityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder( "CREATE " );
queryBuilder.append( "(n:" );
queryBuilder.append( EMBEDDED );
queryBuilder.append( ":" );
appendLabel( entityKeyMetadata, queryBuilder );
appendProperties( entityKeyMetadata, queryBuilder );
queryBuilder.append( ") RETURN n" );
return queryBuilder.toString();
}
/*
* This is only the first part of the query, the one related to the owner of the embedded. We need to know the
* embedded columns to create the whole query. Example: MERGE (owner:ENTITY:Example {id: {0}}) MERGE (owner)
*/
private static String initUpdateEmbeddedNodeQuery(EntityKeyMetadata entityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder( "MERGE " );
appendEntityNode( "owner", entityKeyMetadata, queryBuilder );
queryBuilder.append( " MERGE (owner)" );
return queryBuilder.toString();
}
/*
* Example: MATCH (owner:ENTITY:table {id: {0}}) RETURN owner
*/
private static String initFindEntityQuery(EntityKeyMetadata entityKeyMetadata, boolean includeEmbedded) {
StringBuilder queryBuilder = new StringBuilder();
appendMatchOwnerEntityNode( queryBuilder, entityKeyMetadata );
appendGetEmbeddedNodesIfNeeded( includeEmbedded, queryBuilder );
return queryBuilder.toString();
}
private static String initFindEntityQueryWithEmbeddedEndNode(EntityKeyMetadata entityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder();
appendMatchOwnerEntityNode( queryBuilder, entityKeyMetadata );
appendGetEmbeddedNodesIfNeeded( true, queryBuilder );
queryBuilder.append( ", " );
queryBuilder.append( EMBEDDED_ALIAS );
return queryBuilder.toString();
}
private static void appendGetEmbeddedNodesIfNeeded(boolean includeEmbedded, StringBuilder queryBuilder) {
if ( includeEmbedded ) {
appendOptionalMatchOwnerEmbeddedNodes( queryBuilder );
queryBuilder.append( " RETURN " );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ", r" );
}
else {
queryBuilder.append( " RETURN " );
queryBuilder.append( ENTITY_ALIAS );
}
}
private static void appendOptionalMatchOwnerEmbeddedNodes(StringBuilder queryBuilder) {
queryBuilder.append( " OPTIONAL MATCH (" );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ") -[r*]->(" );
queryBuilder.append( EMBEDDED_ALIAS );
queryBuilder.append( ":" );
queryBuilder.append( NodeLabel.EMBEDDED );
queryBuilder.append( ")" );
}
/*
* Example: MATCH (n:ENTITY:table) RETURN n
*/
private static String initFindEntitiesQuery(EntityKeyMetadata entityKeyMetadata, boolean includeEmbedded) {
StringBuilder queryBuilder = new StringBuilder( "MATCH " );
queryBuilder.append( "(" );
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( ":" );
queryBuilder.append( ENTITY );
queryBuilder.append( ":" );
appendLabel( entityKeyMetadata, queryBuilder );
queryBuilder.append( ")" );
appendOptionalMatchOwnerEmbeddedNodes( queryBuilder );
appendGetEmbeddedNodesIfNeeded( includeEmbedded, queryBuilder );
return queryBuilder.toString();
}
/*
* Example: CREATE (n:ENTITY:table {id: {0}}) RETURN n
*/
private static String initCreateEntityQuery(EntityKeyMetadata entityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder( "CREATE " );
appendEntityNode( ENTITY_ALIAS, entityKeyMetadata, queryBuilder );
queryBuilder.append( " RETURN " );
queryBuilder.append( ENTITY_ALIAS );
return queryBuilder.toString();
}
/*
* Example: CREATE (n:ENTITY:table {props}) RETURN n
*/
private static String initCreateEntityWithPropertiesQuery(EntityKeyMetadata entityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder( "CREATE " );
queryBuilder.append( "(n:" );
queryBuilder.append( ENTITY );
queryBuilder.append( ":" );
appendLabel( entityKeyMetadata, queryBuilder );
// We should not pass a map as parameter as Neo4j cannot cache the query plan for it
queryBuilder.append( " {props})" );
queryBuilder.append( " RETURN n" );
return queryBuilder.toString();
}
/*
* Example: MATCH (n:ENTITY:table {id: {0}}) OPTIONAL MATCH (n) - [r] - () DELETE n, r
*/
private static String initRemoveEntityQuery(EntityKeyMetadata entityKeyMetadata) {
StringBuilder queryBuilder = new StringBuilder( "MATCH " );
appendEntityNode( "n", entityKeyMetadata, queryBuilder );
queryBuilder.append( " OPTIONAL MATCH (n)-[r]->(e:EMBEDDED), path=(e)-[*0..]->(:EMBEDDED) " );
queryBuilder.append( " DELETE r " );
queryBuilder.append( " FOREACH (er IN relationships(path) | DELETE er) " );
queryBuilder.append( " FOREACH (en IN nodes(path) | DELETE en) " );
queryBuilder.append( " WITH n " );
queryBuilder.append( " OPTIONAL MATCH (n)-[r]-() " );
queryBuilder.append( " DELETE r,n " );
return queryBuilder.toString();
}
/*
* Example:
*
* MERGE (owner:ENTITY:Account {login: {0}})
* MERGE (owner) - [:homeAddress] -> (e:EMBEDDED)
* ON CREATE SET e.country = {1}
* ON MATCH SET e.country = {2}
*/
private String initUpdateEmbeddedColumnQuery(Object[] keyValues, String embeddedColumn) {
StringBuilder queryBuilder = new StringBuilder( getUpdateEmbeddedNodeQuery() );
String[] columns = appendEmbeddedNodes( embeddedColumn, queryBuilder );
queryBuilder.append( " ON CREATE SET e." );
escapeIdentifier( queryBuilder, columns[columns.length - 1] );
queryBuilder.append( " = {" );
queryBuilder.append( keyValues.length );
queryBuilder.append( "}" );
queryBuilder.append( " ON MATCH SET e." );
escapeIdentifier( queryBuilder, columns[columns.length - 1] );
queryBuilder.append( " = {" );
queryBuilder.append( keyValues.length + 1 );
queryBuilder.append( "}" );
return queryBuilder.toString();
}
/*
* Given an embedded properties path returns the cypher representation that can be appended to a MERGE or CREATE
* query.
*/
private static String[] appendEmbeddedNodes(String path, StringBuilder queryBuilder) {
String[] columns = split( path );
for ( int i = 0; i < columns.length - 1; i++ ) {
queryBuilder.append( " - [:" );
appendRelationshipType( queryBuilder, columns[i] );
queryBuilder.append( "] ->" );
if ( i < columns.length - 2 ) {
queryBuilder.append( " (e" );
queryBuilder.append( i );
queryBuilder.append( ":" );
queryBuilder.append( EMBEDDED );
queryBuilder.append( ") MERGE (e" );
queryBuilder.append( i );
queryBuilder.append( ")" );
}
}
queryBuilder.append( " (e:" );
queryBuilder.append( EMBEDDED );
queryBuilder.append( ")" );
return columns;
}
public String getUpdateEmbeddedColumnQuery(Object[] keyValues, String embeddedColumn) {
String query = updateEmbeddedPropertyQueryCache.get( embeddedColumn );
if ( query == null ) {
query = initUpdateEmbeddedColumnQuery( keyValues, embeddedColumn );
String cached = updateEmbeddedPropertyQueryCache.putIfAbsent( embeddedColumn, query );
if ( cached != null ) {
query = cached;
}
}
return query;
}
public String getFindAssociationQuery(String role) {
String query = findAssociationQueryCache.get( role );
if ( query == null ) {
query = completeFindAssociationQuery( role );
String cached = findAssociationQueryCache.putIfAbsent( role, query );
if ( cached != null ) {
query = cached;
}
}
return query;
}
/*
* When the id is mapped on several properties
*/
protected String getMultiGetQueryCacheQuery(EntityKey[] keys) {
int numberOfKeys = keys.length;
String query = multiGetQueryCache.get( numberOfKeys );
if ( query == null ) {
query = createMultiGetOnMultiplePropertiesId( numberOfKeys );
String cached = multiGetQueryCache.putIfAbsent( numberOfKeys, query );
if ( cached != null ) {
query = cached;
}
}
return query;
}
protected Map<String, Object> multiGetParams(EntityKey[] keys) {
// We assume only one metadata type
int numberOfColumnNames = keys[0].getColumnNames().length;
int numberOfParams = keys.length * numberOfColumnNames;
int counter = 0;
Map<String, Object> params = new HashMap<>( numberOfParams );
for ( int row = 0; row < keys.length; row++ ) {
for ( int col = 0; col < keys[row].getColumnValues().length; col++ ) {
params.put( String.valueOf( counter++ ), keys[row].getColumnValues()[col] );
}
}
return params;
}
/*
* Example:
*
* MATCH (n:ENTITY:table)
* WHERE (n.id.property1 = {0} AND n.id.property2 = {1} ) OR (n.id.property1 = {2} AND n.id.property2 = {3} )
* RETURN n
*/
private String createMultiGetOnMultiplePropertiesId(int keysNumber) {
StringBuilder builder = new StringBuilder( multiGetQuery );
int counter = 0;
for ( int row = 0; row < keysNumber; row++ ) {
builder.append( "(" );
for ( int col = 0; col < keyColumns.length; col++ ) {
builder.append( ENTITY_ALIAS );
builder.append( "." );
escapeIdentifier( builder, keyColumns[col] );
builder.append( " = {" );
builder.append( counter++ );
builder.append( "}" );
if ( col < keyColumns.length - 1 ) {
builder.append( " AND " );
}
}
builder.append( ")" );
if ( row < keysNumber - 1 ) {
builder.append( " OR " );
}
}
appendGetEmbeddedNodesIfNeeded( includeEmbedded, builder );
return builder.toString();
}
public String getUpdateEntityPropertiesQuery( Map<String, Object> properties ) {
StringBuilder queryBuilder = new StringBuilder( updateEntityProperties );
queryBuilder.append( " SET " );
int index = entityKeyMetadata.getColumnNames().length;
for ( Map.Entry<String, Object> entry : properties.entrySet() ) {
queryBuilder.append( ENTITY_ALIAS );
queryBuilder.append( "." );
escapeIdentifier( queryBuilder, entry.getKey() );
queryBuilder.append( " = {" );
queryBuilder.append( index );
queryBuilder.append( "}, " );
index++;
}
return queryBuilder.substring( 0, queryBuilder.length() - 2 );
}
public String getCreateEmbeddedNodeQuery() {
return createEmbeddedNodeQuery;
}
public String getFindEntityQuery() {
return findEntityQuery;
}
public String getFindEntityWithEmbeddedEndNodeQuery() {
return findEntityWithEmbeddedEndNodeQuery;
}
public String getFindEntitiesQuery() {
return findEntitiesQuery;
}
public String getFindAssociationPartialQuery() {
return findAssociationPartialQuery;
}
public String getCreateEntityQuery() {
return createEntityQuery;
}
public String getCreateEntityWithPropertiesQuery() {
return createEntityWithPropertiesQuery;
}
public String getRemoveEntityQuery() {
return removeEntityQuery;
}
public String getUpdateEmbeddedNodeQuery() {
return updateEmbeddedNodeQuery;
}
public String getUpdateToOneQuery(String associationRole) {
return updateToOneQuery.get( associationRole );
}
public String getFindAssociatedEntityQuery(String associationRole) {
return findAssociatedEntityQuery.get( associationRole );
}
public String getRemoveColumnQuery(String column) {
return removePropertyQueries.get( column );
}
public Map<String, String> getFindEmbeddedNodeQueries() {
return findEmbeddedNodeQueries;
}
public Map<String, String> getRemoveEmbeddedPropertyQuery() {
return removeEmbeddedPropertyQuery;
}
public Map<String, String> getRemovePropertyQueries() {
return removePropertyQueries;
}
public String getRemoveToOneAssociation() {
return removeToOneAssociation;
}
public String getUpdateEntityProperties() {
return updateEntityProperties;
}
public Map<String, String> getFindAssociatedEntityQuery() {
return findAssociatedEntityQuery;
}
}