Skip to content

Commit

Permalink
query language and parser added
Browse files Browse the repository at this point in the history
  • Loading branch information
vasily-kartashov committed Jan 31, 2016
1 parent 8e15c3e commit 279b69d
Show file tree
Hide file tree
Showing 6 changed files with 385 additions and 3 deletions.
36 changes: 33 additions & 3 deletions pom.xml
Expand Up @@ -37,12 +37,22 @@
<artifactId>hibernate-spatial</artifactId>
<version>${hibernate.version}</version>
</dependency>

<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr.version}</version>
</dependency>
<dependency>
<groupId>org.jscience</groupId>
<artifactId>jscience</artifactId>
<version>4.3.1</version>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
<hibernate.version>5.0.7.Final</hibernate.version>
<antlr.version>4.5.1</antlr.version>
</properties>

<build>
Expand All @@ -58,20 +68,40 @@
<forkMode>never</forkMode>
</configuration>
</plugin>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr.version}</version>
<configuration>
<visitor>true</visitor>
<outputDirectory>
${project.build.directory}/generated-sources/com/kartashov/postgis/antlr
</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>org.jboss.repository.releases</id>
<name>JBoss Maven Release Repository</name>
<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
</repository>
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
</repository>
</repositories>

<pluginRepositories>
Expand Down
79 changes: 79 additions & 0 deletions src/main/antlr4/Query.g4
@@ -0,0 +1,79 @@
grammar Query;

@header {
package com.kartashov.postgis.antlr;
}

