Skip to content

Commit

Permalink
OGM-692 [Neo4j] Fix queries on embedded properties
Browse files Browse the repository at this point in the history
  • Loading branch information
DavideD committed Mar 13, 2015
1 parent a606804 commit d36732e
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 33 deletions.
Expand Up @@ -31,6 +31,12 @@ public static void identifier(StringBuilder builder, String identifier) {
escapeIdentifier( builder, identifier );
}

public static String identifier(String identifier, String propertyName) {
StringBuilder builder = new StringBuilder();
identifier( builder, identifier, propertyName );
return builder.toString();
}

public static StringBuilder identifier(StringBuilder builder, String identifier, String propertyName) {
identifier( builder, identifier );
if ( propertyName != null ) {
Expand All @@ -57,14 +63,24 @@ public static StringBuilder compare(StringBuilder builder, String operator, Obje
public static StringBuilder node(StringBuilder builder, String alias, String... labels) {
builder.append( "(" );
escapeIdentifier( builder, alias );
for ( String label : labels ) {
builder.append( ":" );
escapeIdentifier( builder, label );
if ( labels != null ) {
for ( String label : labels ) {
builder.append( ":" );
escapeIdentifier( builder, label );
}
}
builder.append( ")" );
return builder;
}


public static StringBuilder relationship(StringBuilder builder, String relationshipType) {
builder.append( " -[:" );
escapeIdentifier( builder, relationshipType );
builder.append( "]-> " );
return builder;
}

public static void collection(StringBuilder builder, List<Object> values) {
builder.append( "[" );
int counter = 1;
Expand Down
@@ -0,0 +1,84 @@
/*
* 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.query.parsing.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Tree structure that can be used to store alias of embedded components in Neo4j.
* <p>
* For example, a path to two properties like:
* <ol>
* <li>n.first.anotherEmbedded</li>
* <li>n.second.anotherEmbedded</li>
* </ol>
* Might be represented with the following structure:
* <pre>
* n (alias = n)|- first (alias = n_0) -- anotherEmbedded (alias = n_0_0)
* |
* -- second (alias = n_1) -- anotherEmbedded (alias = n_0_1)
* </pre>
*
* @author Davide D'Alto
*/
public class EmbeddedAliasTree {

private final String name;
private final String alias;
private final List<EmbeddedAliasTree> children;

/**
* Creates a tree node.
*
* @param alias the alias used for the embedded
* @param name the name of the property representing the emebedded
*/
public EmbeddedAliasTree(String alias, String name) {
this.name = name;
this.alias = alias;
this.children = new ArrayList<EmbeddedAliasTree>();
}

public EmbeddedAliasTree findChild(String name) {
for ( EmbeddedAliasTree child : children ) {
if ( child.getName().equals( name ) ) {
return child;
}
}
return null;
}

public String getName() {
return name;
}

public void addChild(EmbeddedAliasTree embeddedNode) {
children.add( embeddedNode );
}

public String getAlias() {
return alias;
}

public List<EmbeddedAliasTree> getChildren() {
return Collections.unmodifiableList( children );
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append( "[name=" );
builder.append( name );
builder.append( ", alias=" );
builder.append( alias );
builder.append( "]" );
return builder.toString();
}

}
Expand Up @@ -16,6 +16,7 @@
import org.hibernate.ogm.type.spi.GridType;
import org.hibernate.ogm.type.spi.TypeTranslator;
import org.hibernate.type.AbstractStandardBasicType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;

/**
Expand All @@ -37,11 +38,12 @@ public Neo4jPropertyHelper(SessionFactoryImplementor sessionFactory, EntityNames
public Object convertToPropertyType(String entityType, List<String> propertyPath, String value) {
//TODO Don't invoke for params

if ( propertyPath.size() > 1 ) {
throw new UnsupportedOperationException( "Queries on embedded/associated entities are not supported yet." );
Type propertyType = getPropertyType( entityType, propertyPath );

if ( propertyType.isEntityType() ) {
throw new UnsupportedOperationException( "Queries on associated entities are not supported yet." );
}
OgmEntityPersister persister = getPersister( entityType );
Type propertyType = persister.getPropertyType( propertyPath.get( propertyPath.size() - 1 ) );

if ( propertyType instanceof AbstractStandardBasicType ) {
return ( (AbstractStandardBasicType<?>) propertyType ).fromString( value );
}
Expand All @@ -50,12 +52,35 @@ public Object convertToPropertyType(String entityType, List<String> propertyPath
}
}

public Object convertToLiteral(String entityType, List<String> propertyPath, Object value) {
if ( propertyPath.size() > 1 ) {
throw new UnsupportedOperationException( "Queries on embedded/associated entities are not supported yet." );
}
private Type getPropertyType(String entityType, List<String> propertyPath) {
OgmEntityPersister persister = getPersister( entityType );
Type propertyType = persister.getPropertyType( propertyPath.get( propertyPath.size() - 1 ) );
String propertyName = propertyPath.get( 0 );
Type propertyType = persister.getPropertyType( propertyName );
if ( propertyPath.size() == 1 ) {
return propertyType;
}
else if ( propertyType.isComponentType() ) {
return embeddedPropertyType( propertyPath, (ComponentType) propertyType );
}
throw new UnsupportedOperationException( "Queries on associated entities are not supported yet." );
}

private Type embeddedPropertyType(List<String> propertyPath, ComponentType propertyType) {
Type subType = propertyType;
for ( int i = 1; i < propertyPath.size(); i++ ) {
ComponentType componentType = (ComponentType) subType;
String name = propertyPath.get( i );
int propertyIndex = componentType.getPropertyIndex( name );
subType = componentType.getSubtypes()[propertyIndex];
if ( subType.isAnyType() || subType.isAssociationType() || subType.isEntityType() ) {
throw new UnsupportedOperationException( "Queries on associated entities are not supported yet." );
}
}
return subType;
}

public Object convertToLiteral(String entityType, List<String> propertyPath, Object value) {
Type propertyType = getPropertyType( entityType, propertyPath );
Object gridValue = convertToGridType( value, propertyType );
return gridValue;
}
Expand All @@ -80,9 +105,14 @@ public String getColumnName(Class<?> entityType, String propertyName) {
}

public String getColumnName(OgmEntityPersister persister, String propertyName) {
String columnName = propertyName;
String[] columnNames = persister.getPropertyColumnNames( columnName );
columnName = columnNames[0];
String[] columnNames = persister.getPropertyColumnNames( propertyName );
String columnName = columnNames[0];
return columnName;
}

public String getEmbeddeColumnName(String entityType, String propertyPath) {
String columnName = getColumnName( entityType, propertyPath );
columnName = columnName.substring( columnName.lastIndexOf( '.' ) + 1, columnName.length() );
return columnName;
}

Expand All @@ -95,4 +125,9 @@ private OgmEntityPersister getPersister(String entityType) {
return (OgmEntityPersister) sessionFactory.getEntityPersister( targetedType.getName() );
}

public boolean isEmbedddedProperty(String targetTypeName, List<String> namesWithoutAlias) {
OgmEntityPersister persister = getPersister( targetTypeName );
Type propertyType = persister.getPropertyType( namesWithoutAlias.get( 0 ) );
return propertyType.isComponentType() && !propertyType.isAssociationType();
}
}
Expand Up @@ -9,6 +9,7 @@
import static org.hibernate.ogm.datastore.neo4j.query.parsing.cypherdsl.impl.CypherDSL.as;
import static org.hibernate.ogm.datastore.neo4j.query.parsing.cypherdsl.impl.CypherDSL.identifier;
import static org.hibernate.ogm.datastore.neo4j.query.parsing.cypherdsl.impl.CypherDSL.node;
import static org.hibernate.ogm.datastore.neo4j.query.parsing.cypherdsl.impl.CypherDSL.relationship;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -20,6 +21,7 @@
import org.hibernate.hql.ast.spi.SingleEntityQueryBuilder;
import org.hibernate.hql.ast.spi.SingleEntityQueryRendererDelegate;
import org.hibernate.hql.ast.spi.predicate.ComparisonPredicate.Type;
import org.hibernate.ogm.datastore.neo4j.dialect.impl.NodeLabel;
import org.hibernate.ogm.datastore.neo4j.query.parsing.impl.predicate.impl.Neo4jPredicateFactory;
import org.hibernate.ogm.model.key.spi.EntityKeyMetadata;
import org.hibernate.ogm.persister.impl.OgmEntityPersister;
Expand All @@ -35,13 +37,15 @@ public class Neo4jQueryRendererDelegate extends SingleEntityQueryRendererDelegat
private final Neo4jQueryResolverDelegate resolverDelegate;
private final SessionFactoryImplementor sessionFactory;
private List<OrderByClause> orderByExpressions;
private final List<String> embeddedPropertyProjection;

public Neo4jQueryRendererDelegate(SessionFactoryImplementor sessionFactory, Neo4jQueryResolverDelegate resolverDelegate, EntityNamesResolver entityNames,
Neo4jPropertyHelper propertyHelper, Map<String, Object> namedParameters) {
super( entityNames, singleEntityQueryBuilder( propertyHelper, resolverDelegate ), namedParameters );
this.sessionFactory = sessionFactory;
this.resolverDelegate = resolverDelegate;
this.propertyHelper = propertyHelper;
this.embeddedPropertyProjection = new ArrayList<String>();
}

private static SingleEntityQueryBuilder<StringBuilder> singleEntityQueryBuilder(Neo4jPropertyHelper propertyHelper,
Expand All @@ -60,6 +64,7 @@ public Neo4jQueryParsingResult getResult() {
String label = getKeyMetaData( targetType ).getTable();
StringBuilder queryBuilder = new StringBuilder();
match( queryBuilder, targetAlias, label );
optionalMatch( queryBuilder, targetAlias );
where( queryBuilder );
returns( queryBuilder, targetAlias );
orderBy( queryBuilder );
Expand All @@ -71,6 +76,33 @@ private void match(StringBuilder queryBuilder, String targetAlias, String label)
node( queryBuilder, targetAlias, label );
}

private void optionalMatch(StringBuilder queryBuilder, String targetAlias) {
EmbeddedAliasTree node = resolverDelegate.getAliasTree( targetAlias );
if ( node != null ) {
for ( EmbeddedAliasTree child : node.getChildren() ) {
StringBuilder optionalMatch = new StringBuilder( " OPTIONAL MATCH " );
node( optionalMatch, targetAlias );
relationship( optionalMatch, child.getName() );
node( optionalMatch, child.getAlias(), NodeLabel.EMBEDDED.name() );
appendEmbedded( queryBuilder, optionalMatch.toString(), child );
}
}
}

private void appendEmbedded(StringBuilder queryBuilder, String optionalMatch, EmbeddedAliasTree node) {
if ( node.getChildren().isEmpty() ) {
queryBuilder.append( optionalMatch );
}
else {
for ( EmbeddedAliasTree child : node.getChildren() ) {
StringBuilder builder = new StringBuilder( optionalMatch );
relationship( builder, child.getName() );
node( builder, child.getAlias(), NodeLabel.EMBEDDED.name() );
appendEmbedded( queryBuilder, builder.toString(), child );
}
}
}

private void where(StringBuilder queryBuilder) {
StringBuilder whereCondition = builder.build();
if ( whereCondition != null ) {
Expand Down Expand Up @@ -100,8 +132,13 @@ private void returns(StringBuilder builder, String targetAlias) {
else {
int counter = 1;
for ( String projection : projections ) {
identifier( builder, targetAlias, projection );
as( builder, projection );
if ( embeddedPropertyProjection.contains( projection ) ) {
builder.append( projection );
}
else {
identifier( builder, targetAlias, projection );
as( builder, projection );
}
if ( counter++ < projections.size() ) {
builder.append( ", " );
}
Expand All @@ -112,20 +149,43 @@ private void returns(StringBuilder builder, String targetAlias) {
@Override
public void setPropertyPath(PropertyPath propertyPath) {
if ( status == Status.DEFINING_SELECT ) {
// currently only support selecting non-nested properties (either qualified or unqualified)
if ( ( propertyPath.getNodes().size() == 1 && !propertyPath.getLastNode().isAlias() )
|| ( propertyPath.getNodes().size() == 2 && propertyPath.getNodes().get( 0 ).isAlias() ) ) {
if ( isSimpleProperty( propertyPath ) ) {
projections.add( propertyHelper.getColumnName( targetTypeName, propertyPath.asStringPathWithoutAlias() ) );
}
else if ( propertyPath.getNodes().size() != 1 ) {
throw new UnsupportedOperationException( "Selecting nested/associated properties not yet implemented." );
else if ( isNestedProperty( propertyPath ) ) {
if ( propertyHelper.isEmbedddedProperty( targetTypeName, propertyPath.getNodeNamesWithoutAlias() ) ) {
String entityAlias = propertyPath.getNodes().get( 0 ).getName();
String embeddedAlias = resolverDelegate.createAliasForEmbedded( entityAlias, propertyPath.getNodeNamesWithoutAlias() );
String columnName = propertyHelper.getEmbeddeColumnName( targetTypeName, propertyPath.asStringPathWithoutAlias() );
String projection = identifier( embeddedAlias, columnName );
projections.add( projection );
embeddedPropertyProjection.add( projection );
}
else {
throw new UnsupportedOperationException( "Selecting associated properties not yet implemented." );
}
}
}
else {
this.propertyPath = propertyPath;
}
}

/*
* Property path looks like: [alias, anEmbeddable, anotherEmbeddedable, propertyName]
*/
private boolean isNestedProperty(PropertyPath propertyPath) {
return propertyPath.getNodes().size() > 2 && propertyPath.getNodes().get( 0 ).isAlias();
}

/*
* Property path looks like: [propertyName] or [alias, propertyName]
*/
private boolean isSimpleProperty(PropertyPath propertyPath) {
return ( propertyPath.getNodes().size() == 1 && !propertyPath.getLastNode().isAlias() )
|| ( propertyPath.getNodes().size() == 2 && propertyPath.getNodes().get( 0 ).isAlias() );
}

@Override
protected void addSortField(PropertyPath propertyPath, String collateName, boolean isAscending) {
if ( orderByExpressions == null ) {
Expand Down

0 comments on commit d36732e

Please sign in to comment.