Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 74 additions & 21 deletions src/main/java/com/aerospike/dsl/DSLParser.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package com.aerospike.dsl;

import com.aerospike.client.exp.Expression;
import com.aerospike.client.query.Filter;
import com.aerospike.dsl.exception.AerospikeDSLException;

import java.util.List;

/**
* Contains API to convert dot separated String path into an Aerospike filter -
Expand All @@ -25,6 +21,7 @@ public interface DSLParser {
* <br><br>
* Examples:
* <table border="1">
* <caption>Path element</caption>
* <tr>
* <td> $.binName </td> <td> Bin “binName” </td>
* </tr>
Expand All @@ -47,9 +44,12 @@ public interface DSLParser {
* <td> {=bb} </td> <td> Map value “bb” </td>
* </tr>
* <tr>
* <td> {='1'} </td> <td> Map value (String) “1” </td>
* <td> {='1'} </td>
* <td> Map value (String) “1” </td>
* </tr>
* <td> {#1} </td> <td> Map rank 1 </td>
* <tr>
* <td> {#1} </td>
* <td> Map rank 1 </td>
* </tr>
* <tr>
* <td> [1] </td> <td> List index 1 </td>
Expand All @@ -59,12 +59,11 @@ public interface DSLParser {
* </tr>
* <tr>
* <td> [#1] </td> <td> List rank 1 </td>
* </tr>
* </table>
* <br>
* <table border="1">
* <tr>
* <td> $.binName </td> <td> [binName] </td>
* </tr>
* <caption>A nested element</caption>
* <tr>
* <td> $.mapBinName.k </td> <td> [mapBinName -> mapKey("a")] </td>
* </tr>
Expand All @@ -82,30 +81,84 @@ public interface DSLParser {
* mapKey("10")] </td>
* </tr>
* </table>
* @param input String consisting of dot separated elements, typically bin name and optional context
* @return Expression object
* @throws AerospikeDSLException in case of unsupported DSL String or invalid syntax
*
* @param dslString String consisting of dot separated elements, typically bin name and optional context
* @return {@link ParsedExpression} object
* @throws DslParseException in case of invalid syntax
*/
Expression parseExpression(String input);
ParsedExpression parseExpression(String dslString);

/**
* Parse String DSL path into Aerospike secondary index Filter.
* Parse String DSL path into Aerospike filter Expression.
* <br><br>
* Examples:
* <table border="1">
* <caption>Path element</caption>
* <tr>
* <td> $.binName </td> <td> Bin “binName” </td>
* </tr>
* <tr>
* <td> a </td> <td> Map key “a” </td>
* </tr>
* <tr>
* <td> '1' </td> <td> Map key (String) “1” </td>
* </tr>
* <tr>
* <td> 1 </td> <td> Map key 1 </td>
* </tr>
* <tr>
* <td> {1} </td> <td> Map index 1 </td>
* </tr>
* <tr>
* <td> {=1} </td> <td> Map value (int) 1 </td>
* </tr>
* <tr>
* <td> {=bb} </td> <td> Map value “bb” </td>
* </tr>
* <tr>
* <td> $.intBin1 == 10 </td> <td> Filter.equal("intBin1", 10) </td>
* <td> {='1'} </td>
* <td> Map value (String) “1” </td>
* </tr>
* <tr>
* <td> $.intBin1 > 10 </td> <td> Filter.range("intBin1", 11, Long.MAX_VALUE) </td>
* <td> {#1} </td>
* <td> Map rank 1 </td>
* </tr>
* <tr>
* <td> $.stringBin1 == 'text' </td> <td> Filter.equal("stringBin1", "text") </td>
* <td> [1] </td> <td> List index 1 </td>
* </tr>
* <tr>
* <td> [=1] </td> <td> List value 1 </td>
* </tr>
* <tr>
* <td> [#1] </td> <td> List rank 1 </td>
* </tr>
* </table>
* <br>
* <table border="1">
* <caption>A nested element</caption>
* <tr>
* <td> $.mapBinName.k </td> <td> [mapBinName -> mapKey("a")] </td>
* </tr>
* <tr>
* <td> $.mapBinName.a.aa.aaa </td> <td> [mapBinName -> mapKey("a") -> mapKey("aa") -> mapKey("aaa")] </td>
* </tr>
* <tr>
* <td> $.mapBinName.a.55 </td> <td> [mapBinName -> mapKey("a") -> mapKey(55)] </td>
* </tr>
* <tr>
* <td> $.listBinName.[1].aa </td> <td> [listBinName -> listIndex(1) -> mapKey("aa")] </td>
* </tr>
* <tr>
* <td> $.mapBinName.ab.cd.[-1].'10' </td> <td> [mapBinName -> mapKey("ab") -> mapKey("cd") -> listIndex(-1) ->
* mapKey("10")] </td>
* </tr>
* </table>
* @param input String consisting of dot separated elements, typically bin name and value
* @return List of Filter objects
* @throws AerospikeDSLException in case of unsupported DSL String or invalid syntax
*
* @param dslString String consisting of dot separated elements, typically bin name and optional context
* @param indexContext Class containing namespace and collection of {@link Index} objects that represent
* existing secondary indexes. Required for creating {@link Filter}. Can be null
* @return {@link ParsedExpression} object
* @throws DslParseException in case of invalid syntax
*/
List<Filter> parseFilters(String input);
ParsedExpression parseExpression(String dslString, IndexContext indexContext);
}
66 changes: 38 additions & 28 deletions src/main/java/com/aerospike/dsl/DSLParserImpl.java
Original file line number Diff line number Diff line change
@@ -1,50 +1,60 @@
package com.aerospike.dsl;

