Skip to content

Commit

Permalink
"distinct" search operator (experimental)
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Feb 23, 2017
1 parent f6f74c6 commit 602548d
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 112 deletions.
Expand Up @@ -26,6 +26,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
* @author semancik
Expand Down Expand Up @@ -124,6 +125,18 @@ public class GetOperationOptions extends AbstractOptions implements Serializable
*/
private Long staleness;

/**
* Should the results be made distinct.
* Not all providers support this option.
*
* BEWARE:
* - may bring a potentially huge performance penalty
* - may interfere with paging (!)
*
* So please consider this option an EXPERIMENTAL, for now.
*/
private Boolean distinct;

public RetrieveOption getRetrieve() {
return retrieve;
}
Expand Down Expand Up @@ -436,6 +449,30 @@ public static boolean isMaxStaleness(GetOperationOptions options) {
return GetOperationOptions.getStaleness(options) == Long.MAX_VALUE;
}

public Boolean getDistinct() {
return distinct;
}

public void setDistinct(Boolean distinct) {
this.distinct = distinct;
}

public static boolean isDistinct(GetOperationOptions options) {
if (options == null) {
return false;
}
if (options.distinct == null) {
return false;
}
return options.distinct;
}

public static GetOperationOptions createDistinct() {
GetOperationOptions opts = new GetOperationOptions();
opts.setDistinct(true);
return opts;
}


public RelationalValueSearchQuery getRelationalValueSearchQuery() {
return relationalValueSearchQuery;
Expand All @@ -446,110 +483,31 @@ public void setRelationalValueSearchQuery(RelationalValueSearchQuery relationalV
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((allowNotFound == null) ? 0 : allowNotFound.hashCode());
result = prime * result + ((doNotDiscovery == null) ? 0 : doNotDiscovery.hashCode());
result = prime * result + ((noFetch == null) ? 0 : noFetch.hashCode());
result = prime * result + ((raw == null) ? 0 : raw.hashCode());
result = prime * result + ((readOnly == null) ? 0 : readOnly.hashCode());
result = prime * result
+ ((relationalValueSearchQuery == null) ? 0 : relationalValueSearchQuery.hashCode());
result = prime * result + ((resolve == null) ? 0 : resolve.hashCode());
result = prime * result + ((resolveNames == null) ? 0 : resolveNames.hashCode());
result = prime * result + ((retrieve == null) ? 0 : retrieve.hashCode());
result = prime * result + ((staleness == null) ? 0 : staleness.hashCode());
result = prime * result + ((tolerateRawData == null) ? 0 : tolerateRawData.hashCode());
return result;
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof GetOperationOptions))
return false;
GetOperationOptions that = (GetOperationOptions) o;
return retrieve == that.retrieve &&
Objects.equals(resolve, that.resolve) &&
Objects.equals(resolveNames, that.resolveNames) &&
Objects.equals(noFetch, that.noFetch) &&
Objects.equals(raw, that.raw) &&
Objects.equals(tolerateRawData, that.tolerateRawData) &&
Objects.equals(doNotDiscovery, that.doNotDiscovery) &&
Objects.equals(relationalValueSearchQuery, that.relationalValueSearchQuery) &&
Objects.equals(allowNotFound, that.allowNotFound) &&
Objects.equals(readOnly, that.readOnly) &&
Objects.equals(staleness, that.staleness) &&
Objects.equals(distinct, that.distinct);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
GetOperationOptions other = (GetOperationOptions) obj;
if (allowNotFound == null) {
if (other.allowNotFound != null) {
return false;
}
} else if (!allowNotFound.equals(other.allowNotFound)) {
return false;
}
if (doNotDiscovery == null) {
if (other.doNotDiscovery != null) {
return false;
}
} else if (!doNotDiscovery.equals(other.doNotDiscovery)) {
return false;
}
if (noFetch == null) {
if (other.noFetch != null) {
return false;
}
} else if (!noFetch.equals(other.noFetch)) {
return false;
}
if (raw == null) {
if (other.raw != null) {
return false;
}
} else if (!raw.equals(other.raw)) {
return false;
}
if (readOnly == null) {
if (other.readOnly != null) {
return false;
}
} else if (!readOnly.equals(other.readOnly)) {
return false;
}
if (relationalValueSearchQuery == null) {
if (other.relationalValueSearchQuery != null) {
return false;
}
} else if (!relationalValueSearchQuery.equals(other.relationalValueSearchQuery)) {
return false;
}
if (resolve == null) {
if (other.resolve != null) {
return false;
}
} else if (!resolve.equals(other.resolve)) {
return false;
}
if (resolveNames == null) {
if (other.resolveNames != null) {
return false;
}
} else if (!resolveNames.equals(other.resolveNames)) {
return false;
}
if (retrieve != other.retrieve) {
return false;
}
if (staleness == null) {
if (other.staleness != null) {
return false;
}
} else if (!staleness.equals(other.staleness)) {
return false;
}
if (tolerateRawData == null) {
if (other.tolerateRawData != null) {
return false;
}
} else if (!tolerateRawData.equals(other.tolerateRawData)) {
return false;
}
return true;
public int hashCode() {
return Objects
.hash(retrieve, resolve, resolveNames, noFetch, raw, tolerateRawData, doNotDiscovery, relationalValueSearchQuery,
allowNotFound, readOnly, staleness, distinct);
}