AND : ',' | 'and' ;
OR : 'or' ;
INT : '-'? [0-9]+ ;
DOUBLE : '-'? [0-9]+'.'[0-9]+ ;
WITHIN : 'within' ;
FROM : 'from' ;
ID : [a-zA-Z_][a-zA-Z_0-9]* ;
STRING : '"' (~["])* '"' | '\'' (~['])* '\''
{
String s = getText();
setText(s.substring(1, s.length() - 1));
}
;
EQ : '=' '=' ? ;
LE : '<=' ;
GE : '>=' ;
NE : '!=' ;
LT : '<' ;
GT : '>' ;
SEP : '.' ;
WS : [ \t\r\n]+ -> skip ;

query : expression ;

expression
: expression AND expression # AndExpression
| expression OR expression # OrExpression
| predicate # PredicateExpression
| '(' expression ')' # BracketExpression
;

reference : element (SEP element)* ;

element : ID ;

predicate
: reference WITHIN amount FROM location # LocationPredicate
| reference operator term # OperatorPredicate
;

location : '(' latitude ',' longitude ')' ;

latitude : DOUBLE ;
longitude : DOUBLE ;

term
: reference
| value
| amount
;

operator
: LE
| GE
| NE
| LT
| GT
| EQ
;

amount : value unit ;

value
: INT # IntegerValue
| DOUBLE # DoubleValue
| STRING # StringValue
| ID # StringValue
;

unit :
| '%'
| ID
;
5 changes: 5 additions & 0 deletions src/main/java/com/kartashov/postgis/entities/Status.java
Expand Up @@ -21,4 +21,9 @@ public String getLifeCycle() {
public void setLifeCycle(String lifeCycle) {
this.lifeCycle = lifeCycle;
}

@Override
public String toString() {
return "{ stateOfCharge: " + stateOfCharge + ", lifeCycle: " + lifeCycle + " }";
}
}
150 changes: 150 additions & 0 deletions src/main/java/com/kartashov/postgis/search/QueryVisitor.java
@@ -0,0 +1,150 @@
package com.kartashov.postgis.search;

import com.kartashov.postgis.antlr.QueryBaseVisitor;
import com.kartashov.postgis.antlr.QueryParser;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import org.jscience.physics.amount.Amount;

import javax.measure.quantity.Length;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class QueryVisitor extends QueryBaseVisitor<String> {

private static final GeometryFactory geometryFactory = new GeometryFactory();

private final Map<String, Object> parameters = new HashMap<>();

public Map<String, Object> getParameters() {
return parameters;
}

private String addParameter(Object value) {
String name = "var" + parameters.size();
parameters.put(name, value);
return name;
}

@Override
public String visitQuery(QueryParser.QueryContext ctx) {
return "SELECT d FROM Device AS d WHERE " + visit(ctx.expression());
}

@Override
public String visitBracketExpression(QueryParser.BracketExpressionContext ctx) {
return visit(ctx.expression());
}

@Override
public String visitAndExpression(QueryParser.AndExpressionContext ctx) {
return visit(ctx.expression(0)) + " AND " + visit(ctx.expression(1));
}

@Override
public String visitPredicateExpression(QueryParser.PredicateExpressionContext ctx) {
return visit(ctx.predicate());
}

@Override
public String visitOrExpression(QueryParser.OrExpressionContext ctx) {
return "(" + visit(ctx.expression(0)) + " OR " + visit(ctx.expression(1)) + ")";
}

@Override
public String visitOperator(QueryParser.OperatorContext ctx) {
return ctx.getText();
}

@Override
public String visitIntegerValue(QueryParser.IntegerValueContext ctx) {
return addParameter(Integer.valueOf(ctx.getText()));
}

@Override
public String visitDoubleValue(QueryParser.DoubleValueContext ctx) {
return addParameter(Double.valueOf(ctx.getText()));
}

@Override
public String visitStringValue(QueryParser.StringValueContext ctx) {
return addParameter(ctx.getText());
}

@Override
public String visitAmount(QueryParser.AmountContext ctx) {
Amount<?> amount = Amount.valueOf(ctx.getText());
@SuppressWarnings("unchecked")
double value = amount.doubleValue((Unit) amount.getUnit().getStandardUnit());
return addParameter(value);
}

@Override
public String visitUnit(QueryParser.UnitContext ctx) {
return ctx.getText();
}

@Override
public String visitElement(QueryParser.ElementContext ctx) {
return ctx.getText();
}

@Override
public String visitOperatorPredicate(QueryParser.OperatorPredicateContext ctx) {
String operator = visit(ctx.operator());
String value = visit(ctx.term());
String reference = visitReference(ctx.reference(), parameters.get(value).getClass());
return reference + " " + operator + " :" + value;
}

public String visitReference(QueryParser.ReferenceContext ctx, Class<?> type) {
List<String> elements = ctx.element().stream().map(this::visitElement).collect(Collectors.toList());
String base = "d." + elements.get(0);
if (elements.size() == 1) {
return base;
} else {
List<String> tail = elements.subList(1, elements.size());
String extract = "extract(" + base + ", '" + String.join("', '", tail) + "')";
if (type == Integer.class) {
return "CAST(" + extract + " integer)";
} else if (type == Double.class) {
return "CAST(" + extract + " double)";
} else {
return extract;
}
}
}

@Override
public String visitLocationPredicate(QueryParser.LocationPredicateContext ctx) {
String reference = visit(ctx.reference());
String location = visit(ctx.location());
String distance = visit(ctx.amount());
return "distance(" + reference + ", :" + location + ") <= :" + distance;
}

@Override
public String visitLocation(QueryParser.LocationContext ctx) {
double latitude = Double.valueOf(ctx.latitude().getText());
double longitude = Double.valueOf(ctx.longitude().getText());
Point point = geometryFactory.createPoint(new Coordinate(latitude, longitude));
point.setSRID(4326);
return addParameter(point);
}

@Override
public String visitTerm(QueryParser.TermContext ctx) {
if (ctx.amount() != null) {
return visit(ctx.amount());
} else if (ctx.value() != null) {
return visit(ctx.value());
} else {
return visit(ctx.reference());
}
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/kartashov/postgis/search/SearchService.java
@@ -0,0 +1,58 @@
package com.kartashov.postgis.search;

import com.kartashov.postgis.antlr.QueryLexer;
import com.kartashov.postgis.antlr.QueryParser;
import com.kartashov.postgis.entities.Device;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

@Service
@Transactional
public class SearchService {

private static final Logger logger = LoggerFactory.getLogger(SearchService.class);

@Autowired
private EntityManager entityManager;

@SuppressWarnings("unchecked")
public List<Device> search(String query) throws IOException {

logger.info("Parsing search query {}", query);

ANTLRInputStream input = new ANTLRInputStream(
new ByteArrayInputStream(query.getBytes(StandardCharsets.UTF_8)));
QueryLexer lexer = new QueryLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);

QueryParser parser = new QueryParser(tokens);
ParseTree tree = parser.query();

logger.info("Expression tree: {}", tree.toStringTree(parser));

QueryVisitor visitor = new QueryVisitor();
String jpqlQuery = visitor.visit(tree);

logger.info("Resulting JPQL query:\n{}", jpqlQuery);

Query queryObject = entityManager.createQuery(jpqlQuery);
for (Map.Entry<String, Object> entry : visitor.getParameters().entrySet()) {
queryObject.setParameter(entry.getKey(), entry.getValue());
}
return queryObject.getResultList();
}
}

0 comments on commit 279b69d

Please sign in to comment.