From 05ac718f5af44095f07d388e05aeee16801d6350 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Sun, 26 Aug 2012 15:00:31 +0200
Subject: [PATCH] Parameterize the Filter type

In preparation for introducing filtering functionality, we convert the
'Filter' type from a '*' kind to a '* -> *' kind.

This allows us to define some general properties for the filter, and
for example introduce later an easy filter compilation, etc.

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Agata Murawska <agatamurawska@google.com>
---
 htools/Ganeti/HTools/QC.hs |  4 +-
 htools/Ganeti/Luxi.hs      |  2 +-
 htools/Ganeti/Qlang.hs     | 89 +++++++++++++++++++++++++-------------
 3 files changed, 63 insertions(+), 32 deletions(-)

diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs
index 47473d3b2..dd855d9ae 100644
--- a/htools/Ganeti/HTools/QC.hs
+++ b/htools/Ganeti/HTools/QC.hs
@@ -590,13 +590,13 @@ instance Arbitrary Rpc.RpcCallNodeInfo where
 
 -- | Custom 'Qlang.Filter' generator (top-level), which enforces a
 -- (sane) limit on the depth of the generated filters.
-genFilter :: Gen Qlang.Filter
+genFilter :: Gen (Qlang.Filter Qlang.FilterField)
 genFilter = choose (0, 10) >>= genFilter'
 
 -- | Custom generator for filters that correctly halves the state of
 -- the generators at each recursive step, per the QuickCheck
 -- documentation, in order not to run out of memory.
