/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esper.regression.spatial;
import com.espertech.esper.client.*;
import com.espertech.esper.client.scopetest.EPAssertionUtil;
import com.espertech.esper.client.scopetest.SupportUpdateListener;
import com.espertech.esper.filter.FilterOperator;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import com.espertech.esper.spatial.quadtree.core.BoundingBox;
import com.espertech.esper.supportregression.bean.SupportSpatialAABB;
import com.espertech.esper.supportregression.bean.SupportSpatialDualAABB;
import com.espertech.esper.supportregression.bean.SupportSpatialEventRectangle;
import com.espertech.esper.supportregression.bean.SupportSpatialPoint;
import com.espertech.esper.supportregression.client.SupportConfigFactory;
import com.espertech.esper.supportregression.epl.SupportQueryPlanIndexHook;
import com.espertech.esper.supportregression.util.*;
import junit.framework.TestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import static com.espertech.esper.supportregression.util.SupportSpatialUtil.assertRectanglesManyRow;
import static com.espertech.esper.supportregression.util.SupportSpatialUtil.sendAssertSpatialAABB;
public class TestSpatialMXCIFQuadTree extends TestCase {
private static final Logger log = LoggerFactory.getLogger(TestSpatialMXCIFQuadTree.class);
private final static List<BoundingBox> BOXES = Arrays.asList(
new BoundingBox(0, 0, 50, 50),
new BoundingBox(50, 0, 100, 50),
new BoundingBox(0, 50, 50, 100),
new BoundingBox(50, 50, 100, 100),
new BoundingBox(25, 25, 75, 75)
);
private EPServiceProvider epService;
private SupportUpdateListener listener;
public void setUp() {
Configuration config = SupportConfigFactory.getConfiguration();
config.getEngineDefaults().getExecution().setAllowIsolatedService(true);
config.getEngineDefaults().getLogging().setEnableQueryPlan(true);
epService = EPServiceProviderManager.getDefaultProvider(config);
epService.initialize();
for (Class clazz : Arrays.asList(SupportSpatialAABB.class, SupportSpatialEventRectangle.class, SupportSpatialDualAABB.class)) {
epService.getEPAdministrator().getConfiguration().addEventType(clazz);
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.startTest(epService, this.getClass(), getName());
}
listener = new SupportUpdateListener();
}
protected void tearDown() throws Exception {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.endTest();
}
listener = null;
}
public void testInvalid() throws Exception {
// invalid-testing overlaps with pointregion-quadtree
runAssertionInvalidEventIndexCreate();
runAssertionInvalidEventIndexRuntime();
runAssertionInvalidMethod();
runAssertionInvalidFilterIndex();
runAssertionDocSample();
}
private void runAssertionInvalidFilterIndex() {
// invalid index for filter
String epl = "expression myindex {pointregionquadtree(0, 0, 100, 100)}" +
"select * from SupportSpatialEventRectangle(rectangle(10, 20, 5, 6, filterindex:myindex).intersects(rectangle(x, y, width, height)))";
SupportMessageAssertUtil.tryInvalid(epService, epl, "Failed to validate filter expression 'rectangle(10,20,5,6,filterindex:myi...(82 chars)': Invalid index type 'pointregionquadtree', expected 'mxcifquadtree'");
}
private void runAssertionInvalidMethod() {
SupportMessageAssertUtil.tryInvalid(epService, "select * from SupportSpatialEventRectangle(rectangle('a', 0).inside(rectangle(0, 0, 0, 0)))",
"Failed to validate filter expression 'rectangle(\"a\",0).inside(rectangle(0...(43 chars)': Failed to validate method-chain parameter expression 'rectangle(0,0,0,0)': Unknown single-row function, expression declaration, script or aggregation function named 'rectangle' could not be resolved (did you mean 'rectangle.intersects')");
SupportMessageAssertUtil.tryInvalid(epService, "select * from SupportSpatialEventRectangle(rectangle(0).intersects(rectangle(0, 0, 0, 0)))",
"Failed to validate filter expression 'rectangle(0).intersects(rectangle(0...(43 chars)': Error validating left-hand-side method 'rectangle', expected 4 parameters but received 1 parameters");
}
private void runAssertionInvalidEventIndexRuntime() throws Exception {
String epl = "@name('mywindow') create window RectangleWindow#keepall as SupportSpatialEventRectangle;\n" +
"insert into RectangleWindow select * from SupportSpatialEventRectangle;\n" +
"create index MyIndex on RectangleWindow((x, y, width, height) mxcifquadtree(0, 0, 100, 100));\n";
epService.getEPAdministrator().getDeploymentAdmin().parseDeploy(epl);
try {
epService.getEPRuntime().sendEvent(new SupportSpatialEventRectangle("E1", null, null, null, null, "category"));
} catch (Exception ex) {
SupportMessageAssertUtil.assertMessage(ex, "Unexpected exception in statement 'mywindow': Invalid value for index 'MyIndex' column 'x' received null and expected non-null");
}
try {
epService.getEPRuntime().sendEvent(new SupportSpatialEventRectangle("E1", 200d, 200d, 1, 1));
} catch (Exception ex) {
SupportMessageAssertUtil.assertMessage(ex, "Unexpected exception in statement 'mywindow': Invalid value for index 'MyIndex' column '(x,y,width,height)' received (200.0,200.0,1.0,1.0) and expected a value intersecting index bounding box (range-end-inclusive) {minX=0.0, minY=0.0, maxX=100.0, maxY=100.0}");
}
}
private void runAssertionInvalidEventIndexCreate() {
// most are covered by point-region test already
epService.getEPAdministrator().createEPL("create window MyWindow#keepall as SupportSpatialEventRectangle");
// invalid number of columns
SupportMessageAssertUtil.tryInvalid(epService, "create index MyIndex on MyWindow(x mxcifquadtree(0, 0, 100, 100))",
"Error starting statement: Index of type 'mxcifquadtree' requires 4 expressions as index columns but received 1");
// same index twice, by-columns
epService.getEPAdministrator().createEPL("create window SomeWindow#keepall as SupportSpatialEventRectangle");
epService.getEPAdministrator().createEPL("create index SomeWindowIdx1 on SomeWindow((x, y, width, height) mxcifquadtree(0, 0, 1, 1))");
SupportMessageAssertUtil.tryInvalid(epService, "create index SomeWindowIdx2 on SomeWindow((x, y, width, height) mxcifquadtree(0, 0, 1, 1))",
"Error starting statement: An index for the same columns already exists");
epService.getEPAdministrator().destroyAllStatements();
}
public void testFilterIndex() throws Exception {
runAssertionFilterIndexPerfPattern();
runAssertionFilterIndexTypeAssertion();
}
public void testEventIndex() throws Exception {
runAssertionEventIndexUnindexed();
runAssertionEventIndexOnTriggerNWInsertRemove(false);
runAssertionEventIndexOnTriggerNWInsertRemove(true);
runAssertionEventIndexUnique();
runAssertionEventIndexPerformance();
runAssertionEventIndexTableFireAndForget();
}
private void runAssertionFilterIndexTypeAssertion() {
String eplNoIndex = "select * from SupportSpatialEventRectangle(rectangle(0, 0, 1, 1).intersects(rectangle(x, y, width, height)))";
SupportFilterHelper.assertFilterMulti(epService, eplNoIndex, "SupportSpatialEventRectangle", new SupportFilterItem[][] {{SupportFilterItem.getBoolExprFilterItem()}});
String eplIndexed = "expression myindex {mxcifquadtree(0, 0, 100, 100)}" +
"select * from SupportSpatialEventRectangle(rectangle(10, 20, 5, 6, filterindex:myindex).intersects(rectangle(x, y, width, height)))";
EPStatement statement = SupportFilterHelper.assertFilterMulti(epService, eplIndexed, "SupportSpatialEventRectangle", new SupportFilterItem[][] {{new SupportFilterItem("x,y,width,height/myindex/mxcifquadtree/0.0,0.0,100.0,100.0,4.0,20.0", FilterOperator.ADVANCED_INDEX)}});
statement.addListener(listener);
sendAssertEventRectangle(10, 20, 0, 0, true);
sendAssertEventRectangle(9, 19, 0.9999, 0.9999, false);
sendAssertEventRectangle(9, 19, 1, 1, true);
sendAssertEventRectangle(15, 26, 0, 0, true);
sendAssertEventRectangle(15.001, 26.001, 0, 0, false);
epService.getEPAdministrator().destroyAllStatements();
}
private void runAssertionFilterIndexPerfPattern() {
EPStatement stmt = epService.getEPAdministrator().createEPL("expression myindex {mxcifquadtree(0, 0, 100, 100)}" +
"select * from pattern [every p=SupportSpatialEventRectangle -> SupportSpatialAABB(rectangle(p.x, p.y, p.width, p.height, filterindex:myindex).intersects(rectangle(x, y, width, height)))]");
stmt.addListener(listener);
sendSpatialEventRectanges(100, 50);
sendAssertSpatialAABB(epService, listener, 100, 50, 1000);
epService.getEPAdministrator().destroyAllStatements();
}
private void runAssertionEventIndexTableFireAndForget() {
epService.getEPAdministrator().createEPL("create table MyTable(id string primary key, tx double, ty double, tw double, th double)");
epService.getEPRuntime().executeQuery("insert into MyTable values ('R1', 10, 20, 5, 6)");
epService.getEPAdministrator().createEPL("create index MyIdxCIFQuadTree on MyTable( (tx, ty, tw, th) mxcifquadtree(0, 0, 100, 100))");
runAssertionFAF(10, 20, 0, 0, true);
runAssertionFAF(9, 19, 1, 1, true);
runAssertionFAF(9, 19, 0.9999, 0.9999, false);
runAssertionFAF(15, 26, 0, 0, true);
runAssertionFAF(15.0001, 26.0001, 0, 0, false);
runAssertionFAF(0, 0, 100, 100, true);
runAssertionFAF(11, 21, 1, 1, true);
epService.getEPAdministrator().destroyAllStatements();
}
private void runAssertionFAF(double x, double y, double width, double height, boolean expected) {
EPOnDemandQueryResult result = epService.getEPRuntime().executeQuery(IndexBackingTableInfo.INDEX_CALLBACK_HOOK + "select id as c0 from MyTable where rectangle(tx, ty, tw, th).intersects(rectangle(" + x + ", " + y + ", " + width + ", " + height + "))");
SupportQueryPlanIndexHook.assertFAFAndReset("MyIdxCIFQuadTree", "EventTableQuadTreeMXCIFImpl");
if (expected) {
EPAssertionUtil.assertPropsPerRowAnyOrder(result.getArray(), "c0".split(","), new Object[][]{{"R1"}});
}
else {
assertEquals(0, result.getArray().length);
}
}
private void runAssertionEventIndexPerformance() throws Exception {
String epl = "create window MyRectangleWindow#keepall as (id string, rx double, ry double, rw double, rh double);\n" +
"insert into MyRectangleWindow select id, x as rx, y as ry, width as rw, height as rh from SupportSpatialEventRectangle;\n" +
"create index Idx on MyRectangleWindow( (rx, ry, rw, rh) mxcifquadtree(0, 0, 100, 100));\n" +
"@Name('out') on SupportSpatialAABB select mpw.id as c0 from MyRectangleWindow as mpw where rectangle(rx, ry, rw, rh).intersects(rectangle(x, y, width, height));\n";
String deploymentId = epService.getEPAdministrator().getDeploymentAdmin().parseDeploy(epl).getDeploymentId();
epService.getEPAdministrator().getStatement("out").addListener(listener);
sendSpatialEventRectanges(100, 50);
long start = System.currentTimeMillis();
for (int x = 0; x < 100; x++) {
for (int y = 0; y < 50; y++) {
epService.getEPRuntime().sendEvent(new SupportSpatialAABB("R", x, y, 0.5, 0.5));
assertEquals(Integer.toString(x) + "_" + Integer.toString(y), listener.assertOneGetNewAndReset().get("c0"));
}
}
long delta = System.currentTimeMillis() - start;
assertTrue("delta=" + delta, delta < 2000);
epService.getEPAdministrator().getDeploymentAdmin().undeploy(deploymentId);
}
private void runAssertionDocSample() throws Exception {
String epl = "create table RectangleTable(rectangleId string primary key, rx double, ry double, rwidth double, rheight double);\n" +
"create index RectangleIndex on RectangleTable((rx, ry, rwidth, rheight) mxcifquadtree(0, 0, 100, 100));\n" +
"create schema OtherRectangleEvent(otherX double, otherY double, otherWidth double, otherHeight double);\n" +
"on OtherRectangleEvent\n" +
"select rectangleId from RectangleTable\n" +
"where rectangle(rx, ry, rwidth, rheight).intersects(rectangle(otherX, otherY, otherWidth, otherHeight));" +
"expression myMXCIFQuadtreeSettings { mxcifquadtree(0, 0, 100, 100) } \n" +
"select * from SupportSpatialAABB(rectangle(10, 20, 5, 5, filterindex:myMXCIFQuadtreeSettings).intersects(rectangle(x, y, width, height)));\n";
String deploymentId = epService.getEPAdministrator().getDeploymentAdmin().parseDeploy(epl).getDeploymentId();
epService.getEPAdministrator().getDeploymentAdmin().undeploy(deploymentId);
}
private void runAssertionEventIndexUnique() throws Exception {
String epl = "@Name('win') create window MyRectWindow#keepall as (id string, rx double, ry double, rw double, rh double);\n" +
"@Name('insert') insert into MyRectWindow select id, x as rx, y as ry, width as rw, height as rh from SupportSpatialEventRectangle;\n" +
"@Name('idx') create unique index Idx on MyRectWindow( (rx, ry, rw, rh) mxcifquadtree(0, 0, 100, 100));\n" +
IndexBackingTableInfo.INDEX_CALLBACK_HOOK + "@Name('out') on SupportSpatialAABB select mpw.id as c0 from MyRectWindow as mpw where rectangle(rx, ry, rw, rh).intersects(rectangle(x, y, width, height));\n";
String deploymentId = epService.getEPAdministrator().getDeploymentAdmin().parseDeploy(epl).getDeploymentId();
epService.getEPAdministrator().getStatement("out").addListener(listener);
SupportQueryPlanIndexHook.assertOnExprTableAndReset("Idx", "unique hash={} btree={} advanced={mxcifquadtree(rx,ry,rw,rh)}");
sendEventRectangle("P1", 10, 15, 1, 2);
try {
sendEventRectangle("P1", 10, 15, 1, 2);
fail();
} catch (RuntimeException ex) { // we have a handler
SupportMessageAssertUtil.assertMessage(ex,
"Unexpected exception in statement 'win': Unique index violation, index 'Idx' is a unique index and key '(10.0,15.0,1.0,2.0)' already exists");
}
epService.getEPAdministrator().getDeploymentAdmin().undeploy(deploymentId);
}
private void runAssertionEventIndexUnindexed() {
EPStatement stmt = epService.getEPAdministrator().createEPL("select rectangle(one.x, one.y, one.width, one.height).intersects(rectangle(two.x, two.y, two.width, two.height)) as c0 from SupportSpatialDualAABB");
stmt.addListener(listener);
// For example, in MySQL:
// SET @g1 = ST_GeomFromText('Polygon((1 1,1 2,2 2,2 1,1 1))');
// SET @g2 = ST_GeomFromText('Polygon((2 2,2 4,4 4,4 2,2 2))');
// SELECT MBRIntersects(@g1,@g2), MBRIntersects(@g2,@g1);
// includes exterior
sendAssert(rect(1, 1, 5, 5), rect(2, 2, 2, 2), true);
sendAssert(rect(1, 1, 1, 1), rect(2, 2, 2, 2), true);
sendAssert(rect(1, 0.9999, 1, 0.99999), rect(2, 2, 2, 2), false);
sendAssert(rect(1, 1, 1, 0.99999), rect(2, 2, 2, 2), false);
sendAssert(rect(1, 0.9999, 1, 1), rect(2, 2, 2, 2), false);
sendAssert(rect(4, 4, 1, 1), rect(2, 2, 2, 2), true);
sendAssert(rect(4.0001, 4, 1, 1), rect(2, 2, 2, 2), false);
sendAssert(rect(4, 4.0001, 1, 1), rect(2, 2, 2, 2), false);
sendAssert(rect(10, 20, 5, 5), rect(0, 0, 50, 50), true);
sendAssert(rect(10, 20, 5, 5), rect(20, 20, 50, 50), false);
sendAssert(rect(10, 20, 5, 5), rect(9, 19, 1, 1), true);
sendAssert(rect(10, 20, 5, 5), rect(15, 25, 1, 1), true);
epService.getEPAdministrator().destroyAllStatements();
}
private void runAssertionEventIndexOnTriggerNWInsertRemove(boolean soda) {
SupportModelHelper.createByCompileOrParse(epService, soda, "create window MyWindow#length(5) as select * from SupportSpatialEventRectangle");
SupportModelHelper.createByCompileOrParse(epService, soda, "create index MyIndex on MyWindow((x,y,width,height) mxcifquadtree(0,0,100,100))");
SupportModelHelper.createByCompileOrParse(epService, soda, "insert into MyWindow select * from SupportSpatialEventRectangle");
String epl = IndexBackingTableInfo.INDEX_CALLBACK_HOOK + " on SupportSpatialAABB as aabb " +
"select rects.id as c0 from MyWindow as rects where rectangle(rects.x,rects.y,rects.width,rects.height).intersects(rectangle(aabb.x,aabb.y,aabb.width,aabb.height))";
EPStatement stmt = SupportModelHelper.createByCompileOrParse(epService, soda, epl);
stmt.addListener(listener);
SupportQueryPlanIndexHook.assertOnExprTableAndReset("MyIndex", "non-unique hash={} btree={} advanced={mxcifquadtree(x,y,width,height)}");
sendEventRectangle("R1", 10, 40, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R1", null, null, null, null);
sendEventRectangle("R2", 80, 80, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R1", null, null, "R2", null);
sendEventRectangle("R3", 10, 40, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R1,R3", null, null, "R2", null);
sendEventRectangle("R4", 60, 40, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R1,R3", "R4", null, "R2", "R4");
sendEventRectangle("R5", 20, 75, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R1,R3", "R4", "R5", "R2", "R4");
sendEventRectangle("R6", 50, 50, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R3,R6", "R4,R6", "R5,R6", "R2,R6", "R4,R6");
sendEventRectangle("R7", 0, 0, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R3,R6,R7", "R4,R6", "R5,R6", "R6", "R4,R6");
sendEventRectangle("R8", 99.999, 0, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R6,R7", "R4,R6,R8", "R5,R6", "R6", "R4,R6");
sendEventRectangle("R9", 0, 99.999, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R6,R7", "R6,R8", "R5,R6,R9", "R6", "R6");
sendEventRectangle("R10", 99.999, 99.999, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R6,R7", "R6,R8", "R6,R9", "R6,R10", "R6");
sendEventRectangle("R11", 0, 0, 1, 1);
assertRectanglesManyRow(epService, listener, BOXES, "R7,R11", "R8", "R9", "R10", null);
epService.getEPAdministrator().destroyAllStatements();
}
private void sendEventRectangle(String id, double x, double y, double width, double height) {
epService.getEPRuntime().sendEvent(new SupportSpatialEventRectangle(id, x, y, width, height));
}
private void sendAssertEventRectangle(double x, double y, double width, double height, boolean expected) {
epService.getEPRuntime().sendEvent(new SupportSpatialEventRectangle(null, x, y, width, height));
assertEquals(expected, listener.getIsInvokedAndReset());
}
private SupportSpatialAABB rect(double x, double y, double width, double height) {
return new SupportSpatialAABB(null, x, y, width, height);
}
private void sendAssert(SupportSpatialAABB one, SupportSpatialAABB two, boolean expected) {
BoundingBox bbOne = BoundingBox.from(one.getX(), one.getY(), one.getWidth(), one.getHeight());
assertEquals(expected, bbOne.intersectsBoxIncludingEnd(two.getX(), two.getY(), two.getWidth(), two.getHeight()));
BoundingBox bbTwo = BoundingBox.from(two.getX(), two.getY(), two.getWidth(), two.getHeight());
assertEquals(expected, bbTwo.intersectsBoxIncludingEnd(one.getX(), one.getY(), one.getWidth(), one.getHeight()));
epService.getEPRuntime().sendEvent(new SupportSpatialDualAABB(one, two));
assertEquals(expected, listener.assertOneGetNewAndReset().get("c0"));
epService.getEPRuntime().sendEvent(new SupportSpatialDualAABB(two, one));
assertEquals(expected, listener.assertOneGetNewAndReset().get("c0"));
}
private void sendSpatialEventRectanges(int numX, int numY) {
for (int x = 0; x < numX; x++) {
for (int y = 0; y < numY; y++) {
epService.getEPRuntime().sendEvent(new SupportSpatialEventRectangle(Integer.toString(x) + "_" + Integer.toString(y), (double) x, (double) y, 0.1, 0.2));
}
}
}
}