Skip to content

Commit

Permalink
TEIID-3329: adding geo spatial functions based on geometry data type (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
rareddy committed Sep 26, 2016
1 parent bc7f8d6 commit 769a99b
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 31 deletions.
Expand Up @@ -15,5 +15,6 @@
<module name="org.jboss.teiid.api" />
<module name="org.jboss.teiid.translator.mongodb.api" />
<module name="org.jboss.teiid.translator.jdbc" />
<module name="org.jboss.teiid" /> <!-- for geostuff -->
</dependencies>
</module>
4 changes: 4 additions & 0 deletions connectors/translator-mongodb/pom.xml
Expand Up @@ -20,6 +20,10 @@
<groupId>org.jboss.teiid</groupId>
<artifactId>teiid-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.teiid</groupId>
<artifactId>teiid-engine</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.resource</groupId>
<artifactId>jboss-connector-api_1.7_spec</artifactId>
Expand Down
Expand Up @@ -44,6 +44,7 @@
import org.teiid.core.types.BlobImpl;
import org.teiid.core.types.ClobImpl;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.GeometryType;
import org.teiid.core.types.InputStreamFactory;
import org.teiid.core.types.SQLXMLImpl;
import org.teiid.core.types.Transform;
Expand Down Expand Up @@ -159,25 +160,73 @@ public List<?> translate(Function function) {
registerFunctionModifier(SourceSystemFunctions.IFNULL, new AliasModifier("$ifNull")); //$NON-NLS-1$

FunctionMethod method = null;
method = addPushDownFunction(MONGO, FUNC_GEO_INTERSECTS, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE+"[][]"); //$NON-NLS-1$
method = addPushDownFunction(MONGO, FUNC_GEO_INTERSECTS, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.STRING,
TypeFacility.RUNTIME_NAMES.DOUBLE + "[][]"); //$NON-NLS-1$
method.setProperty(AVOID_PROJECTION, "true");
method = addPushDownFunction(MONGO, FUNC_GEO_WITHIN, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE+"[][]"); //$NON-NLS-1$

method = addPushDownFunction(MONGO, FUNC_GEO_INTERSECTS, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.GEOMETRY); // $NON-NLS-1$
method.setProperty(AVOID_PROJECTION, "true");

method = addPushDownFunction(MONGO, FUNC_GEO_WITHIN, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.STRING,
TypeFacility.RUNTIME_NAMES.DOUBLE + "[][]"); //$NON-NLS-1$
method.setProperty(AVOID_PROJECTION, "true");

method = addPushDownFunction(MONGO, FUNC_GEO_WITHIN, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.GEOMETRY); // $NON-NLS-1$
method.setProperty(AVOID_PROJECTION, "true");

if (getVersion().compareTo(MongoDBExecutionFactory.TWO_6) >= 0) {
method = addPushDownFunction(MONGO, FUNC_GEO_NEAR, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE+"[]", TypeFacility.RUNTIME_NAMES.INTEGER, TypeFacility.RUNTIME_NAMES.INTEGER); //$NON-NLS-1$
method = addPushDownFunction(MONGO, FUNC_GEO_NEAR, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE + "[]", //$NON-NLS-1$
TypeFacility.RUNTIME_NAMES.INTEGER, TypeFacility.RUNTIME_NAMES.INTEGER);
method.setProperty(AVOID_PROJECTION, "true");
method = addPushDownFunction(MONGO, FUNC_GEO_NEAR_SPHERE, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE+"[]", TypeFacility.RUNTIME_NAMES.INTEGER, TypeFacility.RUNTIME_NAMES.INTEGER); //$NON-NLS-1$

method = addPushDownFunction(MONGO, FUNC_GEO_NEAR, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.GEOMETRY,
TypeFacility.RUNTIME_NAMES.INTEGER, TypeFacility.RUNTIME_NAMES.INTEGER);
method.setProperty(AVOID_PROJECTION, "true");

method = addPushDownFunction(MONGO, FUNC_GEO_NEAR_SPHERE, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE + "[]", //$NON-NLS-1$
TypeFacility.RUNTIME_NAMES.INTEGER, TypeFacility.RUNTIME_NAMES.INTEGER);
method.setProperty(AVOID_PROJECTION, "true");

method = addPushDownFunction(MONGO, FUNC_GEO_NEAR_SPHERE, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.GEOMETRY,
TypeFacility.RUNTIME_NAMES.INTEGER, TypeFacility.RUNTIME_NAMES.INTEGER);
method.setProperty(AVOID_PROJECTION, "true");
}
else {
method = addPushDownFunction(MONGO, FUNC_GEO_NEAR, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE+"[]", TypeFacility.RUNTIME_NAMES.INTEGER); //$NON-NLS-1$
method = addPushDownFunction(MONGO, FUNC_GEO_NEAR, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE + "[]", //$NON-NLS-1$
TypeFacility.RUNTIME_NAMES.INTEGER);
method.setProperty(AVOID_PROJECTION, "true");
method = addPushDownFunction(MONGO, FUNC_GEO_NEAR_SPHERE, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE+"[]", TypeFacility.RUNTIME_NAMES.INTEGER); //$NON-NLS-1$

method = addPushDownFunction(MONGO, FUNC_GEO_NEAR_SPHERE, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE + "[]", //$NON-NLS-1$
TypeFacility.RUNTIME_NAMES.INTEGER);
method.setProperty(AVOID_PROJECTION, "true");
}
method = addPushDownFunction(MONGO, FUNC_GEO_POLYGON_INTERSECTS, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE,TypeFacility.RUNTIME_NAMES.DOUBLE,TypeFacility.RUNTIME_NAMES.DOUBLE,TypeFacility.RUNTIME_NAMES.DOUBLE);

method = addPushDownFunction(MONGO, FUNC_GEO_POLYGON_INTERSECTS, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE, TypeFacility.RUNTIME_NAMES.DOUBLE,
TypeFacility.RUNTIME_NAMES.DOUBLE, TypeFacility.RUNTIME_NAMES.DOUBLE);
method.setProperty(AVOID_PROJECTION, "true");
method = addPushDownFunction(MONGO, FUNC_GEO_POLYGON_WITHIN, TypeFacility.RUNTIME_NAMES.BOOLEAN, TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE,TypeFacility.RUNTIME_NAMES.DOUBLE,TypeFacility.RUNTIME_NAMES.DOUBLE,TypeFacility.RUNTIME_NAMES.DOUBLE);

method = addPushDownFunction(MONGO, FUNC_GEO_POLYGON_INTERSECTS, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.GEOMETRY);
method.setProperty(AVOID_PROJECTION, "true");

method = addPushDownFunction(MONGO, FUNC_GEO_POLYGON_WITHIN, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.STRING, TypeFacility.RUNTIME_NAMES.DOUBLE, TypeFacility.RUNTIME_NAMES.DOUBLE,
TypeFacility.RUNTIME_NAMES.DOUBLE, TypeFacility.RUNTIME_NAMES.DOUBLE);
method.setProperty(AVOID_PROJECTION, "true");

method = addPushDownFunction(MONGO, FUNC_GEO_POLYGON_WITHIN, TypeFacility.RUNTIME_NAMES.BOOLEAN,
TypeFacility.RUNTIME_NAMES.GEOMETRY);
method.setProperty(AVOID_PROJECTION, "true");

registerFunctionModifier(FUNC_GEO_NEAR, new AliasModifier("$near"));//$NON-NLS-1$
Expand Down Expand Up @@ -591,7 +640,7 @@ else if (value instanceof BinaryType) {
else if (value instanceof byte[]) {
return new Binary((byte[])value);
}
else if (value instanceof Blob) {
else if (!(value instanceof GeometryType) && value instanceof Blob) {
String uuid = UUID.randomUUID().toString();
GridFS gfs = new GridFS(mongoDB, fqn);
GridFSInputFile gfsFile = gfs.createFile(((Blob)value).getBinaryStream());
Expand All @@ -614,7 +663,7 @@ else if (value instanceof SQLXML) {
gfsFile.setFilename(uuid);
gfsFile.save();
return uuid;
}
}
else if (value instanceof Object[]) {
BasicDBList list = new BasicDBList();
for (Object obj:(Object[])value) {
Expand Down
Expand Up @@ -28,11 +28,17 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

import org.teiid.api.exception.query.FunctionExecutionException;
import org.teiid.core.types.ClobType;
import org.teiid.core.types.GeometryType;
import org.teiid.core.types.TransformationException;
import org.teiid.core.types.basic.ClobToStringTransform;
import org.teiid.language.*;
import org.teiid.language.Join.JoinType;
import org.teiid.language.SortSpecification.Ordering;
import org.teiid.language.visitor.HierarchyVisitor;
import org.teiid.metadata.*;
import org.teiid.query.function.GeometryUtils;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.mongodb.MergeDetails.Association;

Expand Down Expand Up @@ -354,7 +360,11 @@ public void visit(Function obj) {
}
BasicDBObject expr = null;
if (isGeoSpatialFunction(functionName)) {
expr = (BasicDBObject)handleGeoSpatialFunction(functionName, obj);
try {
expr = (BasicDBObject)handleGeoSpatialFunction(functionName, obj);
} catch (TranslatorException e) {
this.exceptions.add(e);
}
}
else if (isStringFunction(functionName)) {
expr = (BasicDBObject)handleStringFunction(functionName, obj);
Expand Down Expand Up @@ -999,15 +1009,15 @@ static List<String> getColumnNames(List<Column> columns){

static enum SpatialType {Point, LineString, Polygon, MultiPoint, MultiLineString};

private DBObject handleGeoSpatialFunction(String functionName, Function function) {
private DBObject handleGeoSpatialFunction(String functionName, Function function) throws TranslatorException{
if (functionName.equalsIgnoreCase(MongoDBExecutionFactory.FUNC_GEO_NEAR) ||
functionName.equalsIgnoreCase(MongoDBExecutionFactory.FUNC_GEO_NEAR_SPHERE)) {
return buildGeoNearFunction(function);
}
return buildGeoFunction(function);
}

private DBObject buildGeoNearFunction(Function function) {
private DBObject buildGeoNearFunction(Function function) throws TranslatorException {
List<Expression> args = function.getParameters();

// Column Name
Expand All @@ -1017,14 +1027,20 @@ private DBObject buildGeoNearFunction(Function function) {
BasicDBObjectBuilder builder = BasicDBObjectBuilder.start();
builder.push(column.documentFieldName);
builder.push(function.getName());
builder.push("$geometry");//$NON-NLS-1$
builder.add("type", SpatialType.Point.name());//$NON-NLS-1$

// walk the co-ordinates
append(args.get(paramIndex++));
BasicDBList coordinates = new BasicDBList();
coordinates.add(this.onGoingExpression.pop());
builder.add("coordinates", coordinates); //$NON-NLS-1$
Object object = this.onGoingExpression.pop();
if (object instanceof GeometryType) {
convertGeometryToJson(builder, (GeometryType)object);
} else {
builder.push("$geometry");//$NON-NLS-1$
builder.add("type", SpatialType.Point.name());//$NON-NLS-1$

// walk the co-ordinates
BasicDBList coordinates = new BasicDBList();
coordinates.add(object);
builder.add("coordinates", coordinates); //$NON-NLS-1$
}

// maxdistance
append(args.get(paramIndex++));
Expand All @@ -1039,28 +1055,44 @@ private DBObject buildGeoNearFunction(Function function) {
return builder.get();
}

private DBObject buildGeoFunction(Function function) {
private DBObject buildGeoFunction(Function function) throws TranslatorException{
List<Expression> args = function.getParameters();

// Column Name
int paramIndex = 0;
ColumnDetail column = getExpressionAlias(args.get(paramIndex++));

// Type: Point, LineString, Polygon..
append(args.get(paramIndex++));
SpatialType type = SpatialType.valueOf((String)this.onGoingExpression.pop());

BasicDBObjectBuilder builder = BasicDBObjectBuilder.start();
builder.push(column.documentFieldName);
builder.push(function.getName());
builder.push("$geometry");//$NON-NLS-1$
builder.add("type", type.name());//$NON-NLS-1$

// walk the co-ordinates
append(args.get(paramIndex++));
BasicDBList coordinates = new BasicDBList();
coordinates.add(this.onGoingExpression.pop());
builder.add("coordinates", coordinates); //$NON-NLS-1$
Object object = this.onGoingExpression.pop();
if (object instanceof GeometryType) {
convertGeometryToJson(builder, (GeometryType)object);
} else {
// Type: Point, LineString, Polygon..
SpatialType type = SpatialType.valueOf((String)object);

append(args.get(paramIndex++));
builder.push("$geometry");//$NON-NLS-1$
builder.add("type", type.name());//$NON-NLS-1$
// walk the co-ordinates
BasicDBList coordinates = new BasicDBList();
coordinates.add(this.onGoingExpression.pop());
builder.add("coordinates", coordinates); //$NON-NLS-1$
}
return builder.get();
}

private void convertGeometryToJson(BasicDBObjectBuilder builder, GeometryType object) throws TranslatorException {
try {
ClobType clob = GeometryUtils.geometryToGeoJson(object);
ClobToStringTransform clob2str = new ClobToStringTransform();
String geometry = (String)clob2str.transform(clob, String.class);
builder.add("$geometry", geometry);
} catch (FunctionExecutionException | TransformationException e) {
throw new TranslatorException(e);
}
}
}
Expand Up @@ -22,6 +22,7 @@
package org.teiid.translator.mongodb;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Assert;
Expand All @@ -32,13 +33,29 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.teiid.cdk.api.TranslationUtility;
import org.teiid.core.types.ClobImpl;
import org.teiid.core.types.ClobType;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.GeometryType;
import org.teiid.core.util.ObjectConverterUtil;
import org.teiid.core.util.UnitTestUtil;
import org.teiid.language.ColumnReference;
import org.teiid.language.Command;
import org.teiid.language.Comparison;
import org.teiid.language.DerivedColumn;
import org.teiid.language.Function;
import org.teiid.language.Literal;
import org.teiid.language.NamedTable;
import org.teiid.language.QueryExpression;
import org.teiid.language.Select;
import org.teiid.language.TableReference;
import org.teiid.metadata.FunctionMethod;
import org.teiid.metadata.FunctionParameter;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Table;
import org.teiid.mongodb.MongoDBConnection;
import org.teiid.query.function.FunctionTree;
import org.teiid.query.function.GeometryUtils;
import org.teiid.query.function.UDFSource;
import org.teiid.query.metadata.MetadataValidator;
import org.teiid.query.metadata.TransformationMetadata;
Expand All @@ -49,7 +66,16 @@
import org.teiid.translator.ResultSetExecution;
import org.teiid.translator.TranslatorException;

import com.mongodb.*;
import com.mongodb.AggregationOptions;
import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.Cursor;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;

@SuppressWarnings("nls")
public class TestMongoDBQueryExecution {
Expand Down Expand Up @@ -79,6 +105,9 @@ public void setUp() throws Exception {

private DBCollection helpExecute(String query, String[] expectedCollection, int expectedParameters) throws TranslatorException {
Command cmd = this.utility.parseCommand(query);
return helpExecute(cmd, expectedCollection, expectedParameters);
}
private DBCollection helpExecute(Command cmd, String[] expectedCollection, int expectedParameters) throws TranslatorException {
ExecutionContext context = Mockito.mock(ExecutionContext.class);
Mockito.stub(context.getBatchSize()).toReturn(256);
MongoDBConnection connection = Mockito.mock(MongoDBConnection.class);
Expand Down Expand Up @@ -1263,6 +1292,54 @@ public void testGeoFunctionInWhere() throws Exception {
new BasicDBObject("$project", result));
Mockito.verify(dbCollection).aggregate(Mockito.eq(pipeline), Mockito.any(AggregationOptions.class));
}

private FunctionMethod getFunctionMethod(String name) {
for (FunctionMethod fm: this.translator.getPushDownFunctions()) {
if (fm.getName().equalsIgnoreCase(name)) {
for (FunctionParameter fp:fm.getInputParameters()) {
if (fp.getType().equals(DataTypeManager.DefaultDataTypes.GEOMETRY)) {
return fm;
}
}
}
}
return null;
}

@Test
public void testGeoFunctionInWhereWithGeometry() throws Exception {
Table table = this.utility.createRuntimeMetadata().getTable("northwind.Categories");
NamedTable namedTable = new NamedTable("Categories", "g0", table);
ColumnReference colRef = new ColumnReference(namedTable, "CategoryName", table.getColumnByName("CategoryName"), String.class);
DerivedColumn col = new DerivedColumn("CategoryName", colRef);
Select select = new Select();
select.setDerivedColumns(Arrays.asList(col));
List<TableReference> tables = new ArrayList<TableReference>();
tables.add(namedTable);
select.setFrom(tables);

final GeometryType geo = GeometryUtils.geometryFromClob(new ClobType(new ClobImpl("POLYGON ((1.0 2.0,3.0 4.0,5.0 6.0,1.0 2.0))")));
Function function = new Function("mongo.geoWithin", Arrays.asList(colRef, new Literal(geo, GeometryType.class)), //$NON-NLS-1$
Boolean.class); //$NON-NLS-2$
function.setMetadataObject(getFunctionMethod("mongo.geoWithin"));

Comparison c = new Comparison(function, new Literal(true, Boolean.class), Comparison.Operator.EQ);
select.setWhere(c);

DBCollection dbCollection = helpExecute(select, new String[]{"Categories"}, 2);

BasicDBObjectBuilder builder = new BasicDBObjectBuilder();
builder.push("CategoryName");
builder.push("$geoWithin");//$NON-NLS-1$
builder.add("$geometry", "{\"type\":\"Polygon\",\"coordinates\":[[[1.0,2.0],[3.0,4.0],[5.0,6.0],[1.0,2.0]]]}");//$NON-NLS-1$
BasicDBObject result = new BasicDBObject();
result.append( "CategoryName", "$CategoryName");

List<DBObject> pipeline = buildArray(
new BasicDBObject("$match", builder.get()),
new BasicDBObject("$project", result));
Mockito.verify(dbCollection).aggregate(Mockito.eq(pipeline), Mockito.any(AggregationOptions.class));
}

@Test(expected=TranslatorException.class)
public void testGeoFunctionInWhereWithFalse() throws Exception {
Expand Down

0 comments on commit 769a99b

Please sign in to comment.