/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.bolt.index;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.structr.api.search.SortType;
/**
*
*/
public class CypherQuery {
private final Map<String, Object> parameters = new HashMap<>();
private final List<String> typeLabels = new LinkedList<>();
private final StringBuilder buffer = new StringBuilder();
private String sourceTypeLabel = null;
private String targetTypeLabel = null;
private AbstractCypherIndex<?> index = null;
private boolean sortDescending = false;
private SortType sortType = null;
private String sortKey = null;
private int count = 0;
public CypherQuery(final AbstractCypherIndex<?> index) {
this.index = index;
}
@Override
public String toString() {
return getStatement();
}
public int getHashCode() {
int hashCode = 23;
hashCode += 27 * typeLabels.hashCode();
hashCode += 37 * getStatement().hashCode();
hashCode += 47 * deepHashCode(parameters);
hashCode += 57 * sortKey.hashCode();
if (sortDescending) {
hashCode += 1;
}
return hashCode;
}
public String getStatement() {
final StringBuilder buf = new StringBuilder();
final int typeCount = typeLabels.size();
switch (typeCount) {
case 0:
buf.append(index.getQueryPrefix(null, sourceTypeLabel, targetTypeLabel));
if (buffer.length() > 0) {
buf.append(" WHERE ");
buf.append(buffer);
}
buf.append(index.getQuerySuffix());
break;
case 1:
buf.append(index.getQueryPrefix(typeLabels.get(0), sourceTypeLabel, targetTypeLabel));
if (buffer.length() > 0) {
buf.append(" WHERE ");
buf.append(buffer);
}
buf.append(index.getQuerySuffix());
break;
default:
// create UNION query
for (final Iterator<String> it = typeLabels.iterator(); it.hasNext();) {
buf.append(index.getQueryPrefix(it.next(), sourceTypeLabel, targetTypeLabel));
if (buffer.length() > 0) {
buf.append(" WHERE ");
buf.append(buffer);
}
buf.append(index.getQuerySuffix());
if (it.hasNext()) {
buf.append(" UNION ");
}
}
break;
}
if (sortKey != null) {
buf.append(" ORDER BY COALESCE(n.`");
buf.append(sortKey);
buf.append("`, ");
// COALESCE needs a correctly typed minimum value,
// so we need to supply a value based on the sort
// type.
switch (sortType) {
case Default:
// default is "String"
buf.append("''");
break;
default:
// other types are numeric
buf.append("-1");
}
buf.append(")");
if (sortDescending) {
buf.append(" DESC");
}
}
return buf.toString();
}
public Map<String, Object> getParameters() {
return parameters;
}
public void beginGroup() {
buffer.append("(");
}
public void endGroup() {
buffer.append(")");
}
public void and() {
buffer.append(" AND ");
}
public void not() {
buffer.append(" NOT ");
}
public void andNot() {
buffer.append(" AND NOT ");
}
public void or() {
buffer.append(" OR ");
}
public void noop() {
buffer.append(" True ");
}
public void typeLabel(final String typeLabel) {
this.typeLabels.add(typeLabel);
}
public void addSimpleParameter(final String key, final String operator, final Object value) {
addSimpleParameter(key, operator, value, true);
}
public void addSimpleParameter(final String key, final String operator, final Object value, final boolean isProperty) {
if (value != null) {
final String paramKey = "param" + count++;
if (isProperty) {
buffer.append("n.`");
}
buffer.append(key);
if (isProperty) {
buffer.append("` ");
} else {
buffer.append(" ");
}
buffer.append(operator);
buffer.append(" {");
buffer.append(paramKey);
buffer.append("}");
parameters.put(paramKey, value);
} else {
if (isProperty) {
buffer.append("n.`");
}
buffer.append(key);
if (isProperty) {
buffer.append("` ");
}
buffer.append(operator);
buffer.append(" Null");
}
}
public void addListParameter(final String key, final String operator, final Object value) {
if (value != null) {
final String paramKey = "param" + count++;
buffer.append("ANY(x IN n.`");
buffer.append(key);
buffer.append("` WHERE x ");
buffer.append(operator);
buffer.append(" {");
buffer.append(paramKey);
buffer.append("})");
parameters.put(paramKey, value);
} else {
buffer.append("ANY(x IN n.`");
buffer.append(key);
buffer.append("` WHERE x ");
buffer.append(operator);
buffer.append(" Null)");
}
}
public void addParameters(final String key, final String operator1, final Object value1, final String operator2, final Object value2) {
final String paramKey1 = "param" + count++;
final String paramKey2 = "param" + count++;
buffer.append("(n.`");
buffer.append(key);
buffer.append("` ");
buffer.append(operator1);
buffer.append(" {");
buffer.append(paramKey1);
buffer.append("}");
buffer.append(" AND ");
buffer.append("n.`");
buffer.append(key);
buffer.append("` ");
buffer.append(operator2);
buffer.append(" {");
buffer.append(paramKey2);
buffer.append("})");
parameters.put(paramKey1, value1);
parameters.put(paramKey2, value2);
}
public void sort(final SortType sortType, final String sortKey, final boolean sortDescending) {
this.sortDescending = sortDescending;
this.sortType = sortType;
this.sortKey = sortKey;
}
public void setSourceType(final String sourceTypeLabel) {
this.sourceTypeLabel = sourceTypeLabel;
}
public void setTargetType(final String targetTypeLabel) {
this.targetTypeLabel = targetTypeLabel;
}
private int deepHashCode(final Map<String, Object> map) {
final StringBuilder buf = new StringBuilder();
for (final Entry<String, Object> entry : map.entrySet()) {
buf.append("|");
buf.append(entry.getKey());
buf.append("|");
buf.append(entry.getValue());
}
return buf.toString().hashCode();
}
}