Skip to content

Commit

Permalink
FMWK-347 Support CTX parameter in custom queries (#736)
Browse files Browse the repository at this point in the history
* add support for CTX parameter in custom queries (using the format of Expression DSL)
* refactoring
* add/update tests
  • Loading branch information
agrgr authored May 6, 2024
1 parent e4ad264 commit 32e115e
Show file tree
Hide file tree
Showing 46 changed files with 976 additions and 598 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ static Predicate<KeyRecord> getDistinctPredicate(Query query) {

final Set<Object> distinctValues = ConcurrentHashMap.newKeySet();
distinctPredicate = kr -> {
final String distinctField = query.getCriteriaObject().getField();
final String distinctField = query.getCriteriaObject().getBinName();
if (kr.record == null || kr.record.bins == null) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package org.springframework.data.aerospike.index;

import com.aerospike.client.Value;
import com.aerospike.client.cdt.CTX;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
Expand Down Expand Up @@ -92,8 +91,11 @@ private CTX[] toCtxArray(String ctxString) {
if (!StringUtils.hasLength(ctxString)) return null;

String[] ctxTokens = ctxString.split("\\.");
CTX[] ctxArr = Arrays.stream(ctxTokens).filter(not(String::isEmpty))
.map(this::toCtx).filter(Objects::nonNull).toArray(CTX[]::new);
CTX[] ctxArr = Arrays.stream(ctxTokens)
.filter(not(String::isEmpty))
.map(AerospikeIndexResolverUtils::toCtx)
.filter(Objects::nonNull)
.toArray(CTX[]::new);

if (ctxTokens.length != ctxArr.length) {
throw new IllegalArgumentException("@Indexed annotation '" + ctxString + "' contains empty context");
Expand All @@ -102,10 +104,10 @@ private CTX[] toCtxArray(String ctxString) {
return ctxArr;
}

private enum CtxType {
protected enum CtxType {
MAP('}'), LIST(']');

private final char closingChar;
final char closingChar;

CtxType(char closingChar) {
this.closingChar = closingChar;
Expand All @@ -116,95 +118,4 @@ public String toString() {
return name().toLowerCase(); // when mentioned in exceptions
}
}

private CTX toCtx(String singleCtx) {
switch (singleCtx.charAt(0)) {
case '{' -> {
return processSingleCtx(singleCtx, CtxType.MAP);
}
case '[' -> {
return processSingleCtx(singleCtx, CtxType.LIST);
}
default -> {
Object res = isInDoubleOrSingleQuotes(singleCtx) ? singleCtx.substring(1, singleCtx.length() - 1) :
parseIntOrReturnStr(singleCtx);
return CTX.mapKey(Value.get(res));
}
}
}

private CTX processSingleCtx(String singleCtx, CtxType ctxType) {
int length = singleCtx.length();
if (length < 3) {
throw new IllegalArgumentException("@Indexed annotation: context string '" + singleCtx +
"' has no content");
}
if (singleCtx.charAt(length - 1) != ctxType.closingChar) {
throw new IllegalArgumentException("@Indexed annotation: brackets mismatch, " +
"expecting '" + ctxType.closingChar + "', got '" + singleCtx.charAt(length - 1) + "' instead");
}

CTX result;
String substring = singleCtx.substring(2, length - 1);
if (singleCtx.charAt(1) == '=' && length > 3) {
result = processCtxValue(substring, ctxType);
} else if (singleCtx.charAt(1) == '#' && length > 3) {
result = processCtxRank(substring, ctxType);
} else {
result = processCtxIndex(singleCtx, length, ctxType);
}

return result;
}

private CTX processCtxValue(String substring, CtxType ctxType) {
Object result = isInDoubleOrSingleQuotes(substring) ? substring.substring(1, substring.length() - 1) :
parseIntOrReturnStr(substring);
return switch (ctxType) {
case MAP -> CTX.mapValue(Value.get(result));
case LIST -> CTX.listValue(Value.get(result));
};
}

private CTX processCtxRank(String substring, CtxType ctxType) {
int rank = parseIntOrFail(substring, ctxType, "rank");
return switch (ctxType) {
case MAP -> CTX.mapRank(rank);
case LIST -> CTX.listRank(rank);
};
}

private CTX processCtxIndex(String singleCtx, int length, CtxType ctxType) {
String substring = singleCtx.substring(1, length - 1);
int idx = parseIntOrFail(substring, ctxType, "index");
return switch (ctxType) {
case MAP -> CTX.mapIndex(idx);
case LIST -> CTX.listIndex(idx);
};
}

private int parseIntOrFail(String substring, CtxType ctxType, String parameterName) {
try {
return Integer.parseInt(substring);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("@Indexed annotation " + ctxType + " " + parameterName + ": " +
"expecting only integer values, got '" + substring + "' instead");
}
}

private static Object parseIntOrReturnStr(String str) {
Object res;
try {
res = Integer.parseInt(str);
} catch (NumberFormatException e) {
res = str;
}

return res;
}

private static boolean isInDoubleOrSingleQuotes(String str) {
return str.length() > 2 && (str.charAt(0) == '"' || str.charAt(0) == '\'')
&& (str.charAt(str.length() - 1) == '"' || str.charAt(str.length() - 1) == '\'');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.springframework.data.aerospike.index;

import com.aerospike.client.Value;
import com.aerospike.client.cdt.CTX;

public class AerospikeIndexResolverUtils {

public static CTX toCtx(String singleCtx) {
switch (singleCtx.charAt(0)) {
case '{' -> {
return processSingleCtx(singleCtx, AerospikeIndexResolver.CtxType.MAP);
}
case '[' -> {
return processSingleCtx(singleCtx, AerospikeIndexResolver.CtxType.LIST);
}
default -> {
Object res = isInDoubleOrSingleQuotes(singleCtx) ? singleCtx.substring(1, singleCtx.length() - 1) :
parseIntOrReturnStr(singleCtx);
return CTX.mapKey(Value.get(res));
}
}
}

private static CTX processSingleCtx(String singleCtx, AerospikeIndexResolver.CtxType ctxType) {
int length = singleCtx.length();
if (length < 3) {
throw new IllegalArgumentException("@Indexed annotation: context string '" + singleCtx +
"' has no content");
}
if (singleCtx.charAt(length - 1) != ctxType.closingChar) {
throw new IllegalArgumentException("@Indexed annotation: brackets mismatch, " +
"expecting '" + ctxType.closingChar + "', got '" + singleCtx.charAt(length - 1) + "' instead");
}

CTX result;
String substring = singleCtx.substring(2, length - 1);
if (singleCtx.charAt(1) == '=' && length > 3) {
result = processCtxValue(substring, ctxType);
} else if (singleCtx.charAt(1) == '#' && length > 3) {
result = processCtxRank(substring, ctxType);
} else {
result = processCtxIndex(singleCtx, length, ctxType);
}

return result;
}

private static CTX processCtxValue(String substring, AerospikeIndexResolver.CtxType ctxType) {
Object result = isInDoubleOrSingleQuotes(substring) ? substring.substring(1, substring.length() - 1) :
parseIntOrReturnStr(substring);
return switch (ctxType) {
case MAP -> CTX.mapValue(Value.get(result));
case LIST -> CTX.listValue(Value.get(result));
};
}

private static CTX processCtxRank(String substring, AerospikeIndexResolver.CtxType ctxType) {
int rank = parseIntOrFail(substring, ctxType, "rank");
return switch (ctxType) {
case MAP -> CTX.mapRank(rank);
case LIST -> CTX.listRank(rank);
};
}

private static CTX processCtxIndex(String singleCtx, int length, AerospikeIndexResolver.CtxType ctxType) {
String substring = singleCtx.substring(1, length - 1);
int idx = parseIntOrFail(substring, ctxType, "index");
return switch (ctxType) {
case MAP -> CTX.mapIndex(idx);
case LIST -> CTX.listIndex(idx);
};
}

private static int parseIntOrFail(String substring, AerospikeIndexResolver.CtxType ctxType, String parameterName) {
try {
return Integer.parseInt(substring);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("@Indexed annotation " + ctxType + " " + parameterName + ": " +
"expecting only integer values, got '" + substring + "' instead");
}
}

private static Object parseIntOrReturnStr(String str) {
Object res;
try {
res = Integer.parseInt(str);
} catch (NumberFormatException e) {
res = str;
}

return res;
}

private static boolean isInDoubleOrSingleQuotes(String str) {
return str.length() > 2 && (str.charAt(0) == '"' || str.charAt(0) == '\'')
&& (str.charAt(str.length() - 1) == '"' || str.charAt(str.length() - 1) == '\'');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class ExpiryQualifier extends Qualifier {

public ExpiryQualifier(FilterOperation op, Value value) {
super(Qualifier.builder()
.setField(QueryEngine.Meta.EXPIRATION.toString())
.setBinName(QueryEngine.Meta.EXPIRATION.toString())
.setFilterOperation(op)
.setValue(value)
);
Expand Down
Loading

0 comments on commit 32e115e

Please sign in to comment.