diff --git a/modules/unsupported/cql2-text/pom.xml b/modules/unsupported/cql2-text/pom.xml index 30a47a8f0d5..4b70353c8a6 100644 --- a/modules/unsupported/cql2-text/pom.xml +++ b/modules/unsupported/cql2-text/pom.xml @@ -32,6 +32,17 @@ tests test + + commons-io + commons-io + test + + + org.geotools + gt-geopkg + ${project.version} + test + diff --git a/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ATSOnlineTest.java b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ATSOnlineTest.java new file mode 100644 index 00000000000..65942f0f3fa --- /dev/null +++ b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ATSOnlineTest.java @@ -0,0 +1,57 @@ +package org.geotools.filter.text.cql_2.conformance; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import org.apache.commons.io.FileUtils; +import org.geotools.api.data.DataStore; +import org.geotools.api.data.DataStoreFinder; +import org.geotools.http.HTTPResponse; +import org.geotools.http.SimpleHttpClient; +import org.junit.BeforeClass; + +/** + * Base class for tests issued from the Abstract Test Suite (ATS). See + * https://docs.ogc.org/is/21-065r2/21-065r2.html#ats for the context. + * + *

This class will download the official Natural Earth dataset and store it into the default + * temporary directory. + */ +public abstract class ATSOnlineTest { + + private static String NE_DATA_URL = + "https://github.com/opengeospatial/ogcapi-features/raw/refs/heads/master/cql2/standard/data/ne110m4cql2.gpkg"; + + protected final File neGpkg = Path.of(System.getProperty("java.io.tmpdir"), "ne.gpkg").toFile(); + + @BeforeClass + public static void forceGMT() { + TimeZone.setDefault(TimeZone.getTimeZone("GMT")); + } + + private void downloadNaturalEarthData() throws IOException { + if (neGpkg.exists()) { + return; + } + neGpkg.createNewFile(); + SimpleHttpClient client = new SimpleHttpClient(); + HTTPResponse r = client.get(new URL(NE_DATA_URL)); + FileUtils.copyInputStreamToFile(r.getResponseStream(), neGpkg); + } + + protected DataStore naturalEarthData() throws IOException { + downloadNaturalEarthData(); + Map params = new HashMap(); + params.put("dbtype", "geopkg"); + params.put("database", neGpkg.getAbsolutePath()); + params.put("read-only", true); + + DataStore datastore = DataStoreFinder.getDataStore(params); + + return datastore; + } +} diff --git a/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest13OnlineTest.java b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest13OnlineTest.java new file mode 100644 index 00000000000..6d3413b4697 --- /dev/null +++ b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest13OnlineTest.java @@ -0,0 +1,65 @@ +package org.geotools.filter.text.cql_2.conformance; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.geootols.filter.text.cql_2.CQL2; +import org.geotools.api.data.DataStore; +import org.geotools.api.filter.Filter; +import org.geotools.filter.text.cql2.CQLException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** See table 9 from section A.4.4 Conformance test 13. */ +@RunWith(Parameterized.class) +public class ConformanceTest13OnlineTest extends ATSOnlineTest { + + private final String criteria; + private final int expectedFeatures; + + public ConformanceTest13OnlineTest(String criteria, int expectedFeatures) { + this.criteria = criteria; + this.expectedFeatures = expectedFeatures; + } + + @Parameterized.Parameters(name = "{index} {0}") + public static Collection params() { + return Arrays.asList( + new Object[][] { + {"name LIKE 'B_r%'", 3}, + {"name NOT LIKE 'B_r%'", 240}, + {"pop_other between 1000000 and 3000000", 75}, + {"pop_other not between 1000000 and 3000000", 168}, + {"name IN ('Kiev','kobenhavn','Berlin','athens','foo')", 2}, + {"name NOT IN ('Kiev','kobenhavn','Berlin','athens','foo')", 241}, + {"pop_other in (1038288,1611692,3013258,3013257,3013259)", 3}, + {"pop_other not in (1038288,1611692,3013258,3013257,3013259)", 240}, + {"\"date\" in (DATE('2021-04-16'),DATE('2022-04-16'),DATE('2022-04-18'))", 2}, + { + "\"date\" not in (DATE('2021-04-16'),DATE('2022-04-16'),DATE('2022-04-18'))", + 1 + }, + {"start in (TIMESTAMP('2022-04-16T10:13:19Z'))", 1}, + {"start not in (TIMESTAMP('2022-04-16T10:13:19Z'))", 2}, + {"boolean in (true)", 2}, + {"boolean not in (false)", 2} + }); + } + + public @Test void testConformance() throws IOException, CQLException { + DataStore ds = naturalEarthData(); + int feat = featuresReturned(ds); + ds.dispose(); + + assertEquals(this.expectedFeatures, feat); + } + + private int featuresReturned(DataStore ds) throws CQLException, IOException { + Filter filter = CQL2.toFilter(this.criteria); + + return ds.getFeatureSource("ne_110m_populated_places_simple").getFeatures(filter).size(); + } +} diff --git a/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest26OnlineTest.java b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest26OnlineTest.java new file mode 100644 index 00000000000..5ea5980b681 --- /dev/null +++ b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest26OnlineTest.java @@ -0,0 +1,69 @@ +package org.geotools.filter.text.cql_2.conformance; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.geootols.filter.text.cql_2.CQL2; +import org.geotools.api.data.DataStore; +import org.geotools.api.filter.Filter; +import org.geotools.filter.text.cql2.CQLException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** See table 12 from section A.7.2 Conformance test 26. */ +@RunWith(Parameterized.class) +public class ConformanceTest26OnlineTest extends ATSOnlineTest { + + private final String dataset; + private final String criteria; + private final int expectedFeatures; + + public ConformanceTest26OnlineTest(String dataset, String criteria, int expectedFeatures) { + this.dataset = dataset; + this.criteria = criteria; + this.expectedFeatures = expectedFeatures; + } + + @Parameterized.Parameters(name = "{index} {0} {1}") + public static Collection params() { + return Arrays.asList( + new Object[][] { + {"ne_110m_admin_0_countries", "S_INTERSECTS(geom,BBOX(0,40,10,50))", 8}, + {"ne_110m_admin_0_countries", "S_INTERSECTS(geom,BBOX(150,-90,-150,90))", 10}, + {"ne_110m_admin_0_countries", "S_INTERSECTS(geom,POINT(7.02 49.92))", 1}, + { + "ne_110m_admin_0_countries", + "S_INTERSECTS(geom,BBOX(0,40,10,50)) and S_INTERSECTS(geom,BBOX(5,50,10,60))", + 3 + }, + { + "ne_110m_admin_0_countries", + "S_INTERSECTS(geom,BBOX(0,40,10,50)) and not S_INTERSECTS(geom,BBOX(5,50,10,60))", + 5 + }, + { + "ne_110m_admin_0_countries", + "S_INTERSECTS(geom,BBOX(0,40,10,50)) or S_INTERSECTS(geom,BBOX(-90,40,-60,50))", + 10 + }, + {"ne_110m_populated_places_simple", "S_INTERSECTS(geom,BBOX(0,40,10,50))", 7}, + {"ne_110m_rivers_lake_centerlines", "S_INTERSECTS(geom,BBOX(-180,-90,0,90))", 4} + }); + } + + public @Test void testConformance() throws CQLException, IOException { + DataStore ds = naturalEarthData(); + int feat = featuresReturned(ds); + ds.dispose(); + + assertEquals(this.expectedFeatures, feat); + } + + private int featuresReturned(DataStore ds) throws CQLException, IOException { + Filter filter = CQL2.toFilter(this.criteria); + return ds.getFeatureSource(this.dataset).getFeatures(filter).size(); + } +} diff --git a/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest8OnlineTest.java b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest8OnlineTest.java new file mode 100644 index 00000000000..3ecc2197ba6 --- /dev/null +++ b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest8OnlineTest.java @@ -0,0 +1,121 @@ +package org.geotools.filter.text.cql_2.conformance; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.geootols.filter.text.cql_2.CQL2; +import org.geotools.api.data.DataStore; +import org.geotools.api.filter.Filter; +import org.geotools.filter.text.cql2.CQLException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** See table 7 from section A.3.5 Conformance test 8. */ +@RunWith(Parameterized.class) +public class ConformanceTest8OnlineTest extends ATSOnlineTest { + + private final String dataset; + private final String criteria; + private final int expectedFeatures; + + public ConformanceTest8OnlineTest(String dataset, String criteria, int expectedFeatures) { + this.dataset = dataset; + this.criteria = criteria; + this.expectedFeatures = expectedFeatures; + } + + @Parameterized.Parameters(name = "{index} {0} {1}") + public static Collection params() { + return Arrays.asList( + new Object[][] { + {"ne_110m_admin_0_countries", "NAME='Luxembourg'", 1}, + {"ne_110m_admin_0_countries", "NAME>='Luxembourg'", 84}, + {"ne_110m_admin_0_countries", "NAME>'Luxembourg'", 83}, + {"ne_110m_admin_0_countries", "NAME<='Luxembourg'", 94}, + {"ne_110m_admin_0_countries", "NAME<'Luxembourg'", 93}, + {"ne_110m_admin_0_countries", "NAME<>'Luxembourg'", 176}, + {"ne_110m_admin_0_countries", "POP_EST=37589262", 1}, + {"ne_110m_admin_0_countries", "POP_EST>=37589262", 39}, + {"ne_110m_admin_0_countries", "POP_EST>37589262", 38}, + {"ne_110m_admin_0_countries", "POP_EST<=37589262", 139}, + {"ne_110m_admin_0_countries", "POP_EST<37589262", 138}, + {"ne_110m_admin_0_countries", "POP_EST<>37589262", 176}, + {"ne_110m_populated_places_simple", "name IS NOT NULL", 243}, + {"ne_110m_populated_places_simple", "name IS NULL", 0}, + {"ne_110m_populated_places_simple", "name='København'", 1}, + {"ne_110m_populated_places_simple", "name>='København'", 137}, + {"ne_110m_populated_places_simple", "name>'København'", 136}, + {"ne_110m_populated_places_simple", "name<='København'", 107}, + {"ne_110m_populated_places_simple", "name<'København'", 106}, + {"ne_110m_populated_places_simple", "name<>'København'", 242}, + {"ne_110m_populated_places_simple", "pop_other IS NOT NULL", 243}, + {"ne_110m_populated_places_simple", "pop_other IS NULL", 0}, + {"ne_110m_populated_places_simple", "pop_other=1038288", 1}, + {"ne_110m_populated_places_simple", "pop_other>=1038288", 123}, + {"ne_110m_populated_places_simple", "pop_other>1038288", 122}, + {"ne_110m_populated_places_simple", "pop_other<=1038288", 121}, + {"ne_110m_populated_places_simple", "pop_other<1038288", 120}, + {"ne_110m_populated_places_simple", "pop_other<>1038288", 242}, + {"ne_110m_populated_places_simple", "\"date\" IS NOT NULL", 3}, + {"ne_110m_populated_places_simple", "\"date\" IS NULL", 240}, + {"ne_110m_populated_places_simple", "\"date\"=DATE('2022-04-16')", 1}, + {"ne_110m_populated_places_simple", "\"date\">=DATE('2022-04-16')", 2}, + {"ne_110m_populated_places_simple", "\"date\">DATE('2022-04-16')", 1}, + {"ne_110m_populated_places_simple", "\"date\"<=DATE('2022-04-16')", 2}, + {"ne_110m_populated_places_simple", "\"date\"DATE('2022-04-16')", 2}, + {"ne_110m_populated_places_simple", "start IS NOT NULL", 3}, + {"ne_110m_populated_places_simple", "start IS NULL", 240}, + { + "ne_110m_populated_places_simple", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + 1 + }, + { + "ne_110m_populated_places_simple", + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + 2 + }, + { + "ne_110m_populated_places_simple", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + 2 + }, + { + "ne_110m_populated_places_simple", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + 1 + }, + { + "ne_110m_populated_places_simple", + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + 2 + }, + {"ne_110m_populated_places_simple", "boolean IS NOT NULL", 3}, + {"ne_110m_populated_places_simple", "boolean IS NULL", 240}, + {"ne_110m_populated_places_simple", "boolean=true", 2}, + {"ne_110m_populated_places_simple", "boolean=false", 1} + }); + } + + public @Test void testConformance() throws CQLException, IOException { + DataStore ds = naturalEarthData(); + int feat = featuresReturned(ds); + ds.dispose(); + + assertEquals(this.expectedFeatures, feat); + } + + private int featuresReturned(DataStore ds) throws CQLException, IOException { + Filter filter = CQL2.toFilter(this.criteria); + return ds.getFeatureSource(this.dataset).getFeatures(filter).size(); + } +} diff --git a/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest9OnlineTest.java b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest9OnlineTest.java new file mode 100644 index 00000000000..9a35e46cee3 --- /dev/null +++ b/modules/unsupported/cql2-text/src/test/java/org/geotools/filter/text/cql_2/conformance/ConformanceTest9OnlineTest.java @@ -0,0 +1,586 @@ +package org.geotools.filter.text.cql_2.conformance; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.geootols.filter.text.cql_2.CQL2; +import org.geotools.api.data.DataStore; +import org.geotools.api.filter.Filter; +import org.geotools.filter.text.cql2.CQLException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** See table 8 from section A.3.6 Conformance test 9. */ +@RunWith(Parameterized.class) +public class ConformanceTest9OnlineTest extends ATSOnlineTest { + + private final String p1; + private final String p2; + private final String p3; + private final String p4; + private final int expectedFeatures; + + public ConformanceTest9OnlineTest( + String p1, String p2, String p3, String p4, int expectedFeatures) { + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + this.p4 = p4; + this.expectedFeatures = expectedFeatures; + } + + @Parameterized.Parameters(name = "{index} {0} {1} {2} {3}") + public static Collection params() { + return Arrays.asList( + new Object[][] { + { + "pop_other<>1038288", + "name<>'København'", + "pop_other IS NULL", + "name<'København'", + 1 + }, + { + "pop_other<>1038288", + "name>'København'", + "name<='København'", + "boolean=true", + 107 + }, + { + "start IS NULL", + "pop_other IS NOT NULL", + "pop_other IS NOT NULL", + "pop_other>1038288", + 124 + }, + { + "pop_other<1038288", + "pop_other>1038288", + "pop_other IS NULL", + "start'København'", + 2 + }, + { + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + "name<>'København'", + "boolean=true", + "name<'København'", + 2 + }, + { + "pop_other=1038288", + "start IS NULL", + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + "boolean IS NOT NULL", + 242 + }, + { + "start IS NULL", + "pop_other>1038288", + "start IS NOT NULL", + "name>'København'", + 122 + }, + { + "pop_other<1038288", + "name<>'København'", + "name='København'", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "name IS NOT NULL", + "start IS NULL", + "pop_other<1038288", + 120 + }, + { + "name>='København'", + "start IS NOT NULL", + "boolean=true", + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + 137 + }, + { + "start IS NOT NULL", + "name>='København'", + "start IS NOT NULL", + "name IS NOT NULL", + 3 + }, + { + "name IS NULL", + "name<'København'", + "pop_other IS NOT NULL", + "boolean IS NOT NULL", + 243 + }, + { + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + "name>'København'", + "pop_other=1038288", + "name<'København'", + 3 + }, + { + "start'København'", + 138 + }, + { + "pop_other IS NOT NULL", + "start IS NULL", + "pop_other>=1038288", + "name>'København'", + 62 + }, + { + "name='København'", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "boolean=true", + "pop_other IS NULL", + 243 + }, + { + "name>'København'", + "pop_other<1038288", + "pop_other>1038288", + "name<='København'", + 122 + }, + { + "pop_other<>1038288", + "name='København'", + "name<='København'", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + 243 + }, + { + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other=1038288", + "start IS NULL", + 3 + }, + { + "name<>'København'", + "boolean=true", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "start IS NULL", + 2 + }, + { + "name IS NULL", + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + "startTIMESTAMP('2022-04-16T10:13:19Z')", + "name>'København'", + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + "name IS NOT NULL", + 3 + }, + { + "name<>'København'", + "pop_other<>1038288", + "pop_other<1038288", + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + 2 + }, + { + "boolean IS NULL", + "pop_other>1038288", + "boolean IS NOT NULL", + "pop_other IS NULL", + 122 + }, + { + "pop_other=1038288", + "start IS NULL", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other IS NOT NULL", + 2 + }, + {"pop_other<>1038288", "start IS NULL", "pop_other>1038288", "boolean=true", 2}, + { + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other<1038288", + "name<='København'", + "pop_other=1038288", + 2 + }, + { + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + "name<='København'", + "name<>'København'", + 107 + }, + {"boolean=true", "name IS NOT NULL", "boolean IS NULL", "pop_other=1038288", 1}, + { + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other=1038288", + "pop_other<1038288", + "name<>'København'", + 122 + }, + { + "pop_other<>1038288", + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + "start IS NOT NULL", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + 3 + }, + { + "name<>'København'", + "pop_other<>1038288", + "pop_other IS NOT NULL", + "name IS NOT NULL", + 243 + }, + { + "name='København'", + "pop_other<1038288", + "start IS NOT NULL", + "pop_other<>1038288", + 3 + }, + { + "name<'København'", + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + 2 + }, + { + "boolean=true", + "pop_other<1038288", + "name IS NOT NULL", + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + 3 + }, + { + "pop_other<=1038288", + "name<'København'", + "pop_other<1038288", + "pop_other<1038288", + 243 + }, + { + "pop_other IS NULL", + "name<='København'", + "name='København'", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + 2 + }, + { + "pop_other<1038288", + "name<>'København'", + "pop_other<>1038288", + "name<>'København'", + 243 + }, + { + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other IS NULL", + "startTIMESTAMP('2022-04-16T10:13:19Z')", + "name='København'", + "boolean IS NULL", + "pop_other<>1038288", + 241 + }, + { + "boolean=true", + "pop_other<=1038288", + "name<>'København'", + "pop_other IS NULL", + 2 + }, + { + "name IS NOT NULL", + "pop_other<=1038288", + "start IS NOT NULL", + "boolean IS NOT NULL", + 124 + }, + { + "pop_other<=1038288", + "pop_other<1038288", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other>1038288", + 1 + }, + { + "start IS NOT NULL", + "boolean IS NOT NULL", + "name>='København'", + "pop_other IS NOT NULL", + 137 + }, + { + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + "start IS NOT NULL", + "pop_other>1038288", + "pop_other<1038288", + 1 + }, + { + "pop_other<=1038288", + "name<='København'", + "boolean IS NULL", + "start IS NOT NULL", + 198 + }, + { + "name>='København'", + "name>='København'", + "name<='København'", + "name>='København'", + 107 + }, + { + "boolean=true", + "startTIMESTAMP('2022-04-16T10:13:19Z')", + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other IS NULL", + "pop_other<=1038288", + 1 + }, + { + "pop_other<1038288", + "name='København'", + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + "name<'København'", + 181 + }, + { + "pop_other<1038288", + "pop_other<=1038288", + "pop_other IS NULL", + "start IS NOT NULL", + 121 + }, + { + "name>='København'", + "pop_other>=1038288", + "boolean=true", + "name IS NOT NULL", + 79 + }, + { + "boolean IS NULL", + "name<>'København'", + "boolean IS NULL", + "pop_other IS NOT NULL", + 240 + }, + { + "pop_other<1038288", + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + "name>'København'", + "pop_other<=1038288", + 199 + }, + { + "name<='København'", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "name<'København'", + "boolean IS NULL", + 106 + }, + { + "pop_other IS NOT NULL", + "name<>'København'", + "pop_other<1038288", + "pop_other<=1038288", + 121 + }, + { + "name>='København'", + "start IS NOT NULL", + "name>='København'", + "name IS NOT NULL", + 137 + }, + { + "pop_other<1038288", + "start=1038288", + 1 + }, + { + "pop_other>=1038288", + "name>'København'", + "boolean IS NOT NULL", + "start IS NOT NULL", + 184 + }, + { + "start IS NOT NULL", + "name<>'København'", + "name<='København'", + "name IS NULL", + 241 + }, + { + "name>='København'", + "pop_other<>1038288", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "name<>'København'", + 2 + }, + { + "boolean IS NOT NULL", + "pop_other<=1038288", + "pop_other=1038288", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + 1 + }, + { + "name IS NOT NULL", + "start IS NOT NULL", + "start IS NOT NULL", + "name>='København'", + 241 + }, + { + "pop_other=1038288", + "pop_other IS NOT NULL", + "start IS NOT NULL", + "name<>'København'", + 2 + }, + { + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "start IS NULL", + "pop_other>1038288", + "pop_other<=1038288", + 1 + }, + { + "name IS NULL", + "start IS NOT NULL", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "name IS NOT NULL", + 1 + }, + { + "boolean IS NOT NULL", + "name='København'", + "boolean IS NOT NULL", + "name IS NOT NULL", + 3 + }, + { + "pop_other<>1038288", + "pop_other<>1038288", + "pop_other=1038288", + "pop_other<=1038288", + 1 + }, + { + "pop_other IS NULL", + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "boolean IS NOT NULL", + 241 + }, + { + "startTIMESTAMP('2022-04-16T10:13:19Z')", + "name<'København'", + 2 + }, + { + "pop_other>1038288", + "pop_other<>1038288", + "start<>TIMESTAMP('2022-04-16T10:13:19Z')", + "name<>'København'", + 2 + }, + { + "start>=TIMESTAMP('2022-04-16T10:13:19Z')", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other=1038288", + "name IS NOT NULL", + 2 + }, + { + "pop_other<=1038288", + "start IS NOT NULL", + "start<=TIMESTAMP('2022-04-16T10:13:19Z')", + "boolean IS NOT NULL", + 242 + }, + { + "boolean=true", + "start>TIMESTAMP('2022-04-16T10:13:19Z')", + "pop_other<1038288", + "pop_other<>1038288", + 122 + }, + { + "pop_other>=1038288", + "pop_other>1038288", + "boolean IS NULL", + "pop_other=1038288", + 121 + }, + { + "name<'København'", + "pop_other>1038288", + "start=TIMESTAMP('2022-04-16T10:13:19Z')", + "boolean=true", + 44 + } + }); + } + + public @Test void testConformance() throws IOException, CQLException { + DataStore ds = naturalEarthData(); + int feat = featuresReturned(ds); + ds.dispose(); + + assertEquals(this.expectedFeatures, feat); + } + + private int featuresReturned(DataStore ds) throws CQLException, IOException { + Filter filter = + CQL2.toFilter( + String.format( + "(NOT (%s) AND %s) OR (%s and %s) or not (%s OR %s)", + p2, p1, p3, p4, p1, p4)); + + return ds.getFeatureSource("ne_110m_populated_places_simple").getFeatures(filter).size(); + } +}