import com.aerospike.client.exp.Exp;
import com.aerospike.client.exp.Expression;
import com.aerospike.client.query.Filter;
import com.aerospike.dsl.annotation.Beta;
import com.aerospike.dsl.exception.AerospikeDSLException;
import com.aerospike.dsl.model.AbstractPart;
import com.aerospike.dsl.parts.AbstractPart;
import com.aerospike.dsl.visitor.ExpressionConditionVisitor;
import com.aerospike.dsl.visitor.FilterConditionVisitor;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class DSLParserImpl implements DSLParser {

@Beta
public Expression parseExpression(String input) {
ConditionLexer lexer = new ConditionLexer(CharStreams.fromString(input));
ConditionParser parser = new ConditionParser(new CommonTokenStream(lexer));
ParseTree tree = parser.parse();

ExpressionConditionVisitor visitor = new ExpressionConditionVisitor();
AbstractPart abstractPart = visitor.visit(tree);

// When we can't identify a specific case of syntax error, we throw a generic DSL syntax error instead of NPE
if (abstractPart == null) {
throw new AerospikeDSLException("Could not parse given input, wrong syntax");
}
Exp expResult = abstractPart.getExp();
return Exp.build(expResult);
public ParsedExpression parseExpression(String dslString) {
ParseTree parseTree = getParseTree(dslString);
return getParsedExpression(parseTree, null);
}

@Beta
public List<Filter> parseFilters(String input) {
public ParsedExpression parseExpression(String input, IndexContext indexContext) {
ParseTree parseTree = getParseTree(input);
return getParsedExpression(parseTree, indexContext);
}

private ParseTree getParseTree(String input) {
ConditionLexer lexer = new ConditionLexer(CharStreams.fromString(input));
ConditionParser parser = new ConditionParser(new CommonTokenStream(lexer));
ParseTree tree = parser.parse();

FilterConditionVisitor visitor = new FilterConditionVisitor();
AbstractPart abstractPart = visitor.visit(tree);
return parser.parse();
}

if (abstractPart == null) {
throw new AerospikeDSLException("Could not parse given input, wrong syntax");
private ParsedExpression getParsedExpression(ParseTree parseTree, IndexContext indexContext) {
final String namespace = Optional.ofNullable(indexContext)
.map(IndexContext::getNamespace)
.orElse(null);
final Collection<Index> indexes = Optional.ofNullable(indexContext)
.map(IndexContext::getIndexes)
.orElse(Collections.emptyList());

Map<String, List<Index>> indexesMap = indexes.stream()
// Filtering the indexes with the given namespace
.filter(idx -> idx.getNamespace() != null && idx.getNamespace().equals(namespace))
// Group the indexes by bin name
.collect(Collectors.groupingBy(Index::getBin));

AbstractPart resultingPart = new ExpressionConditionVisitor().visit(parseTree);

// When we can't identify a specific case of syntax error, we throw a generic DSL syntax error
if (resultingPart == null) {
throw new DslParseException("Could not parse given DSL expression input");
}
return abstractPart.getSIndexFilter().getFilters();
// Return the parsed tree along with indexes Map
return new ParsedExpression(resultingPart, indexesMap);
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/aerospike/dsl/DslParseException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.aerospike.dsl;

/**
* Represents a general processing exception that can occur during DSL expression parsing.
* It is typically not expected to be caught by the caller, but rather indicates a potentially
* unrecoverable issue like invalid input, failing validation or unsupported functionality.
*/
public class DslParseException extends RuntimeException {

public DslParseException(String description) {
super(description);
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/aerospike/dsl/Index.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.aerospike.dsl;

import com.aerospike.client.query.IndexType;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;

/**
* This class represents a secondary index created in the cluster.
*/
@Builder
@EqualsAndHashCode
@Getter
public class Index {

/**
* Namespace of the indexed bin
*/
private final String namespace;
/**
* Name of the indexed bin
*/
private final String bin;
/**
* {@link IndexType} of the index
*/
private final IndexType indexType;
/**
* Cardinality of the index calculated using "sindex-stat" command and looking at the ratio of entries
* to unique bin values for the given secondary index on the node (entries_per_bval)
*
*/
private int binValuesRatio;
}
26 changes: 26 additions & 0 deletions src/main/java/com/aerospike/dsl/IndexContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.aerospike.dsl;

import com.aerospike.client.query.Filter;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Collection;

/**
* This class stores namespace and indexes required to build secondary index {@link Filter}.
*/
@AllArgsConstructor(staticName = "of")
@Getter
public class IndexContext {

/**
* Namespace to be used for creating {@link Filter}. Is matched with namespace of indexes
*/
private String namespace;
/**
* Collection of {@link Index} objects to be used for creating {@link Filter}.
* Namespace of indexes is matched with the given {@link #namespace}, bin name and index type are matched
* with bins in DSL String
*/
private Collection<Index> indexes;
}
24 changes: 24 additions & 0 deletions src/main/java/com/aerospike/dsl/ParseResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.aerospike.dsl;

import com.aerospike.client.exp.Exp;
import com.aerospike.client.query.Filter;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* This class stores result of parsing DSL expression using {@link DSLParserImpl#parseExpression}
* in form of Java client's secondary index {@link Filter} and filter {@link Exp}.
*/
@AllArgsConstructor
@Getter
public class ParseResult {

/**
* Secondary index {@link Filter}. Can be null in case of invalid or unsupported DSL string
*/
Filter filter;
/**
* Filter {@link Exp}. Can be null in case of invalid or unsupported DSL string
*/
Exp exp;
}
59 changes: 59 additions & 0 deletions src/main/java/com/aerospike/dsl/ParsedExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.aerospike.dsl;

import com.aerospike.client.exp.Exp;
import com.aerospike.client.query.Filter;
import com.aerospike.dsl.annotation.Beta;
import com.aerospike.dsl.parts.AbstractPart;
import com.aerospike.dsl.parts.ExpressionContainer;
import lombok.Getter;

import java.util.List;
import java.util.Map;

import static com.aerospike.dsl.parts.AbstractPart.PartType.EXPRESSION_CONTAINER;
import static com.aerospike.dsl.visitor.VisitorUtils.buildExpr;


/**
* A class to build and store the results of DSL expression parsing: {@link ParseResult} that holds
* a potential secondary index {@link Filter} and a potential filter {@link Exp}, and parsed {@code expressionTree}.
*/
@Beta
@Getter
public class ParsedExpression {

private final AbstractPart expressionTree;
private final Map<String, List<Index>> indexesMap;
private ParseResult result;

public ParsedExpression(AbstractPart expressionTree, Map<String, List<Index>> indexesMap) {
this.expressionTree = expressionTree;
this.indexesMap = indexesMap;
}

/**
* @return Pair of secondary index {@link Filter} and filter {@link Exp}. Each can be null in case of invalid or
* unsupported DSL string
* @throws DslParseException If there was an error
*/
public ParseResult getResult() {
if (result == null) {
result = getParseResult();
}
return result;
}

private ParseResult getParseResult() {
if (expressionTree != null) {
if (expressionTree.getPartType() == EXPRESSION_CONTAINER) {
AbstractPart resultPart = buildExpr((ExpressionContainer) expressionTree, indexesMap);
return new ParseResult(resultPart.getFilter(), resultPart.getExp());
} else {
Filter filter = expressionTree.getFilter();
Exp exp = expressionTree.getExp();
return new ParseResult(filter, exp);
}
}
return new ParseResult(null, null);
}
}
Loading
Loading