Skip to content

Commit

Permalink
Support full SQL filters in square brackets
Browse files Browse the repository at this point in the history
  • Loading branch information
luigidellaquila committed Feb 16, 2016
1 parent a4aeaf8 commit c6fba8c
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@
import com.orientechnologies.orient.core.config.OStorageConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.record.*;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordElement.STATUS;
import com.orientechnologies.orient.core.db.record.ORecordLazyList;
import com.orientechnologies.orient.core.db.record.ORecordLazyMap;
import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue;
import com.orientechnologies.orient.core.db.record.ORecordLazySet;
import com.orientechnologies.orient.core.db.record.ORecordTrackedList;
import com.orientechnologies.orient.core.db.record.ORecordTrackedSet;
import com.orientechnologies.orient.core.db.record.OTrackedList;
import com.orientechnologies.orient.core.db.record.OTrackedMap;
import com.orientechnologies.orient.core.db.record.OTrackedSet;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.exception.OQueryParsingException;
import com.orientechnologies.orient.core.id.ORID;
Expand All @@ -39,6 +48,7 @@
import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerStringAbstract;
import com.orientechnologies.orient.core.sql.OSQLEngine;
import com.orientechnologies.orient.core.sql.OSQLHelper;
import com.orientechnologies.orient.core.sql.filter.OSQLPredicate;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;
import com.orientechnologies.orient.core.sql.method.OSQLMethod;
import com.orientechnologies.orient.core.type.tree.OMVRBTreeRIDSet;
Expand All @@ -47,12 +57,21 @@
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
* Helper class to manage documents.
*
*
* @author Luca Garulli
*/
public class ODocumentHelper {
Expand Down Expand Up @@ -82,7 +101,8 @@ public static void sort(List<? extends OIdentifiable> ioResultSet, List<OPair<St
}

@SuppressWarnings("unchecked")
public static <RET> RET convertField(final ODocument iDocument, final String iFieldName, final Class<?> iFieldType, Object iValue) {
public static <RET> RET convertField(final ODocument iDocument, final String iFieldName, final Class<?> iFieldType,
Object iValue) {
if (iFieldType == null)
return (RET) iValue;

Expand Down Expand Up @@ -401,14 +421,14 @@ else if (indexParts.size() > 1) {

} else if (OMultiValue.isMultiValue(value)) {
// MULTI VALUE
final Object index = getIndexPart(iContext, indexPart);
final Object index = getIndexPart(iContext, indexPart);
final String indexAsString = index != null ? index.toString() : null;

final List<String> indexParts = OStringSerializerHelper.smartSplit(indexAsString, ',');
final List<String> indexRanges = OStringSerializerHelper.smartSplit(indexAsString, '-');
final List<String> indexCondition = OStringSerializerHelper.smartSplit(indexAsString, '=', ' ');

if (indexParts.size() == 1 && indexRanges.size() == 1 && indexCondition.size() == 1) {
if (isFieldName(indexAsString)) {
// SINGLE VALUE
if (value instanceof Map<?, ?>)
value = getMapEntry((Map<String, ?>) value, index);
Expand All @@ -418,15 +438,19 @@ else if (Character.isDigit(indexAsString.charAt(0)))
// FILTER BY FIELD
value = getFieldValue(value, indexAsString, iContext);

} else if (indexParts.size() > 1) {
} else if (isListOfNumbers(indexParts)) {

// MULTI VALUES
final Object[] values = new Object[indexParts.size()];
for (int i = 0; i < indexParts.size(); ++i)
values[i] = OMultiValue.getValue(value, Integer.parseInt(indexParts.get(i)));
value = values;
if(indexParts.size() > 1){
value = values;
}else{
value = values[0];
}

} else if (indexRanges.size() > 1) {
} else if (isListOfNumbers(indexRanges)) {

// MULTI VALUES RANGE
String from = indexRanges.get(0);
Expand All @@ -441,22 +465,18 @@ else if (Character.isDigit(indexAsString.charAt(0)))
values[i - rangeFrom] = OMultiValue.getValue(value, i);
value = values;

} else if (!indexCondition.isEmpty()) {
} else {
// CONDITION
final String conditionFieldName = indexCondition.get(0);
Object conditionFieldValue = ORecordSerializerStringAbstract.getTypeValue(indexCondition.get(1));

if (conditionFieldValue instanceof String)
conditionFieldValue = OStringSerializerHelper.getStringContent(conditionFieldValue);

OSQLPredicate pred = new OSQLPredicate(indexAsString);
final HashSet<Object> values = new HashSet<Object>();

for (Object v : OMultiValue.getMultiValueIterable(value)) {
Object filtered = filterItem(conditionFieldName, conditionFieldValue, v);
if (filtered != null)
if (filtered instanceof Collection<?>)
values.addAll((Collection<? extends Object>) filtered);
else
values.add(filtered);
if (v instanceof OIdentifiable) {
Object result = pred.evaluate((OIdentifiable) v, (ODocument) ((OIdentifiable) v).getRecord(), iContext);
if (Boolean.TRUE.equals(result)) {
values.add(v);
}
}
}

if (values.isEmpty())
Expand Down Expand Up @@ -551,6 +571,52 @@ else if (v instanceof Map)
return (RET) value;
}

private static boolean isFieldName(String indexAsString) {
indexAsString = indexAsString.trim();
if (indexAsString.startsWith("`") && indexAsString.endsWith("`")) {
//quoted identifier
return !indexAsString.substring(1, indexAsString.length() - 1).contains("`");
}
boolean firstChar = true;
for (char c : indexAsString.toCharArray()) {
if (isLetter(c) || (isNumber(c) && !firstChar)) {
firstChar = false;
continue;
}
return false;
}
return true;
}

private static boolean isNumber(char c) {
return c >= '0' && c <= '9';
}

private static boolean isLetter(char c) {
if (c == '$' || c == '_' || c == '@') {
return true;
}
if (c >= 'a' && c <= 'z') {
return true;
}
if (c >= 'A' && c <= 'Z') {
return true;
}

return false;
}

private static boolean isListOfNumbers(List<String> list) {
for (String s : list) {
try {
Integer.parseInt(s);
} catch (NumberFormatException e) {
return false;
}
}
return true;
}

protected static Object getIndexPart(final OCommandContext iContext, final String indexPart) {
Object index = indexPart;
if (indexPart.indexOf(',') == -1 && (indexPart.charAt(0) == '"' || indexPart.charAt(0) == '\''))
Expand Down Expand Up @@ -595,9 +661,8 @@ protected static Object filterItem(final String iConditionFieldName, final Objec

/**
* Retrieves the value crossing the map with the dotted notation
*
* @param iKey
* Field(s) to retrieve. If are multiple fields, then the dot must be used as separator
*
* @param iKey Field(s) to retrieve. If are multiple fields, then the dot must be used as separator
* @return
*/
@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -877,8 +942,7 @@ public static boolean hasSameContentItem(final Object iCurrent, ODatabaseDocumen
* Makes a deep comparison field by field to check if the passed ODocument instance is identical as identity and content to the
* current one. Instead equals() just checks if the RID are the same.
*
* @param iOther
* ODocument instance
* @param iOther ODocument instance
* @return true if the two document are identical, otherwise false
* @see #equals(Object)
*/
Expand All @@ -891,9 +955,8 @@ public static boolean hasSameContentOf(final ODocument iCurrent, final ODatabase
/**
* Makes a deep comparison field by field to check if the passed ODocument instance is identical in the content to the current
* one. Instead equals() just checks if the RID are the same.
*
* @param iOther
* ODocument instance
*
* @param iOther ODocument instance
* @return true if the two document are identical, otherwise false
* @see #equals(Object)
*/
Expand Down Expand Up @@ -1392,7 +1455,7 @@ public static void deleteCrossRefs(final ORID iRid, final ODocument iContent) {
deleteCrossRefs(iRid, (ODocument) fieldValue);
} else if (OMultiValue.isMultiValue(fieldValue)) {
// MULTI-VALUE (COLLECTION, ARRAY OR MAP), CHECK THE CONTENT
for (final Iterator<?> it = OMultiValue.getMultiValueIterator(fieldValue); it.hasNext();) {
for (final Iterator<?> it = OMultiValue.getMultiValueIterator(fieldValue); it.hasNext(); ) {
final Object item = it.next();

if (fieldValue.equals(iRid)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,22 @@ public void beforeClass() throws Exception {
initLinkListSequence(db);
initMaxLongNumber(db);
initFilterAndOrderByTest(db);
initComplexFilterInSquareBrackets(db);
}

private void initComplexFilterInSquareBrackets(ODatabaseDocumentTx db) {
db.command(new OCommandSQL("CREATE CLASS ComplexFilterInSquareBrackets1")).execute();
db.command(new OCommandSQL("CREATE CLASS ComplexFilterInSquareBrackets2")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n1', value = 1")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n2', value = 2")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n3', value = 3")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n4', value = 4")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n5', value = 5")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n6', value = -1")).execute();
db.command(new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets1 SET name = 'n7', value = null")).execute();
db.command(
new OCommandSQL("INSERT INTO ComplexFilterInSquareBrackets2 SET collection = (select from ComplexFilterInSquareBrackets1)"))
.execute();
}

private void initFilterAndOrderByTest(ODatabaseDocumentTx db) {
Expand Down Expand Up @@ -1105,7 +1121,7 @@ public void testMaxLongNumber() {
assertEquals(results.size(), 0);
}

public void testFilterAndOrderBy(){
public void testFilterAndOrderBy() {
//issue http://www.prjhub.com/#/issues/6199

OSQLSynchQuery sql = new OSQLSynchQuery("SELECT FROM FilterAndOrderByTest WHERE active = true ORDER BY dc DESC");
Expand All @@ -1127,6 +1143,51 @@ public void testFilterAndOrderBy(){
assertEquals(cal.get(Calendar.YEAR), 2009);

}

public void testComplexFilterInSquareBrackets() {
//issues #513 #5451

OSQLSynchQuery sql = new OSQLSynchQuery("SELECT expand(collection[name = 'n1']) FROM ComplexFilterInSquareBrackets2");
List<ODocument> results = db.query(sql);
assertEquals(results.size(), 1);
assertEquals(results.iterator().next().field("name"), "n1");

sql = new OSQLSynchQuery("SELECT expand(collection[name = 'n1' and value = 1]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 1);
assertEquals(results.iterator().next().field("name"), "n1");

sql = new OSQLSynchQuery("SELECT expand(collection[name = 'n1' and value > 1]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 0);

sql = new OSQLSynchQuery("SELECT expand(collection[name = 'n1' or value = -1]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 2);
for (ODocument doc : results) {
assertTrue(doc.field("name").equals("n1") || doc.field("value").equals(-1));
}

sql = new OSQLSynchQuery("SELECT expand(collection[name = 'n1' and not value = 1]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 0);

sql = new OSQLSynchQuery("SELECT expand(collection[value < 0]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 1);
assertEquals(results.iterator().next().field("value"), -1);

sql = new OSQLSynchQuery("SELECT expand(collection[2]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 1);

sql = new OSQLSynchQuery("SELECT expand(collection[1-3]) FROM ComplexFilterInSquareBrackets2");
results = db.query(sql);
assertEquals(results.size(), 3);


}

private long indexUsages(ODatabaseDocumentTx db) {
final long oldIndexUsage;
try {
Expand Down

0 comments on commit c6fba8c

Please sign in to comment.