public GetOperationOptions clone() {
Expand All @@ -563,6 +521,7 @@ public GetOperationOptions clone() {
clone.allowNotFound = this.allowNotFound;
clone.readOnly = this.readOnly;
clone.staleness = this.staleness;
clone.distinct = this.distinct;
if (this.relationalValueSearchQuery != null) {
clone.relationalValueSearchQuery = this.relationalValueSearchQuery.clone();
}
Expand All @@ -581,6 +540,7 @@ public String toString() {
appendFlag(sb, "allowNotFound", allowNotFound);
appendFlag(sb, "readOnly", readOnly);
appendVal(sb, "staleness", staleness);
appendVal(sb, "distinct", distinct);
appendVal(sb, "relationalValueSearchQuery", relationalValueSearchQuery);
removeLastComma(sb);
sb.append(")");
Expand All @@ -606,7 +566,7 @@ public static Collection<SelectorOptions<GetOperationOptions>> fromRestOptions(L
return rv;
}

public static GetOperationOptions fromRestOptions(List<String> options){
public static GetOperationOptions fromRestOptions(List<String> options) {
if (options == null || options.isEmpty()){
return null;
}
Expand Down
Expand Up @@ -350,6 +350,10 @@ <T extends ObjectType> SearchResultMetadata searchObjectsIterative(Class<T> type
<T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query, OperationResult parentResult)
throws SchemaException;

<T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options,
OperationResult parentResult) throws SchemaException;

boolean isAnySubordinate(String upperOrgOid, Collection<String> lowerObjectOids) throws SchemaException;

<O extends ObjectType> boolean isDescendant(PrismObject<O> object, String orgOid) throws SchemaException;
Expand Down
Expand Up @@ -227,6 +227,15 @@ public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query,
return repository.countObjects(type, query, parentResult);
}

@Override
public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, OperationResult parentResult)
throws SchemaException {
// TODO use cached query result if applicable
log("Cache: PASS countObjects ({})", type.getSimpleName());
return repository.countObjects(type, query, options, parentResult);
}

public <T extends ObjectType> void modifyObject(Class<T> type, String oid, Collection<? extends ItemDelta> modifications,
OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException {
modifyObject(type, oid, modifications, null, parentResult);
Expand Down
Expand Up @@ -423,6 +423,11 @@ public <T extends ObjectType> void deleteObject(Class<T> type, String oid, Opera

@Override
public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query, OperationResult result) {
return countObjects(type, query, null, result);
}

public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) {
Validate.notNull(type, "Object type must not be null.");
Validate.notNull(result, "Operation result must not be null.");

Expand Down Expand Up @@ -451,7 +456,7 @@ public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query,

while (true) {
try {
return objectRetriever.countObjectsAttempt(type, query, subResult);
return objectRetriever.countObjectsAttempt(type, query, options, subResult);
} catch (RuntimeException ex) {
attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, subResult);
}
Expand Down
Expand Up @@ -317,8 +317,9 @@ public PrismObject<UserType> listAccountShadowOwnerAttempt(String accountOid, Op
return userType;
}

public <T extends ObjectType> int countObjectsAttempt(Class<T> type, ObjectQuery query, OperationResult result) {
LOGGER_PERFORMANCE.debug("> count objects {}", new Object[]{type.getSimpleName()});
public <T extends ObjectType> int countObjectsAttempt(Class<T> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) {
LOGGER_PERFORMANCE.debug("> count objects {}", type.getSimpleName());

int count = 0;

Expand All @@ -329,6 +330,9 @@ public <T extends ObjectType> int countObjectsAttempt(Class<T> type, ObjectQuery
session = baseHelper.beginReadOnlyTransaction();
Number longCount;
if (query == null || query.getFilter() == null) {
if (GetOperationOptions.isDistinct(SelectorOptions.findRootOptions(options))) {
throw new UnsupportedOperationException("Distinct option is not supported here"); // TODO
}
// this is 5x faster than count with 3 inner joins, it can probably improved also for queries which
// filters uses only properties from concrete entities like RUser, RRole by improving interpreter [lazyman]
SQLQuery sqlQuery = session.createSQLQuery("SELECT COUNT(*) FROM " + RUtil.getTableName(hqlType));
Expand All @@ -337,7 +341,7 @@ public <T extends ObjectType> int countObjectsAttempt(Class<T> type, ObjectQuery
RQuery rQuery;
if (isUseNewQueryInterpreter(query)) {
QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext);
rQuery = engine.interpret(query, type, null, true, session);
rQuery = engine.interpret(query, type, options, true, session);
} else {
QueryEngine engine = new QueryEngine(getConfiguration(), prismContext);
rQuery = engine.interpret(query, type, null, true, session);
Expand Down
Expand Up @@ -120,11 +120,19 @@ public RootHibernateQuery interpret(ObjectQuery query, Class<? extends Container
interpretPagingAndSorting(context, query, countingObjects);

RootHibernateQuery hibernateQuery = context.getHibernateQuery();
boolean distinct = GetOperationOptions.isDistinct(SelectorOptions.findRootOptions(options));

if (countingObjects) {
hibernateQuery.addProjectionElement(new ProjectionElement("count(*)"));
if (distinct) {
String rootAlias = hibernateQuery.getPrimaryEntityAlias();
hibernateQuery.addProjectionElement(new ProjectionElement("count(distinct " + rootAlias + ")"));
} else {
hibernateQuery.addProjectionElement(new ProjectionElement("count(*)"));
}
} else {
String rootAlias = hibernateQuery.getPrimaryEntityAlias();
hibernateQuery.setDistinct(distinct);

String rootAlias = hibernateQuery.getPrimaryEntityAlias();
hibernateQuery.addProjectionElement(new ProjectionElement(rootAlias + ".fullObject"));
// TODO other objects if parent is requested?
if (context.isObject()) {
Expand Down
Expand Up @@ -98,11 +98,15 @@ public void addCondition(Condition condition) {
conditions.add(condition);
}

public String getAsHqlText(int indent) {
public String getAsHqlText(int indent, boolean distinct) {
StringBuilder sb = new StringBuilder();

indent(sb, indent);
sb.append("select\n");
sb.append("select");
if (distinct) {
sb.append(" distinct");
}
sb.append("\n");
ProjectionElement.dumpToHql(sb, projectionElements, indent + 1); // we finish at the end of the last line (not at the new line)
sb.append("\n");

Expand Down
Expand Up @@ -50,6 +50,7 @@ public class RootHibernateQuery extends HibernateQuery {
private Integer maxResults;
private Integer firstResult;
private ResultTransformer resultTransformer;
private boolean distinct;

public RootHibernateQuery(JpaEntityDefinition primaryEntityDef) {
super(primaryEntityDef);
Expand Down Expand Up @@ -81,7 +82,7 @@ private String findFreeName(String prefix) {
}

public Query getAsHqlQuery(Session session) {
String text = getAsHqlText(0);
String text = getAsHqlText(0, distinct);
LOGGER.trace("HQL text generated:\n{}", text);
Query query = session.createQuery(text);
for (Map.Entry<String,QueryParameterValue> parameter : parameters.entrySet()) {
Expand Down Expand Up @@ -132,7 +133,11 @@ public void setResultTransformer(ResultTransformer resultTransformer) {
this.resultTransformer = resultTransformer;
}

public Condition createIsNull(String propertyPath) {
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}

public Condition createIsNull(String propertyPath) {
return new IsNullCondition(this, propertyPath);
}

Expand Down

0 comments on commit 602548d

Please sign in to comment.