@@ -22,6 +22,12 @@ module Database.Bloodhound.Types
2222 , mkBoolQuery
2323 , mkRangeQuery
2424 , mkQueryStringQuery
25+ , mkAggregations
26+ , mkTermsAggregation
27+ , mkTermsScriptAggregation
28+ , mkDateHistogram
29+ , toTerms
30+ , toDateHistogram
2531 , omitNulls
2632 , Version(..)
2733 , Status(..)
@@ -143,16 +149,33 @@ module Database.Bloodhound.Types
143149 , FieldOrFields(..)
144150 , Monoid(..)
145151 , ToJSON(..)
152+ , Interval(..)
153+ , TimeInterval(..)
154+ , ExecutionHint(..)
155+ , CollectionMode(..)
156+ , TermOrder(..)
157+ , TermInclusion(..)
158+ , Aggregation(..)
159+ , Aggregations
160+ , AggregationResults
161+ , Bucket(..)
162+ , BucketAggregation(..)
163+ , TermsAggregation(..)
164+ , DateHistogramAggregation(..)
165+ , TermsResult(..)
166+ , DateHistogramResult(..)
146167 ) where
147168
148169import Control.Applicative
149170import Data.Aeson
171+import Data.Aeson.Types (parseMaybe)
150172import qualified Data.ByteString.Lazy.Char8 as L
151173import Data.List (nub)
152174import Data.List.NonEmpty (NonEmpty (..))
153175import Data.Monoid
154176import Data.Text (Text)
155177import qualified Data.Text as T
178+import qualified Data.Map.Strict as M
156179import Data.Time.Clock (UTCTime)
157180import qualified Data.Vector as V
158181import GHC.Generics (Generic)
@@ -473,9 +496,10 @@ type TrackSortScores = Bool
473496type From = Int
474497type Size = Int
475498
476−data Search = Search { queryBody :: Maybe Query
477− , filterBody :: Maybe Filter
478− , sortBody :: Maybe Sort
499+data Search = Search { queryBody :: Maybe Query
500+ , filterBody :: Maybe Filter
501+ , sortBody :: Maybe Sort
502+ , aggBody :: Maybe Aggregations
479503 -- default False
480504 , trackSortScores :: TrackSortScores
481505 , from :: From
@@ -915,10 +939,11 @@ data DistanceRange =
915939 , distanceTo :: Distance } deriving (Eq, Show)
916940
917941data FromJSON a => SearchResult a =
918− SearchResult { took :: Int
919− , timedOut :: Bool
920− , shards :: ShardResult
921− , searchHits :: SearchHits a } deriving (Eq, Show)
942+ SearchResult { took :: Int
943+ , timedOut :: Bool
944+ , shards :: ShardResult
945+ , searchHits :: SearchHits a
946+ , aggregations :: Maybe AggregationResults } deriving (Eq, Show)
922947
923948type Score = Double
924949
@@ -942,6 +967,201 @@ data ShardResult =
942967showText :: Show a => a -> Text
943968showText = T.pack . show
944969
970+type Aggregations = M.Map Text Aggregation
971+
972+emptyAggregations :: Aggregations
973+emptyAggregations = M.empty
974+
975+mkAggregations :: Text -> Aggregation -> Aggregations
976+mkAggregations name aggregation = M.insert name aggregation emptyAggregations
977+
978+data TermOrder = TermOrder{ termSortField :: Text
979+ , termSortOrder :: SortOrder } deriving (Eq, Show)
980+
981+data TermInclusion = TermInclusion Text
982+ | TermPattern Text Text deriving (Eq, Show)
983+
984+data CollectionMode = BreadthFirst
985+ | DepthFirst deriving (Eq, Show)
986+
987+data ExecutionHint = Ordinals
988+ | GlobalOrdinals
989+ | GlobalOrdinalsHash
990+ | GlobalOrdinalsLowCardinality
991+ | Map deriving (Eq, Show)
992+
993+data TimeInterval = Weeks
994+ | Days
995+ | Hours
996+ | Minutes
997+ | Seconds deriving (Eq)
998+
999+data Interval = Year
1000+ | Quarter
1001+ | Month
1002+ | Week
1003+ | Day
1004+ | Hour
1005+ | Minute
1006+ | Second
1007+ | FractionalInterval Float TimeInterval deriving (Eq, Show)
1008+
1009+data Aggregation = TermsAgg TermsAggregation
1010+ | DateHistogramAgg DateHistogramAggregation deriving (Eq, Show)
1011+
1012+
1013+data TermsAggregation = TermsAggregation { term :: Either Text Text
1014+ , termInclude :: Maybe TermInclusion
1015+ , termExclude :: Maybe TermInclusion
1016+ , termOrder :: Maybe TermOrder
1017+ , termMinDocCount :: Maybe Int
1018+ , termSize :: Maybe Int
1019+ , termShardSize :: Maybe Int
1020+ , termCollectMode :: Maybe CollectionMode
1021+ , termExecutionHint :: Maybe ExecutionHint
1022+ , termAggs :: Maybe Aggregations
1023+ } deriving (Eq, Show)
1024+
1025+data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
1026+ , dateInterval :: Interval
1027+ , dateFormat :: Maybe Text
1028+ , datePreZone :: Maybe Text
1029+ , datePostZone :: Maybe Text
1030+ , datePreOffset :: Maybe Text
1031+ , datePostOffset :: Maybe Text
1032+ , dateAggs :: Maybe Aggregations
1033+ } deriving (Eq, Show)
1034+
1035+
1036+mkTermsAggregation :: Text -> TermsAggregation
1037+mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
1038+
1039+mkTermsScriptAggregation :: Text -> TermsAggregation
1040+mkTermsScriptAggregation t = TermsAggregation (Right t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
1041+
1042+mkDateHistogram :: FieldName -> Interval -> DateHistogramAggregation
1043+mkDateHistogram t i = DateHistogramAggregation t i Nothing Nothing Nothing Nothing Nothing Nothing
1044+
1045+instance ToJSON TermOrder where
1046+ toJSON (TermOrder termSortField termSortOrder) = object [termSortField .= termSortOrder]
1047+
1048+instance ToJSON TermInclusion where
1049+ toJSON (TermInclusion x) = toJSON x
1050+ toJSON (TermPattern pattern flags) = omitNulls [ "pattern" .= pattern,
1051+ "flags" .= flags]
1052+
1053+instance ToJSON CollectionMode where
1054+ toJSON BreadthFirst = "breadth_first"
1055+ toJSON DepthFirst = "depth_first"
1056+
1057+instance ToJSON ExecutionHint where
1058+ toJSON Ordinals = "ordinals"
1059+ toJSON GlobalOrdinals = "global_ordinals"
1060+ toJSON GlobalOrdinalsHash = "global_ordinals_hash"
1061+ toJSON GlobalOrdinalsLowCardinality = "global_ordinals_low_cardinality"
1062+ toJSON Map = "map"
1063+
1064+instance ToJSON Interval where
1065+ toJSON Year = "year"
1066+ toJSON Quarter = "quarter"
1067+ toJSON Month = "month"
1068+ toJSON Week = "week"
1069+ toJSON Day = "day"
1070+ toJSON Hour = "hour"
1071+ toJSON Minute = "minute"
1072+ toJSON Second = "second"
1073+ toJSON (FractionalInterval fraction interval) = toJSON $ show fraction ++ show interval
1074+
1075+instance Show TimeInterval where
1076+ show Weeks = "w"
1077+ show Days = "d"
1078+ show Hours = "h"
1079+ show Minutes = "m"
1080+ show Seconds = "s"
1081+
1082+instance ToJSON Aggregation where
1083+ toJSON (TermsAgg (TermsAggregation term include exclude order minDocCount size shardSize collectMode executionHint termAggs)) =
1084+ omitNulls ["terms" .= omitNulls [ toJSON' term,
1085+ "include" .= toJSON include,
1086+ "exclude" .= toJSON exclude,
1087+ "order" .= toJSON order,
1088+ "min_doc_count" .= toJSON minDocCount,
1089+ "size" .= toJSON size,
1090+ "shard_size" .= toJSON shardSize,
1091+ "collect_mode" .= toJSON collectMode,
1092+ "execution_hint" .= toJSON executionHint
1093+ ],
1094+ "aggs" .= toJSON termAggs ]
1095+ where
1096+ toJSON' x = case x of { Left y -> "field" .= toJSON y; Right y -> "script" .= toJSON y }
1097+
1098+ toJSON (DateHistogramAgg (DateHistogramAggregation field interval format preZone postZone preOffset postOffset dateHistoAggs)) =
1099+ omitNulls ["date_histogram" .= omitNulls [ "field" .= toJSON field,
1100+ "interval" .= toJSON interval,
1101+ "format" .= toJSON format,
1102+ "pre_zone" .= toJSON preZone,
1103+ "post_zone" .= toJSON postZone,
1104+ "pre_offset" .= toJSON preOffset,
1105+ "post_offset" .= toJSON postOffset
1106+ ],
1107+ "aggs" .= toJSON dateHistoAggs ]
1108+
1109+type AggregationResults = M.Map Text Value
1110+
1111+class BucketAggregation a where
1112+ key :: a -> Text
1113+ docCount :: a -> Int
1114+ aggs :: a -> Maybe AggregationResults
1115+
1116+
1117+data (FromJSON a, BucketAggregation a) => Bucket a = Bucket { buckets :: [a]} deriving (Show)
1118+
1119+data TermsResult = TermsResult { termKey :: Text
1120+ , termsDocCount :: Int
1121+ , termsAggs :: Maybe AggregationResults } deriving (Show)
1122+
1123+data DateHistogramResult = DateHistogramResult { dateKey :: Int
1124+ , dateKeyStr :: Maybe Text
1125+ , dateDocCount :: Int
1126+ , dateHistogramAggs :: Maybe AggregationResults } deriving (Show)
1127+
1128+toTerms :: Text -> AggregationResults -> Maybe (Bucket TermsResult)
1129+toTerms t a = M.lookup t a >>= deserialize
1130+ where deserialize = parseMaybe parseJSON
1131+
1132+toDateHistogram :: Text -> AggregationResults -> Maybe (Bucket DateHistogramResult)
1133+toDateHistogram t a = M.lookup t a >>= deserialize
1134+ where deserialize = parseMaybe parseJSON
1135+
1136+instance BucketAggregation TermsResult where
1137+ key = termKey
1138+ docCount = termsDocCount
1139+ aggs = termsAggs
1140+
1141+instance BucketAggregation DateHistogramResult where
1142+ key = showText . dateKey
1143+ docCount = dateDocCount
1144+ aggs = dateHistogramAggs
1145+
1146+instance (FromJSON a, BucketAggregation a) => FromJSON (Bucket a) where
1147+ parseJSON (Object v) = Bucket <$>
1148+ v .: "buckets"
1149+ parseJSON _ = mempty
1150+
1151+instance FromJSON TermsResult where
1152+ parseJSON (Object v) = TermsResult <$>
1153+ v .: "key" <*>
1154+ v .: "doc_count" <*>
1155+ v .:? "aggregations"
1156+ parseJSON _ = mempty
1157+
1158+instance FromJSON DateHistogramResult where
1159+ parseJSON (Object v) = DateHistogramResult <$>
1160+ v .: "key" <*>
1161+ v .:? "key_as_string" <*>
1162+ v .: "doc_count" <*>
1163+ v .:? "aggregations"
1164+ parseJSON _ = mempty
9451165
9461166instance Monoid Filter where
9471167 mempty = IdentityFilter
@@ -1026,11 +1246,11 @@ instance ToJSON Filter where
10261246 toJSON (RangeFilter (FieldName fieldName) (Left halfRange) rangeExecution cache) =
10271247 object ["range" .=
10281248 object [fieldName .=
1029− object [key .= val]
1249+ object [rqFKey .= rqFVal]
10301250 , "execution" .= toJSON rangeExecution
10311251 , "_cache" .= cache]]
10321252 where
1033− (key, val) = halfRangeToKV halfRange
1253+ (rqFKey, rqFVal) = halfRangeToKV halfRange
10341254
10351255 toJSON (RangeFilter (FieldName fieldName) (Right range) rangeExecution cache) =
10361256 object ["range" .=
@@ -1244,8 +1464,8 @@ instance ToJSON RangeQuery where
12441464 toJSON (RangeQuery (FieldName fieldName) (Left halfRange) boost) =
12451465 object [ fieldName .= conjoined ]
12461466 where conjoined = [ "boost" .= toJSON boost
1247− , key .= val ]
1248− (key, val) = halfRangeToKV halfRange
1467+ , rqKey .= rqVal ]
1468+ (rqKey, rqVal) = halfRangeToKV halfRange
12491469
12501470
12511471instance ToJSON PrefixQuery where
@@ -1536,10 +1756,11 @@ instance (FromJSON a) => FromJSON (EsResult a) where
15361756
15371757
15381758instance ToJSON Search where
1539− toJSON (Search query sFilter sort sTrackSortScores sFrom sSize) =
1759+ toJSON (Search query sFilter sort searchAggs sTrackSortScores sFrom sSize) =
15401760 omitNulls [ "query" .= query
15411761 , "filter" .= sFilter
15421762 , "sort" .= sort
1763+ , "aggregations" .= searchAggs
15431764 , "from" .= sFrom
15441765 , "size" .= sSize
15451766 , "track_scores" .= sTrackSortScores ]
@@ -1677,10 +1898,11 @@ instance ToJSON BoolMatch where
16771898
16781899instance (FromJSON a) => FromJSON (SearchResult a) where
16791900 parseJSON (Object v) = SearchResult <$>
1680− v .: "took" <*>
1681− v .: "timed_out" <*>
1682− v .: "_shards" <*>
1683− v .: "hits"
1901+ v .: "took" <*>
1902+ v .: "timed_out" <*>
1903+ v .: "_shards" <*>
1904+ v .: "hits" <*>
1905+ v .:? "aggregations"
16841906 parseJSON _ = empty
16851907
16861908instance (FromJSON a) => FromJSON (SearchHits a) where