-genFilter' :: Int -> Gen Qlang.Filter
+genFilter' :: Int -> Gen (Qlang.Filter Qlang.FilterField)
 genFilter' 0 =
   oneof [ return Qlang.EmptyFilter
         , Qlang.TrueFilter     <$> getName
diff --git a/htools/Ganeti/Luxi.hs b/htools/Ganeti/Luxi.hs
index c7b771bb6..2869d7972 100644
--- a/htools/Ganeti/Luxi.hs
+++ b/htools/Ganeti/Luxi.hs
@@ -112,7 +112,7 @@ $(genLuxiOp "LuxiOp"
   [ (luxiReqQuery,
     [ ("what",    [t| Qlang.ItemType |])
     , ("fields",  [t| [String]  |])
-    , ("qfilter", [t| Qlang.Filter |])
+    , ("qfilter", [t| Qlang.Filter Qlang.FilterField |])
     ])
   , (luxiReqQueryFields,
     [ ("what",    [t| Qlang.ItemType |])
diff --git a/htools/Ganeti/Qlang.hs b/htools/Ganeti/Qlang.hs
index e23910403..37471e7e1 100644
--- a/htools/Ganeti/Qlang.hs
+++ b/htools/Ganeti/Qlang.hs
@@ -27,6 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 module Ganeti.Qlang
     ( Filter(..)
+    , FilterField
     , FilterValue(..)
     , Fields
     , Query(..)
@@ -46,6 +47,8 @@ module Ganeti.Qlang
     ) where
 
 import Control.Applicative
+import Data.Foldable
+import Data.Traversable (Traversable, traverse, fmapDefault, foldMapDefault)
 import Data.Ratio (numerator, denominator)
 import Text.JSON.Pretty (pp_value)
 import Text.JSON.Types
@@ -106,24 +109,26 @@ $(makeJSONInstance ''ItemType)
 -- | List of requested fields.
 type Fields = [ String ]
 
--- | Query2 filter expression.
-data Filter
-    = EmptyFilter                             -- ^ No filter at all
-    | AndFilter      [ Filter ]               -- ^ & [<expression>, ...]
-    | OrFilter       [ Filter ]               -- ^ | [<expression>, ...]
-    | NotFilter      Filter                   -- ^ ! <expression>
-    | TrueFilter     FilterField              -- ^ ? <field>
-    | EQFilter       FilterField FilterValue  -- ^ (=|!=) <field> <value>
-    | LTFilter       FilterField FilterValue  -- ^ < <field> <value>
-    | GTFilter       FilterField FilterValue  -- ^ > <field> <value>
-    | LEFilter       FilterField FilterValue  -- ^ <= <field> <value>
-    | GEFilter       FilterField FilterValue  -- ^ >= <field> <value>
-    | RegexpFilter   FilterField FilterRegexp -- ^ =~ <field> <regexp>
-    | ContainsFilter FilterField FilterValue  -- ^ =[] <list-field> <value>
+-- | Query2 filter expression. It's a parameteric type since we can
+-- filter different \"things\"; e.g. field names, or actual field
+-- getters, etc.
+data Filter a
+    = EmptyFilter                   -- ^ No filter at all
+    | AndFilter      [ Filter a ]   -- ^ & [<expression>, ...]
+    | OrFilter       [ Filter a ]   -- ^ | [<expression>, ...]
+    | NotFilter      (Filter a)     -- ^ ! <expression>
+    | TrueFilter     a              -- ^ ? <field>
+    | EQFilter       a FilterValue  -- ^ (=|!=) <field> <value>
+    | LTFilter       a FilterValue  -- ^ < <field> <value>
+    | GTFilter       a FilterValue  -- ^ > <field> <value>
+    | LEFilter       a FilterValue  -- ^ <= <field> <value>
+    | GEFilter       a FilterValue  -- ^ >= <field> <value>
+    | RegexpFilter   a FilterRegexp -- ^ =~ <field> <regexp>
+    | ContainsFilter a FilterValue  -- ^ =[] <list-field> <value>
       deriving (Show, Read, Eq)
 
 -- | Serialiser for the 'Filter' data type.
-showFilter :: Filter -> JSValue
+showFilter :: (JSON a) => Filter a -> JSValue
 showFilter (EmptyFilter)          = JSNull
 showFilter (AndFilter exprs)      =
   JSArray $ (showJSON C.qlangOpAnd):(map showJSON exprs)
@@ -149,7 +154,7 @@ showFilter (ContainsFilter field value) =
   JSArray [showJSON C.qlangOpContains, showJSON field, showJSON value]
 
 -- | Deserializer for the 'Filter' data type.
-readFilter :: JSValue -> Result Filter
+readFilter :: (JSON a) => JSValue -> Result (Filter a)
 readFilter JSNull = Ok EmptyFilter
 readFilter (JSArray (JSString op:args)) =
   readFilterArray (fromJSString op) args
@@ -160,28 +165,30 @@ readFilter v =
 -- | Helper to deserialise an array corresponding to a single filter
 -- and return the built filter. Note this looks generic but is (at
 -- least currently) only used for the NotFilter.
-readFilterArg :: (Filter -> Filter) -- ^ Constructor
-              -> [JSValue]          -- ^ Single argument
-              -> Result Filter
+readFilterArg :: (JSON a) =>
+                 (Filter a -> Filter a) -- ^ Constructor
+              -> [JSValue]              -- ^ Single argument
+              -> Result (Filter a)
 readFilterArg constr [flt] = constr <$> readJSON flt
 readFilterArg _ v = Error $ "Cannot deserialise field, expected [filter]\
                             \ but got " ++ show (pp_value (showJSON v))
 
 -- | Helper to deserialise an array corresponding to a single field
 -- and return the built filter.
-readFilterField :: (FilterField -> Filter) -- ^ Constructor
-                -> [JSValue]               -- ^ Single argument
-                -> Result Filter
+readFilterField :: (JSON a) =>
+                   (a -> Filter a)   -- ^ Constructor
+                -> [JSValue]         -- ^ Single argument
+                -> Result (Filter a)
 readFilterField constr [field] = constr <$> readJSON field
 readFilterField _ v = Error $ "Cannot deserialise field, expected [fieldname]\
                               \ but got " ++ show (pp_value (showJSON v))
 
 -- | Helper to deserialise an array corresponding to a field and
 -- value, returning the built filter.
-readFilterFieldValue :: (JSON a) =>
-                        (FilterField -> a -> Filter) -- ^ Constructor
-                     -> [JSValue] -- ^ Arguments array
-                     -> Result Filter
+readFilterFieldValue :: (JSON a, JSON b) =>
+                        (a -> b -> Filter a) -- ^ Constructor
+                     -> [JSValue]            -- ^ Arguments array
+                     -> Result (Filter a)
 readFilterFieldValue constr [field, value] =
   constr <$> readJSON field <*> readJSON value
 readFilterFieldValue _ v =
@@ -189,7 +196,7 @@ readFilterFieldValue _ v =
           \ but got " ++ show (pp_value (showJSON v))
 
 -- | Inner deserialiser for 'Filter'.
-readFilterArray :: String -> [JSValue] -> Result Filter
+readFilterArray :: (JSON a) => String -> [JSValue] -> Result (Filter a)
 readFilterArray op args
   | op == C.qlangOpAnd      = AndFilter <$> mapM readJSON args
   | op == C.qlangOpOr       = OrFilter  <$> mapM readJSON args
@@ -204,10 +211,34 @@ readFilterArray op args
   | op == C.qlangOpContains = readFilterFieldValue ContainsFilter args
   | otherwise = Error $ "Unknown filter operand '" ++ op ++ "'"
 
-instance JSON Filter where
+instance (JSON a) => JSON (Filter a) where
   showJSON = showFilter
   readJSON = readFilter
 
+-- Traversable implementation for 'Filter'.
+traverseFlt :: (Applicative f) => (a -> f b) -> Filter a -> f (Filter b)
+traverseFlt _ EmptyFilter       = pure EmptyFilter
+traverseFlt f (AndFilter flts)  = AndFilter <$> (traverse (traverseFlt f) flts)
+traverseFlt f (OrFilter  flts)  = OrFilter  <$> (traverse (traverseFlt f) flts)
+traverseFlt f (NotFilter flt)   = NotFilter <$> (traverseFlt f flt)
+traverseFlt f (TrueFilter a)    = TrueFilter <$> f a
+traverseFlt f (EQFilter a fval) = EQFilter <$> f a <*> pure fval
+traverseFlt f (LTFilter a fval) = LTFilter <$> f a <*> pure fval
+traverseFlt f (GTFilter a fval) = GTFilter <$> f a <*> pure fval
+traverseFlt f (LEFilter a fval) = LEFilter <$> f a <*> pure fval
+traverseFlt f (GEFilter a fval) = GEFilter <$> f a <*> pure fval
+traverseFlt f (RegexpFilter a re)     = RegexpFilter <$> f a <*> pure re
+traverseFlt f (ContainsFilter a fval) = ContainsFilter <$> f a <*> pure fval
+
+instance Traversable Filter where
+  traverse = traverseFlt
+
+instance Functor Filter where
+  fmap = fmapDefault
+
+instance Foldable Filter where
+  foldMap = foldMapDefault
+
 -- | Field name to filter on.
 type FilterField = String
 
@@ -281,7 +312,7 @@ type ResultValue = JSValue
 -- * Main Qlang queries and responses.
 
 -- | Query2 query.
-data Query = Query ItemType Fields Filter
+data Query = Query ItemType Fields (Filter FilterField)
 
 -- | Query2 result.
 $(buildObject "QueryResult" "qres"
-- 
GitLab