diff --git a/INSTALL b/INSTALL
index 4ced783e752c2d5f048aa07e1fc13e370dc6ab5e..70f70563a4df4f4ddc6b1fb1e6d55beb7783e30f 100644
--- a/INSTALL
+++ b/INSTALL
@@ -134,8 +134,10 @@ just going to run Ganeti). More specifically:
 - `bytestring <http://hackage.haskell.org/package/bytestring>`_ and
   `utf8-string <http://hackage.haskell.org/package/utf8-string>`_
   libraries; these usually come with the GHC compiler
+- `regex-pcre <http://hackage.haskell.org/package/regex-pcre>`_,
+  bindings for the ``pcre`` library
 
-All of these are also available as package in Debian/Ubuntu::
+Some of these are also available as package in Debian/Ubuntu::
 
   $ apt-get install ghc6 libghc6-json-dev libghc6-network-dev \
                     libghc6-parallel-dev libghc6-curl-dev
@@ -152,7 +154,8 @@ Note that more recent version have switched to GHC 7.x and the packages
 were renamed::
 
   $ apt-get install ghc libghc-json-dev libghc-network-dev \
-                    libghc-parallel-dev libghc-curl-dev
+                    libghc-parallel-dev libghc-curl-dev \
+                    libghc-regex-pcre-dev
 
 If using a distribution which does not provide them, first install
 the Haskell platform. You can also install ``cabal`` manualy::
@@ -162,7 +165,7 @@ the Haskell platform. You can also install ``cabal`` manualy::
 Then install the additional libraries via
 ``cabal``::
 
-  $ cabal install json network parallel curl
+  $ cabal install json network parallel curl regex-pcre
 
 The compilation of the htools components is automatically enabled when
 the compiler and the requisite libraries are found. You can use the
diff --git a/Makefile.am b/Makefile.am
index ee420b7d8a8a86b8ab151db776c3cf38025ba297..6c8bcc78e81d520d35c0aa923a95229727d894fe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -418,6 +418,7 @@ HS_LIB_SRCS = \
 	htools/Ganeti/Rpc.hs \
 	htools/Ganeti/Qlang.hs \
 	htools/Ganeti/Query/Common.hs \
+	htools/Ganeti/Query/Filter.hs \
 	htools/Ganeti/Query/Node.hs \
 	htools/Ganeti/Query/Query.hs \
 	htools/Ganeti/Query/Types.hs \
