Skip to content

Commit

Permalink
GEOMESA-3350 Support JSON path filter operators in json-type attribut…
Browse files Browse the repository at this point in the history
…es (#3119)
  • Loading branch information
elahrvivaz authored Jun 10, 2024
1 parent e4eceaf commit 80e283e
Show file tree
Hide file tree
Showing 16 changed files with 1,500 additions and 397 deletions.
2 changes: 2 additions & 0 deletions build/cqs.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ joda-time:joda-time 2.12.6 compile
net.java.dev.jna:jna 5.14.0 compile
net.java.dev.jna:jna-platform 5.14.0 compile
net.jodah:typetools 0.6.1 compile
net.minidev:accessors-smart 2.5.1 compile
net.minidev:json-smart 2.5.1 compile
net.sf.geographiclib:GeographicLib-Java 1.49 compile
net.sf.saxon:Saxon-HE 12.4 compile
org.antlr:antlr4-runtime 4.7.2 compile
Expand Down
9 changes: 7 additions & 2 deletions docs/user/datastores/json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ JSON attributes are still strings, and are set as any other strings. The value m
SimpleFeature sf = ...
sf.setAttribute("json", json);
JSON attributes can be queried using JSONPath expressions. The first part of the path refers to the simple
feature attribute name, and the rest of the path is applied to the JSON attribute. Note that in ECQL, path
JSON attributes can be queried using
`JSONPath expressions <https://github.com/json-path/JsonPath?tab=readme-ov-file#jayway-jsonpath>`__. The first part of the path
refers to the simple feature attribute name, and the rest of the path is applied to the JSON attribute. Note that in ECQL, path
expressions must be enclosed in double quotes.

.. code-block:: java
Expand All @@ -40,6 +41,10 @@ expressions must be enclosed in double quotes.
sf.getAttribute("\"$.json.foo\""); // returns "baz"
sf.getAttribute("\"$.json.bar\""); // returns null
.. note::

GeoMesa does not support the JSONPath functions `stddev()`, `keys()`, `concat()` and `append()`.

.. _json_path_filter_function:

JSONPath CQL Filter Function
Expand Down
6 changes: 6 additions & 0 deletions docs/user/datastores/runtime_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ geomesa.ingest.local.batch.size
Controls the batch size for local ingests via the command-line tools. By default, feature writers will be
flushed every 20,000 features.

geomesa.json.cache.expiry
+++++++++++++++++++++++++

Controls the length of time that parsed JSON-path objects are kept in memory. The expiry is specified as a duration, e.g.
``10 minutes`` or ``1 hour``. The default is ``10 minutes``.

geomesa.metadata.expiry
+++++++++++++++++++++++

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ class AccumuloDataStoreJsonTest extends Specification with TestWithFeatureType {
sf1.setAttribute(1, "POINT(45 61)")

val sf2 = new ScalaSimpleFeature(sft, "2")
sf2.setAttribute(0, getJson(45, 62, """{"id":"two","characteristics":{"height":20,"weight":200}}"""))
sf2.setAttribute(0, getJson(45, 62, """{"id":"two","characteristics":{"height":20,"weight":200,"age":20}}"""))
sf2.setAttribute(1, "POINT(45 62)")

val sf3 = new ScalaSimpleFeature(sft, "3")
sf3.setAttribute(0, getJson(45, 63, """{"id":"three","characteristics":{"height":30,"weight":300}}"""))
sf3.setAttribute(0, getJson(45, 63, """{"id":"three","characteristics":{"height":30,"weight":300,"age":20}}"""))
sf3.setAttribute(1, "POINT(45 63)")

val sf4 = new ScalaSimpleFeature(sft, "4")
Expand All @@ -63,6 +63,13 @@ class AccumuloDataStoreJsonTest extends Specification with TestWithFeatureType {
features.head.getID mustEqual "3"
features.head.getAttributes mustEqual sf3.getAttributes // note: whitespace will be stripped from json string
}
"support queries against json attributes with json-path filters" in {
val query = new Query(sftName, ECQL.toFilter("""jsonPath('$.json.properties.characteristics[?(@.height == 30 && @.age == 20)]') IS NOT NULL"""))
val features = SelfClosingIterator(ds.getFeatureReader(query, Transaction.AUTO_COMMIT)).toList
features must haveLength(1)
features.head.getID mustEqual "3"
features.head.getAttributes mustEqual sf3.getAttributes // note: whitespace will be stripped from json string
}
"support queries against json arrays" in {
val query = new Query(sftName, ECQL.toFilter(""""$.json[0]" = 'a1'"""))
val features = SelfClosingIterator(ds.getFeatureReader(query, Transaction.AUTO_COMMIT)).toList
Expand Down
4 changes: 4 additions & 0 deletions geomesa-features/geomesa-feature-kryo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</dependency>
<dependency>
<groupId>org.parboiled</groupId>
<artifactId>parboiled-scala_${scala.binary.version}</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ class JsonPathFilterFunction extends FunctionExpressionImpl(

private val cache = new ConcurrentHashMap[String, PropertyAccessor]

override def evaluate(obj: java.lang.Object): AnyRef = {
val sf = try {
obj.asInstanceOf[SimpleFeature]
} catch {
case e: Exception => throw new IllegalArgumentException(s"Expected SimpleFeature, Received ${obj.getClass}. " +
s"Only simple features are supported. ${obj.toString}", e)
override def evaluate(obj: Object): AnyRef = {
val sf = obj match {
case sf: SimpleFeature => sf
case _ =>
throw new IllegalArgumentException(
s"Expected SimpleFeature, but received ${obj.getClass}. Only simple features are supported: $obj")
}
val base = params.get(0) match {
case p: PropertyName => p.getPropertyName // for property name expressions, we want the attribute name
Expand Down
Loading

0 comments on commit 80e283e

Please sign in to comment.