@@ -18,14 +18,15 @@ package org.locationtech.geomesa.core.index
18
18
19
19
20
20
import java .util .Map .Entry
21
+ import java .util .{Collection => JCollection , Map => JMap }
21
22
23
+ import com .typesafe .scalalogging .slf4j .Logging
22
24
import org .apache .accumulo .core .client .{BatchScanner , IteratorSetting , Scanner }
23
25
import org .apache .accumulo .core .data .{Key , Value , Range => AccRange }
24
26
import org .apache .hadoop .io .Text
25
27
import org .geotools .data .Query
26
28
import org .geotools .filter .text .ecql .ECQL
27
29
import org .geotools .temporal .`object` .DefaultPeriod
28
- import org .locationtech .geomesa .core .DEFAULT_FILTER_PROPERTY_NAME
29
30
import org .locationtech .geomesa .core .data .FeatureEncoding .FeatureEncoding
30
31
import org .locationtech .geomesa .core .data ._
31
32
import org .locationtech .geomesa .core .data .tables .{AttributeTable , RecordTable }
@@ -34,14 +35,17 @@ import org.locationtech.geomesa.core.index.FilterHelper._
34
35
import org .locationtech .geomesa .core .index .QueryPlanner ._
35
36
import org .locationtech .geomesa .core .iterators ._
36
37
import org .locationtech .geomesa .core .util .{BatchMultiScanner , SelfClosingIterator }
38
+ import org .locationtech .geomesa .core .{DEFAULT_FILTER_PROPERTY_NAME , index }
39
+ import org .locationtech .geomesa .utils .geotools .Conversions .RichAttributeDescriptor
37
40
import org .opengis .feature .simple .SimpleFeatureType
38
41
import org .opengis .filter .expression .{Expression , Literal , PropertyName }
39
42
import org .opengis .filter .temporal .{After , Before , During , TEquals }
40
43
import org .opengis .filter .{Filter , PropertyIsEqualTo , PropertyIsLike , _ }
41
44
42
45
import scala .collection .JavaConversions ._
46
+ import scala .collection .JavaConverters ._
43
47
44
- trait AttributeIdxStrategy extends Strategy {
48
+ trait AttributeIdxStrategy extends Strategy with Logging {
45
49
46
50
/**
47
51
* Perform scan against the Attribute Index Table and get an iterator returning records from the Record table
@@ -77,7 +81,7 @@ trait AttributeIdxStrategy extends Strategy {
77
81
78
82
val opts = oFilter.map(f => DEFAULT_FILTER_PROPERTY_NAME -> ECQL .toCQL(f)).toMap
79
83
80
- iteratorChoice.iterator match {
84
+ val iter = iteratorChoice.iterator match {
81
85
case IndexOnlyIterator =>
82
86
indexOnlyIterator(attrScanner,
83
87
featureType,
@@ -100,6 +104,19 @@ trait AttributeIdxStrategy extends Strategy {
100
104
opts,
101
105
output)
102
106
}
107
+
108
+ // wrap with a de-duplicator if the attribute could have multiple values, and it won't be
109
+ // de-duped by the query planner
110
+ if (! IndexSchema .mayContainDuplicates(featureType)
111
+ && featureType.getDescriptor(attributeName).isMultiValued) {
112
+ val returnSft = Option (query.getHints.get(TRANSFORM_SCHEMA ).asInstanceOf [SimpleFeatureType ])
113
+ .getOrElse(featureType)
114
+ val decoder = SimpleFeatureDecoder (returnSft, iqp.featureEncoding)
115
+ val deduper = new DeDuplicatingIterator (iter, (_ : Key , value : Value ) => decoder.extractFeatureId(value))
116
+ SelfClosingIterator (deduper)
117
+ } else {
118
+ iter
119
+ }
103
120
}
104
121
105
122
/**
@@ -242,21 +259,34 @@ trait AttributeIdxStrategy extends Strategy {
242
259
* @return
243
260
*/
244
261
def getEncodedAttrIdxRow (sft : SimpleFeatureType , prop : String , value : Any ): String = {
262
+ val descriptor = sft.getDescriptor(prop)
245
263
// the class type as defined in the SFT
246
- val expectedBinding = sft.getDescriptor(prop) .getType.getBinding
264
+ val expectedBinding = descriptor .getType.getBinding
247
265
// the class type of the literal pulled from the query
248
266
val actualBinding = value.getClass
249
- val typedValue =
250
- if (expectedBinding.equals(actualBinding)) {
251
- value
267
+ val typedValue = if (expectedBinding == actualBinding) {
268
+ value
269
+ } else if (descriptor.isCollection) {
270
+ // we need to encode with the collection type
271
+ val collectionType = index.getCollectionType(descriptor).head
272
+ if (collectionType == actualBinding) {
273
+ Seq (value).asJava
252
274
} else {
253
- // type mismatch, encoding won't work b/c class is stored as part of the row
254
- // try to convert to the appropriate class
255
- AttributeTable .convertType(value, actualBinding, expectedBinding)
275
+ Seq (AttributeTable .convertType(value, actualBinding, collectionType)).asJava
256
276
}
277
+ } else if (descriptor.isMap) {
278
+ // TODO GEOMESA-454 - support querying against map attributes
279
+ Map .empty.asJava
280
+ } else {
281
+ // type mismatch, encoding won't work b/c value is wrong class
282
+ // try to convert to the appropriate class
283
+ AttributeTable .convertType(value, actualBinding, expectedBinding)
284
+ }
257
285
258
286
val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(sft)
259
- AttributeTable .getAttributeIndexRow(rowIdPrefix, prop, Some (typedValue))
287
+ // grab the first encoded row - right now there will only ever be a single item in the seq
288
+ // eventually we may support searching a whole collection at once
289
+ AttributeTable .getAttributeIndexRows(rowIdPrefix, descriptor, Some (typedValue)).head
260
290
}
261
291
262
292
/**
@@ -305,36 +335,38 @@ class AttributeIdxEqualsStrategy extends AttributeIdxStrategy {
305
335
306
336
override def execute (acc : AccumuloConnectorCreator ,
307
337
iqp : QueryPlanner ,
308
- featureType : SimpleFeatureType ,
338
+ sft : SimpleFeatureType ,
309
339
query : Query ,
310
340
output : ExplainerOutputType ): SelfClosingIterator [Entry [Key , Value ]] = {
311
- val (strippedQuery, filter) = partitionFilter(query, featureType )
341
+ val (strippedQuery, filter) = partitionFilter(query, sft )
312
342
val (prop, range) =
313
343
filter match {
314
344
case f : PropertyIsEqualTo =>
315
345
val (prop, lit, _) = checkOrder(f.getExpression1, f.getExpression2)
316
- (prop, AccRange .exact(getEncodedAttrIdxRow(featureType , prop, lit)))
346
+ (prop, AccRange .exact(getEncodedAttrIdxRow(sft , prop, lit)))
317
347
318
348
case f : TEquals =>
319
349
val (prop, lit, _) = checkOrder(f.getExpression1, f.getExpression2)
320
- (prop, AccRange .exact(getEncodedAttrIdxRow(featureType , prop, lit)))
350
+ (prop, AccRange .exact(getEncodedAttrIdxRow(sft , prop, lit)))
321
351
322
352
case f : PropertyIsNil =>
323
353
val prop = f.getExpression.asInstanceOf [PropertyName ].getPropertyName
324
- val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(featureType)
325
- (prop, AccRange .exact(AttributeTable .getAttributeIndexRow(rowIdPrefix, prop, None )))
354
+ val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(sft)
355
+ val exact = AttributeTable .getAttributeIndexRows(rowIdPrefix, sft.getDescriptor(prop), None ).head
356
+ (prop, AccRange .exact(exact))
326
357
327
358
case f : PropertyIsNull =>
328
359
val prop = f.getExpression.asInstanceOf [PropertyName ].getPropertyName
329
- val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(featureType)
330
- (prop, AccRange .exact(AttributeTable .getAttributeIndexRow(rowIdPrefix, prop, None )))
360
+ val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(sft)
361
+ val exact = AttributeTable .getAttributeIndexRows(rowIdPrefix, sft.getDescriptor(prop), None ).head
362
+ (prop, AccRange .exact(exact))
331
363
332
364
case _ =>
333
365
val msg = s " Unhandled filter type in equals strategy: ${filter.getClass.getName}"
334
366
throw new RuntimeException (msg)
335
367
}
336
368
337
- attrIdxQuery(acc, strippedQuery, iqp, featureType , prop, range, output)
369
+ attrIdxQuery(acc, strippedQuery, iqp, sft , prop, range, output)
338
370
}
339
371
}
340
372
@@ -415,31 +447,33 @@ class AttributeIdxRangeStrategy extends AttributeIdxStrategy {
415
447
attrIdxQuery(acc, strippedQuery, iqp, featureType, prop, range, output)
416
448
}
417
449
418
- private def greaterThanRange (featureType : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
419
- val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(featureType)
420
- val start = new Text (getEncodedAttrIdxRow(featureType, prop, lit))
421
- val end = AccRange .followingPrefix(new Text (AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, prop)))
450
+ private def greaterThanRange (sft : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
451
+ val rowIdPrefix = getTableSharingPrefix(sft)
452
+ val start = new Text (getEncodedAttrIdxRow(sft, prop, lit))
453
+ val endPrefix = AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, sft.getDescriptor(prop))
454
+ val end = AccRange .followingPrefix(new Text (endPrefix))
422
455
new AccRange (start, false , end, false )
423
456
}
424
457
425
- private def greaterThanOrEqualRange (featureType : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
426
- val rowIdPrefix = org.locationtech.geomesa.core.index.getTableSharingPrefix(featureType)
427
- val start = new Text (getEncodedAttrIdxRow(featureType, prop, lit))
428
- val end = AccRange .followingPrefix(new Text (AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, prop)))
458
+ private def greaterThanOrEqualRange (sft : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
459
+ val rowIdPrefix = getTableSharingPrefix(sft)
460
+ val start = new Text (getEncodedAttrIdxRow(sft, prop, lit))
461
+ val endPrefix = AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, sft.getDescriptor(prop))
462
+ val end = AccRange .followingPrefix(new Text (endPrefix))
429
463
new AccRange (start, true , end, false )
430
464
}
431
465
432
- private def lessThanRange (featureType : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
433
- val rowIdPrefix = org.locationtech.geomesa.core.index. getTableSharingPrefix(featureType )
434
- val start = AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, prop)
435
- val end = getEncodedAttrIdxRow(featureType , prop, lit)
466
+ private def lessThanRange (sft : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
467
+ val rowIdPrefix = getTableSharingPrefix(sft )
468
+ val start = AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, sft.getDescriptor( prop) )
469
+ val end = getEncodedAttrIdxRow(sft , prop, lit)
436
470
new AccRange (start, false , end, false )
437
471
}
438
472
439
- private def lessThanOrEqualRange (featureType : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
440
- val rowIdPrefix = org.locationtech.geomesa.core.index. getTableSharingPrefix(featureType )
441
- val start = AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, prop)
442
- val end = getEncodedAttrIdxRow(featureType , prop, lit)
473
+ private def lessThanOrEqualRange (sft : SimpleFeatureType , prop : String , lit : AnyRef ): AccRange = {
474
+ val rowIdPrefix = getTableSharingPrefix(sft )
475
+ val start = AttributeTable .getAttributeIndexRowPrefix(rowIdPrefix, sft.getDescriptor( prop) )
476
+ val end = getEncodedAttrIdxRow(sft , prop, lit)
443
477
new AccRange (start, false , end, true )
444
478
}
445
479
}
0 commit comments