diff --git a/htools/Ganeti/Query/Filter.hs b/htools/Ganeti/Query/Filter.hs
new file mode 100644
index 0000000000000000000000000000000000000000..7e8c1e18418ed9fb58c998f2d7eabb6e7cc453ac
--- /dev/null
+++ b/htools/Ganeti/Query/Filter.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE Rank2Types #-}
+
+{-| Implementation of the Ganeti Query2 filterning.
+
+The filtering of results should be done in two phases.
+
+In the first phase, before contacting any remote nodes for runtime
+data, the filtering should be executed with 'Nothing' for the runtime
+context. This will make all non-runtime filters filter correctly,
+whereas all runtime filters will respond successfully. As described in
+the Python version too, this makes for example /Or/ filters very
+inefficient if they contain runtime fields.
+
+Once this first filtering phase has been done, we hopefully eliminated
+some remote nodes out of the list of candidates, we run the remote
+data gathering, and we evaluate the filter again, this time with a
+'Just' runtime context. This will make all filters work correctly.
+
+Note that the second run will re-evaluate the config/simple fields,
+without caching; this is not perfect, but we consider config accesses
+very cheap (and the configuration snapshot we have won't change
+between the two runs, hence we will not get inconsistent results).
+
+-}
+
+{-
+
+Copyright (C) 2012 Google Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+-}
+
+module Ganeti.Query.Filter
+  ( compileFilter
+  , evaluateFilter
+  ) where
+
+import Control.Applicative
+import qualified Data.Map as Map
+import Data.Traversable (traverse)
+import Text.JSON (JSValue(..), fromJSString)
+import Text.JSON.Pretty (pp_value)
+import Text.Regex.PCRE ((=~))
+
+import Ganeti.BasicTypes
+import Ganeti.Objects
+import Ganeti.Qlang
+import Ganeti.Query.Types
+import Ganeti.HTools.JSON
+
+-- | Compiles a filter based on field names to one based on getters.
+compileFilter :: FieldMap a b
+              -> Filter FilterField
+              -> Result (Filter (FieldGetter a b))
+compileFilter fm =
+  traverse (\field -> maybe (Bad $ "Can't find field named '" ++ field ++ "'")
+                      (Ok . snd) (field `Map.lookup` fm))
+
+-- | Wraps a getter, filter pair. If the getter is 'FieldRuntime' but
+-- we don't have a runtime context, we skip the filtering, returning
+-- \"pass\". Otherwise, we pass the actual value to the filter.
+wrapGetter :: ConfigData
+           -> Maybe b
+           -> a
+           -> FieldGetter a b
+           -> (JSValue -> Result Bool)
+           -> Result Bool
+wrapGetter cfg b a getter faction =
+  case tryGetter cfg b a getter of
+    Nothing -> Ok True -- runtime missing, accepting the value
+    Just v ->
+      case v of
+        ResultEntry RSNormal (Just fval) -> faction fval
+        ResultEntry RSNormal Nothing ->
+          Bad "Internal error: Getter returned RSNormal/Nothing"
+        _ -> Ok True -- filter has no data to work, accepting it
+
+-- | Helper to evaluate a filter getter (and the value it generates) in
+-- a boolean context.
+trueFilter :: JSValue -> Result Bool
+trueFilter (JSBool x) = Ok x
+trueFilter v = Bad $ "Unexpected value '" ++ show (pp_value v) ++
+               "' in boolean context"
+
+-- | A type synonim for a rank-2 comparator function. This is used so
+-- that we can pass the usual '<=', '>', '==' functions to 'binOpFilter'
+-- and for them to be used in multiple contexts.
+type Comparator = (Eq a, Ord a) => a -> a -> Bool
+
+-- | Helper to evaluate a filder getter (and the value it generates)
+-- in a boolean context. Note the order of arguments is reversed from
+-- the filter definitions (due to the call chain), make sure to
+-- compare in the reverse order too!.
+binOpFilter :: Comparator -> FilterValue -> JSValue -> Result Bool
+binOpFilter comp (QuotedString y) (JSString x) =
+  Ok $ fromJSString x `comp` y
+binOpFilter comp (NumericValue y) (JSRational _ x) =
+  Ok $ x `comp` fromIntegral y
+binOpFilter _ expr actual =
+  Bad $ "Invalid types in comparison, trying to compare " ++
+      show (pp_value actual) ++ " with '" ++ show expr ++ "'"
+
+-- | Implements the 'RegexpFilter' matching.
+regexpFilter :: String -> JSValue -> Result Bool
+regexpFilter re (JSString val) = Ok $ (fromJSString val) =~ re
+regexpFilter _ x =
+  Bad $ "Invalid field value used in regexp matching,\
+        \ expecting string but got '" ++ show (pp_value x) ++ "'"
+
+-- | Implements the 'ContainsFilter' matching.
+containsFilter :: FilterValue -> JSValue -> Result Bool
+-- note: the next two implementations are the same, but we have to
+-- repeat them due to the encapsulation done by FilterValue
+containsFilter (QuotedString val) lst = do
+  lst' <- fromJVal lst
+  return $ val `elem` lst'
+containsFilter (NumericValue val) lst = do
+  lst' <- fromJVal lst
+  return $ val `elem` lst'
+
+-- | Verifies if a given item passes a filter. The runtime context
+-- might be missing, in which case most of the filters will consider
+-- this as passing the filter.
+evaluateFilter :: ConfigData -> Maybe b -> a
+               -> Filter (FieldGetter a b)
+               -> Result Bool
+evaluateFilter _ _  _ EmptyFilter = Ok True
+evaluateFilter c mb a (AndFilter flts) =
+  all id <$> mapM (evaluateFilter c mb a) flts
+evaluateFilter c mb a (OrFilter flts)  =
+  any id <$> mapM (evaluateFilter c mb a) flts
+evaluateFilter c mb a (NotFilter flt)  =
+  not <$> evaluateFilter c mb a flt
+evaluateFilter c mb a (TrueFilter getter)  =
+  wrapGetter c mb a getter trueFilter
+evaluateFilter c mb a (EQFilter getter val) =
+  wrapGetter c mb a getter (binOpFilter (==) val)
+evaluateFilter c mb a (LTFilter getter val) =
+  wrapGetter c mb a getter (binOpFilter (<) val)
+evaluateFilter c mb a (LEFilter getter val) =
+  wrapGetter c mb a getter (binOpFilter (<=) val)
+evaluateFilter c mb a (GTFilter getter val) =
+  wrapGetter c mb a getter (binOpFilter (>) val)
+evaluateFilter c mb a (GEFilter getter val) =
+  wrapGetter c mb a getter (binOpFilter (>=) val)
+evaluateFilter c mb a (RegexpFilter getter re) =
+  wrapGetter c mb a getter (regexpFilter re)
+evaluateFilter c mb a (ContainsFilter getter val) =
+  wrapGetter c mb a getter (containsFilter val)
+
+-- | Runs a getter with potentially missing runtime context.
+tryGetter :: ConfigData -> Maybe b -> a -> FieldGetter a b -> Maybe ResultEntry
+tryGetter _   _ item (FieldSimple getter)  = Just $ getter item
+tryGetter cfg _ item (FieldConfig getter)  = Just $ getter cfg item
+tryGetter _  rt item (FieldRuntime getter) =
+  maybe Nothing (\rt' -> Just $ getter rt' item) rt
+tryGetter _   _ _    FieldUnknown          = Just $
+                                             ResultEntry RSUnknown Nothing
diff --git a/htools/Ganeti/Query/Query.hs b/htools/Ganeti/Query/Query.hs
index 071e6e5a59ac6c58b17f1a12068178ed42966ab7..65544c7e3333ea8cd93cd1dbe4001e73ab9b9b1c 100644
--- a/htools/Ganeti/Query/Query.hs
+++ b/htools/Ganeti/Query/Query.hs
@@ -23,11 +23,34 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 -}
 
+{-
+
+TODO: problems with the current model:
+
+1. There's nothing preventing a result such as ResultEntry RSNormal
+Nothing, or ResultEntry RSNoData (Just ...); ideally, we would
+separate the the RSNormal and other types; we would need a new data
+type for this, though, with JSON encoding/decoding
+
+2. We don't have a way to 'bind' a FieldDefinition's field type
+(e.q. QFTBool) with the actual value that is returned from a
+FieldGetter. This means that the various getter functions can return
+divergent types for the same field when evaluated against multiple
+items. This is bad; it only works today because we 'hide' everything
+behind JSValue, but is not nice at all. We should probably remove the
+separation between FieldDefinition and the FieldGetter, and introduce
+a new abstract data type, similar to QFT*, that contains the values
+too.
+
+-}
+
 module Ganeti.Query.Query
+
     ( query
     , queryFields
     ) where
 
+import Control.Monad (filterM)
 import Data.Maybe (fromMaybe)
 import qualified Data.Map as Map
 
@@ -35,6 +58,7 @@ import Ganeti.BasicTypes
 import Ganeti.HTools.JSON
 import Ganeti.Qlang
 import Ganeti.Query.Common
+import Ganeti.Query.Filter
 import Ganeti.Query.Types
 import Ganeti.Query.Node
 import Ganeti.Objects
@@ -70,12 +94,19 @@ query :: ConfigData   -- ^ The current configuration
       -> Query        -- ^ The query (item, fields, filter)
       -> IO (Result QueryResult) -- ^ Result
 
-query cfg (Query QRNode fields _) = return $ do
+query cfg (Query QRNode fields qfilter) = return $ do
+  cfilter <- compileFilter nodeFieldsMap qfilter
   let selected = getSelectedFields nodeFieldsMap fields
       (fdefs, fgetters) = unzip selected
       nodes = Map.elems . fromContainer $ configNodes cfg
-      fdata = map (\node -> map (execGetter cfg NodeRuntime node) fgetters)
-              nodes
+  -- runs first pass of the filter, without a runtime context; this
+  -- will limit the nodes that we'll contact for runtime data
+  fnodes <- filterM (\n -> evaluateFilter cfg Nothing n cfilter)
+            nodes
+  -- here we would run the runtime data gathering, then filter again
+  -- the nodes, based on existing runtime data
+  let fdata = map (\node -> map (execGetter cfg NodeRuntime node) fgetters)
+              fnodes
   return QueryResult { qresFields = fdefs, qresData = fdata }
 
 query _ (Query qkind _ _) =