diff --git a/src/Storm/Model/PersistenceStrategy/Db.php b/src/Storm/Model/PersistenceStrategy/Db.php
index ed70a15d2504ea309c69cdae59f3dbc6941d9c23..86541ea0d868491d9545f25395ad69114fe90867 100644
--- a/src/Storm/Model/PersistenceStrategy/Db.php
+++ b/src/Storm/Model/PersistenceStrategy/Db.php
@@ -98,9 +98,9 @@ class Storm_Model_PersistenceStrategy_Db
 
   protected function _generateWhereClauseForKeyAndValue(string $key,
                                                         $value) : string {
-    $clause = ($value instanceof Storm_Model_PersistenceStrategy_Clause)
+    $clause = ($value instanceof Storm_Query_Clause)
       ? $value
-      : Storm_Model_PersistenceStrategy_Clause::newWith($key, $value);
+      : Storm_Query_Clause::newWith($key, $value);
 
     return $clause->getFormatDb($this->getTable());
   }
@@ -172,7 +172,7 @@ class Storm_Model_PersistenceStrategy_Db
    */
   public function _generateSelectFor($args) {
     if ($args instanceof Storm_Query)
-      return $args->getSelectAndAssemble();
+      return $this->_selectForQuery($args);
 
     if (array_key_exists('role', $args) && array_key_exists('model', $args)) {
       $model = $args['model'];
@@ -196,6 +196,14 @@ class Storm_Model_PersistenceStrategy_Db
   }
 
 
+  protected function _selectForQuery(Storm_Query $query) {
+    $select = $this->getTable()->select();
+    $query->assemble($select);
+
+    return $select;
+  }
+
+
   protected function _addInSelect($select, string $field, $value_or_clause) : self {
     if (in_array($field, ['order', 'limit', 'where'], true)) {
       $select->$field($value_or_clause);
@@ -212,8 +220,8 @@ class Storm_Model_PersistenceStrategy_Db
       return $this;
     }
 
-    if (!($value_or_clause instanceof Storm_Model_PersistenceStrategy_Clause))
-      $value_or_clause = Storm_Model_PersistenceStrategy_Clause::newWith($field, $value_or_clause);
+    if (!($value_or_clause instanceof Storm_Query_Clause))
+      $value_or_clause = Storm_Query_Clause::newWith($field, $value_or_clause);
 
     $select->where($this->_generateWhereClauseForKeyAndValue($field, $value_or_clause));
     return $this;
diff --git a/src/Storm/Model/PersistenceStrategy/Match.php b/src/Storm/Model/PersistenceStrategy/Match.php
deleted file mode 100644
index e601ea22083d6a532c07f6f12cd4f234819e7d64..0000000000000000000000000000000000000000
--- a/src/Storm/Model/PersistenceStrategy/Match.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-/*
-STORM is under the MIT License (MIT)
-
-Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-*/
-
-class Storm_Model_PersistenceStrategy_Match {
-
-  protected array $_terms;
-  protected bool $_boolean_mode;
-  protected bool $_strict;
-
-  public function __construct(array $terms, bool $strict, bool $boolean_mode) {
-    $this->_terms = $terms;
-    $this->_strict = $strict;
-    $this->_boolean_mode = $boolean_mode;
-  }
-
-
-  public function getFormatDb($table) : string {
-    $content = '';
-    foreach ($this->_terms as $key => $term)
-      $content = implode(' ', array_filter([$content,
-                                            $term->getFormatDb($this->_strict)]));
-
-    return sprintf('AGAINST(%s%s)',
-                   $table->getAdapter()->quoteInto('?', $content, null, null),
-                   ($this->_boolean_mode ? ' IN BOOLEAN MODE' : ''));
-  }
-
-
-  public function containVolatile(string $contents) : bool {
-    if (!$contents)
-      return false;
-
-    foreach ($this->_terms as $term) {
-      if ($this->_strict && !$term->containVolatile($contents))
-        return false;
-      if (!$this->_strict && $term->containVolatile($contents))
-        return true;
-    }
-
-    return $this->_strict;
-  }
-
-
-  public function getCompareValue(string $contents) : int {
-    $compare = 0;
-
-    foreach ($this->_terms as $term) {
-      $new_compare = $term->getCompareValue($contents);
-      if ($this->_strict && 0 === $new_compare)
-        return 0;
-
-      $compare += $term->getCompareValue($contents);
-    }
-
-    return $this->_boolean_mode
-      ? min($compare, 1)
-      : ceil(($compare / count(explode(' ', $contents))) * 10);
-  }
-}
diff --git a/src/Storm/Model/PersistenceStrategy/MatchTerms.php b/src/Storm/Model/PersistenceStrategy/MatchTerms.php
index bb5eb3032244eb3a00289989c3dc6af5ec44b48f..737d76fcc2b8dc6f33f891b9df4544bd9f426eac 100644
--- a/src/Storm/Model/PersistenceStrategy/MatchTerms.php
+++ b/src/Storm/Model/PersistenceStrategy/MatchTerms.php
@@ -30,12 +30,9 @@ class Storm_Model_PersistenceStrategy_MatchTerms {
   protected array $_values;
   protected bool $_exact;
 
-  public function __construct($array_or_value, ?bool $exact = false) {
-    if (!is_array($array_or_value))
-      $array_or_value = explode(' ', $array_or_value);
-
-    $this->_values = $array_or_value;
-    $this->_exact = $exact ?? false;
+  public function __construct(array $values, bool $exact) {
+    $this->_values = $values;
+    $this->_exact = $exact;
   }
 
 
diff --git a/src/Storm/Model/PersistenceStrategy/Volatile.php b/src/Storm/Model/PersistenceStrategy/Volatile.php
index afdd2b73c2120ae55b109503a5d421fbb427718c..9476f22f5c2dd06c3c716f88eab41d37338ea9fb 100644
--- a/src/Storm/Model/PersistenceStrategy/Volatile.php
+++ b/src/Storm/Model/PersistenceStrategy/Volatile.php
@@ -189,16 +189,16 @@ class Storm_Model_PersistenceStrategy_Volatile
 
 
   public function containsAttribute(array $model, string $key, $value) : bool {
-    $clause = ($value instanceof Storm_Model_PersistenceStrategy_Clause)
+    $clause = ($value instanceof Storm_Query_Clause)
       ? $value
       : null;
 
     if (!$clause && preg_match('/left\((.+),(.+)\)/', $key, $matches))
-      $clause = Storm_Model_PersistenceStrategy_Clause::like(trim($matches[1]),
+      $clause = Storm_Query_Clause::like(trim($matches[1]),
                                                              (substr($value, 0, (int)$matches[2]) . '%'));
 
     if (!$clause)
-      $clause = Storm_Model_PersistenceStrategy_Clause::newWith($key, $value);
+      $clause = Storm_Query_Clause::newWith($key, $value);
 
     return $clause->containAttibuteInVolatile($model);
   }
@@ -269,7 +269,7 @@ class Storm_Model_PersistenceStrategy_Volatile
   }
 
 
-  protected function _allMatchingInstancesDo(array $clauses, callable $callback) : int {
+  protected function _allMatchingInstancesDo($clauses, callable $callback) : int {
     $matching_instances = array_filter($this->getInstancesArray(),
                                        function ($model) use ($clauses) {
                                          return $this->_modelMatchesClauses($model, $clauses);
@@ -279,7 +279,10 @@ class Storm_Model_PersistenceStrategy_Volatile
   }
 
 
-  protected function _modelMatchesClauses(array $model, array $clauses) : bool {
+  protected function _modelMatchesClauses(array $model, $clauses) : bool {
+    if ($clauses instanceof Storm_Query)
+      return $clauses->containsAllAttributes($model);
+
     return $this->containsAllAttributes($model, $clauses);
   }
 
diff --git a/src/Storm/Model/PersistenceStrategy/Volatile/Filtered.php b/src/Storm/Model/PersistenceStrategy/Volatile/Filtered.php
index 2f0da257a0ebf2e9a116cff4da9d4d43c5a095fc..84104d4208700ceeb7c37df360cd336df2ac25b3 100644
--- a/src/Storm/Model/PersistenceStrategy/Volatile/Filtered.php
+++ b/src/Storm/Model/PersistenceStrategy/Volatile/Filtered.php
@@ -75,9 +75,9 @@ class Storm_Model_PersistenceStrategy_Volatile_Filtered {
   }
 
 
-  public function getClauses() : array {
+  public function getClauses() {
     return $this->_query
-      ? $this->_query->getClauses()
+      ? $this->_query
       : $this->_select;
   }
 
diff --git a/src/Storm/Query.php b/src/Storm/Query.php
index 207e2110c0b32a7f8d15f77c1c5773cb228f9f26..a3fcbedeac395e88d45cce7b06c4626797fc7f97 100644
--- a/src/Storm/Query.php
+++ b/src/Storm/Query.php
@@ -25,308 +25,268 @@
 */
 
 
-class Storm_Query {
+class Storm_Query implements Storm_Query_CriteriaInterface {
 
-  protected array $_clauses;
   protected array $_orders;
-  protected array $_terms;
   protected Storm_Model_Loader $_loader;
-  protected ?Storm_Model_PersistenceStrategy_Clause $_clause_limit;
-  protected ?Storm_Model_PersistenceStrategy_Clause $_clause_limit_page;
-  protected ?Storm_Model_PersistenceStrategy_Clause $_clause_group_by;
+  protected Storm_Query_CriteriaInterface $_criteria;
+  protected ?Storm_Query_Clause $_clause_limit;
+  protected ?Storm_Query_Clause $_clause_limit_page;
+  protected ?Storm_Query_Clause $_clause_group_by;
 
-  public function __construct() {
-    $this->_clauses = [];
+
+  protected function __construct(Storm_Model_Loader $loader) {
+    $this->_loader = $loader;
     $this->_orders = [];
-    $this->_terms = [];
     $this->_clause_limit = null;
     $this->_clause_limit_page = null;
     $this->_clause_group_by = null;
+    $this->_criteria = new Storm_Query_Criteria;
   }
 
 
-  public function __call($method, $args) {
-    return $this->_callQuery($method, $args);
+  public static function from(Storm_Model_Loader $loader) : self {
+    return new static($loader);
   }
 
 
-  public static function __callStatic($method, $args) {
-    return (new static)->_callQuery($method, $args);
+  public function limit($limit) : self {
+    $this->_clause_limit = Storm_Query_Clause::limit($limit);
+    return $this;
   }
 
 
-  protected function _callQuery($method, $args) {
-    if (!method_exists($this , '_' . $method))
-      throw new Exception('Call undefined method ' . $method);
-
-    return $this->{'_' . $method}(...$args);
+  public function limit_page(array $range) {
+    $this->_clause_limit_page = Storm_Query_Clause::limit_page($range);
+    return $this;
   }
 
 
-  public function getSelectAndAssemble() {
-    $select = $this->_loader->getTable()->select();
-
-    foreach ($this->getClauses() as $clause)
-      $clause->assemble($select);
-
-    foreach ($this->getOrders() as $order)
-      $order->assemble($select);
-
-    if ($this->_clause_limit)
-      $this->_clause_limit->assemble($select);
-
-    if ($this->_clause_limit_page)
-      $this->_clause_limit_page->assemble($select);
-
-    if ($this->_clause_group_by)
-      $this->_clause_group_by->assemble($select);
-
-    return $select;
+  public function group(string $value) : self {
+    $this->_clause_group_by = Storm_Query_Clause::group($value);
+    return $this;
   }
 
 
-  public function orderVolatile(array $models) : array {
-    return (new Storm_Query_Order(array_reverse($this->getOrders())))
-      ->compareVolatile($models);
+  public function order(string $key) : self {
+    $this->_orders [] = Storm_Query_Clause::order($key)
+      ->setOrder(false);
+    return $this;
   }
 
 
-  public function getClauses() : array {
-    return $this->_clauses;
+  public function order_desc(string $key) : self {
+    $this->_orders [] = Storm_Query_Clause::order($key)
+      ->setOrder(true);
+    return $this;
   }
 
 
-  public function getOrders() : array {
-    return $this->_orders;
-  }
-
+  public function order_match(Storm_Query_MatchRating $match) : self {
+    $clause_match = Storm_Query_Clause::match($match);
+    $this->_orders [] =
+      Storm_Query_Clause::order($match->getKey(),
+                                                    $clause_match)
+      ->setOrder(false);
 
-  public function getTerms() : array {
-    return $this->_terms;
+    return $this;
   }
 
 
-  public function getLimitValue() {
-    return $this->_clause_limit ?
-      $this->_clause_limit->getValueOrArray()
-      : '';
-  }
-
+  public function order_match_desc(Storm_Query_MatchRating $match) : self {
+    $clause_match = Storm_Query_Clause::match($match);
+    $this->_orders [] =
+      Storm_Query_Clause::order($match->getKey(),
+                                                    $clause_match)
+      ->setOrder(true);
 
-  public function getLimitPageValue() : ?array {
-    return $this->_clause_limit_page
-      ? $this->_clause_limit_page->getValueOrArray()
-      : null;
+    return $this;
   }
 
 
-  public function getGroupByValue() : string {
-    return $this->_clause_group_by
-      ? $this->_clause_group_by->getValueOrArray()
-      : '';
+  public function fetchAll() : array {
+    return $this->_loader->findAllBy($this);
   }
 
 
-  protected function _limit($limit) : self {
-    $this->_clause_limit = Storm_Model_PersistenceStrategy_Clause::limit($limit);
-    return $this;
+  public function count() : int {
+    return $this->_loader->countBy($this);
   }
 
 
-  protected function _limit_page(array $range) {
-    $this->_clause_limit_page = Storm_Model_PersistenceStrategy_Clause::limit_page($range);
-    return $this;
+  public function fetchFirst() : ?Storm_Model_Abstract {
+    return $this->_loader->findFirstBy($this);
   }
 
 
-  protected function _group(string $value) : self {
-    $this->_clause_group_by = Storm_Model_PersistenceStrategy_Clause::group($value);
+  /** interface Storm_Query_CriteriaInterface */
+
+  public function beOr() : self {
+    $this->_criteria->beOr();
     return $this;
   }
 
 
-  protected function _eq(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::equal($key, $value);
+  public function or(Storm_Query_CriteriaInterface $criteria) : self {
+    $this->_criteria->or($criteria);
     return $this;
   }
 
 
-  public function _not_eq(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::equal($key, $value)->setNegated(true);
+  public function and(Storm_Query_CriteriaInterface $criteria) : self {
+    $this->_criteria->and($criteria);
     return $this;
   }
 
 
-  protected function _like(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::like($key, $value);
+  public function eq(string $key, string $value) : self {
+    $this->_criteria->eq($key, $value);
     return $this;
   }
 
 
-  protected function _not_like(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::like($key, $value)->setNegated(true);
+  public function not_eq(string $key, string $value) : self {
+    $this->_criteria->not_eq($key, $value);
     return $this;
   }
 
 
-  protected function _gt(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::greater($key, $value);
+  public function like(string $key, string $value) : self {
+    $this->_criteria->like($key, $value);
     return $this;
   }
 
 
-  protected function _gt_eq(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::greaterEqual($key, $value);
+  public function not_like(string $key, string $value) : self {
+    $this->_criteria->not_like($key, $value);
     return $this;
   }
 
 
-  protected function _lt(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::lesser($key, $value);
+  public function gt(string $key, string $value) : self {
+    $this->_criteria->gt($key, $value);
     return $this;
   }
 
 
-  protected function _lt_eq(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::lesserEqual($key, $value);
+  public function gt_eq(string $key, string $value) : self {
+    $this->_criteria->gt_eq($key, $value);
     return $this;
   }
 
 
-  protected function _is(string $key) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::is($key);
+  public function lt(string $key, string $value) : self {
+    $this->_criteria->lt($key, $value);
     return $this;
   }
 
 
-  protected function _not_is(string $key) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::is($key)->setNegated(true);
+  public function lt_eq(string $key, string $value) : self {
+    $this->_criteria->lt_eq($key, $value);
     return $this;
   }
 
 
-  protected function _in(string $key, array $array) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::in($key, $array);
+  public function is(string $key) : self {
+    $this->_criteria->is($key);
     return $this;
   }
 
 
-  protected function _not_in(string $key, array $array) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::in($key, $array)->setNegated(true);
+  public function not_is(string $key) : self {
+    $this->_criteria->not_is($key);
     return $this;
   }
 
 
-  protected function _start(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::start($key, $value);
+  public function in(string $key, array $array) : self {
+    $this->_criteria->in($key, $array);
     return $this;
   }
 
 
-  protected function _end(string $key, string $value) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::end($key, $value);
+  public function not_in(string $key, array $array) : self {
+    $this->_criteria->not_in($key, $array);
     return $this;
   }
 
 
-  protected function _or(Storm_Query ...$querys) : self {
-    $clauses = [];
-    foreach ($querys as $q)
-      $clauses = [...$clauses, ...$q->getClauses()];
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::or($clauses);
+  public function start(string $key, string $value) : self {
+    $this->_criteria->start($key, $value);
     return $this;
   }
 
 
-  protected function _and(Storm_Query ...$querys) : self {
-    $clauses = [];
-    foreach ($querys as $q)
-      $clauses = [...$clauses, ...$q->getClauses()];
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::and($clauses);
+  public function end(string $key, string $value) : self {
+    $this->_criteria->end($key, $value);
     return $this;
   }
 
 
-  protected function _match(string $key,
-                            Storm_Query $query,
-                            ?bool $strict = false,
-                            ?bool $boolean_mode = true) : self {
-    $this->_clauses [] = Storm_Model_PersistenceStrategy_Clause::match($key,
-                                                                       $query->getTerms(),
-                                                                       $strict,
-                                                                       $boolean_mode);
+  public function match(Storm_Query_MatchRating $match) : self {
+    $this->_criteria->match($match);
     return $this;
   }
 
 
-  protected function _terms($value_or_array, ?bool $exact = false) : self {
-    $this->_terms [] = (new Storm_Model_PersistenceStrategy_MatchTerms($value_or_array, $exact));
-    return $this;
-  }
+  /** DB management */
 
+  /**
+   * @param $select Zend_Db_Table_Select|Storm_Test_ObjectWrapper
+   */
+  public function assemble(object $select) : self {
+    $sql = new Storm_Query_Sql;
+    $this->_criteria->assemble($sql);
+    if ($where = $sql->implode($this->_criteria->separator()))
+      $select->where($where);
 
-  protected function _order(string $key) : self {
-    $this->_orders [] = Storm_Model_PersistenceStrategy_Clause::order($key)->setOrder(false);
-    return $this;
-  }
+    foreach ($this->_orders as $order)
+      $order->assemble($select);
 
+    if ($this->_clause_limit)
+      $this->_clause_limit->assemble($select);
 
-  protected function _order_desc(string $key) : self {
-    $this->_orders [] = Storm_Model_PersistenceStrategy_Clause::order($key)->setOrder(true);
-    return $this;
-  }
+    if ($this->_clause_limit_page)
+      $this->_clause_limit_page->assemble($select);
 
+    if ($this->_clause_group_by)
+      $this->_clause_group_by->assemble($select);
 
-  protected function _order_match(string $key,
-                                  Storm_Query $query,
-                                  ?bool $strict = false,
-                                  ?bool $boolean_mode = true) : self {
-    $match = Storm_Model_PersistenceStrategy_Clause::match($key,
-                                                           $query->getTerms(),
-                                                           $strict,
-                                                           $boolean_mode);
-    $this->_orders [] = Storm_Model_PersistenceStrategy_Clause::order($key, $match)->setOrder(false);
     return $this;
   }
 
 
-  protected function _order_match_desc(string $key,
-                                       Storm_Query $query,
-                                       ?bool $strict = false,
-                                       ?bool $boolean_mode = true) : self {
-    $match = Storm_Model_PersistenceStrategy_Clause::match($key,
-                                                           $query->getTerms(),
-                                                           $strict,
-                                                           $boolean_mode);
-    $this->_orders [] = Storm_Model_PersistenceStrategy_Clause::order($key, $match)->setOrder(true);
-    return $this;
+  /** Volatile management */
+
+  public function orderVolatile(array $models) : array {
+    return (new Storm_Query_Order(array_reverse($this->_orders)))
+      ->compareVolatile($models);
   }
 
 
-  protected function _from(Storm_Model_Loader $loader) : self {
-    $this->_loader = $loader;
-    return $this;
+  public function containsAllAttributes(array $model) : bool {
+    return $this->_criteria->containsAllAttributes($model);
   }
 
 
-  protected function _fetchAll() : array {
-    return $this->_loader
-      ? $this->_loader->findAllBy($this)
-      : [];
+  public function getLimitValue() {
+    return $this->_clause_limit ?
+      $this->_clause_limit->getValueOrArray()
+      : '';
   }
 
 
-  protected function _count() : array {
-    return $this->_loader
-      ? $this->_loader->countBy($this)
-      : [];
+  public function getLimitPageValue() : ?array {
+    return $this->_clause_limit_page
+      ? $this->_clause_limit_page->getValueOrArray()
+      : null;
   }
 
 
-  protected function _fetchFirst() : ?Storm_Model_Abstract {
-    return $this->_loader
-      ? $this->_loader->findFirstBy($this)
-      : null;
+  public function getGroupByValue() : string {
+    return $this->_clause_group_by
+      ? $this->_clause_group_by->getValueOrArray()
+      : '';
   }
 }
 
@@ -336,7 +296,7 @@ class Storm_Query {
 class Storm_Query_Order {
 
   protected ?Storm_Query_Order $_next_query;
-  protected ?Storm_Model_PersistenceStrategy_Clause $_clause;
+  protected ?Storm_Query_Clause $_clause;
   protected int $_position;
 
   public function __construct(array $orders, ?int $position = 0) {
@@ -359,7 +319,7 @@ class Storm_Query_Order {
   }
 
 
-  public function getClause() : ?Storm_Model_PersistenceStrategy_Clause {
+  public function getClause() : ?Storm_Query_Clause {
     return $this->_clause;
   }
 
diff --git a/src/Storm/Model/PersistenceStrategy/Clause.php b/src/Storm/Query/Clause.php
similarity index 54%
rename from src/Storm/Model/PersistenceStrategy/Clause.php
rename to src/Storm/Query/Clause.php
index b41320c55089c9e1989c3daf3f1e55531f562dce..13ea5006e6b359821ffc2fe2be04933e3a3e90d1 100644
--- a/src/Storm/Model/PersistenceStrategy/Clause.php
+++ b/src/Storm/Query/Clause.php
@@ -25,7 +25,7 @@ THE SOFTWARE.
 */
 
 
-class Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_Clause {
 
   protected string $_key;
   protected string $_operator;
@@ -33,8 +33,6 @@ class Storm_Model_PersistenceStrategy_Clause {
   protected $_value_or_array;
 
   const
-    CLAUSE_OR = 'or',
-    CLAUSE_AND = 'and',
     CLAUSE_WHERE = 'where',
     CLAUSE_LIKE = 'like',
     CLAUSE_EQUAL = '=',
@@ -61,36 +59,34 @@ class Storm_Model_PersistenceStrategy_Clause {
 
 
   public static function newFor(string $key, string $operator, $value_or_array) : self {
-    if (static::CLAUSE_OR === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseOr($key, $operator, $value_or_array));
-    if (static::CLAUSE_AND === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseAnd($key, $operator, $value_or_array));
     if (static::CLAUSE_LIKE === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseLike($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseLike($key, $operator, $value_or_array));
     if (static::CLAUSE_EQUAL === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseEqual($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseEqual($key, $operator, $value_or_array));
     if (static::CLAUSE_IN === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseIn($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseIn($key, $operator, $value_or_array));
     if (static::CLAUSE_IS === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseIs($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseIs($key, $operator, $value_or_array));
     if (static::CLAUSE_GREATER === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseGreater($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseGreater($key, $operator, $value_or_array));
     if (static::CLAUSE_GREATER_EQUAL === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseGreaterEqual($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseGreaterEqual($key, $operator, $value_or_array));
     if (static::CLAUSE_LESSER === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseLesser($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseLesser($key, $operator, $value_or_array));
     if (static::CLAUSE_LESSER_EQUAL === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseLesserEqual($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseLesserEqual($key, $operator, $value_or_array));
     if (static::CLAUSE_MATCH === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseMatch($key, $operator, $value_or_array));
+      return (new Storm_Query_Clause_Match($key, $operator, $value_or_array));
     if (static::CLAUSE_LIMIT === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseLimit($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseLimit($key, $operator, $value_or_array));
     if (static::CLAUSE_LIMIT_PAGE === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseLimitPage($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseLimitPage($key, $operator, $value_or_array));
     if (static::CLAUSE_ORDER_BY === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseOrderBy($key, $operator, $value_or_array));
+      return (new Storm_Query_Clause_OrderBy($key, $operator, $value_or_array));
     if (static::CLAUSE_GROUP_BY === $operator)
-      return (new Storm_Model_PersistenceStrategy_ClauseGroupBy($key, $operator, $value_or_array));
+      return (new Storm_Query_ClauseGroupBy($key, $operator, $value_or_array));
+    if (static::CLAUSE_WHERE === $operator)
+      return (new Storm_Query_ClauseWhere($key, $operator, $value_or_array));
 
     return (new static($key, $operator, $value_or_array));
   }
@@ -169,23 +165,8 @@ class Storm_Model_PersistenceStrategy_Clause {
   }
 
 
-  public static function or(array $array) : self {
-    return static::newFor(static::CLAUSE_OR, static::CLAUSE_OR, $array);
-  }
-
-
-  public static function and(array $array) : self {
-    return static::newFor(static::CLAUSE_AND, static::CLAUSE_AND, $array);
-  }
-
-
-  public static function match(string $key,
-                               array $array,
-                               ?bool $strict = false,
-                               ?bool $boolean_mode = true) : self {
-    return static::newFor($key,
-                          static::CLAUSE_MATCH,
-                          (new Storm_Model_PersistenceStrategy_Match($array, $strict, $boolean_mode)));
+  public static function match(Storm_Query_MatchRating $match) : self {
+    return static::newFor($match->getKey(), static::CLAUSE_MATCH, $match);
   }
 
 
@@ -204,7 +185,7 @@ class Storm_Model_PersistenceStrategy_Clause {
   }
 
 
-  public static function order(string $key, ?Storm_Model_PersistenceStrategy_Clause $clause = null) : self {
+  public static function order(string $key, ?Storm_Query_Clause $clause = null) : self {
     return static::newFor($key, static::CLAUSE_ORDER_BY, $clause);
   }
 
@@ -221,18 +202,18 @@ class Storm_Model_PersistenceStrategy_Clause {
 
 
   public function assemble($select) : self {
-    $select->where($this->getFormatDb($select->getTable()));
+    $select->where($this->getFormatDb());
     return $this;
   }
 
 
-  public function getFormatDb($table) : string {
+  public function getFormatDb() : string {
     if (static::CLAUSE_WHERE === $this->_operator)
       return '(' . $this->getValueOrArray() . ')';
 
-    return $table->getAdapter()
-                 ->quoteInto($this->_clauseFormatDb(),
-                             $this->getValueOrArray(), null, null);
+    return Zend_Db_Table_Abstract::getDefaultAdapter()
+      ->quoteInto($this->_clauseFormatDb(),
+                  $this->getValueOrArray(), null, null);
   }
 
 
@@ -262,54 +243,8 @@ class Storm_Model_PersistenceStrategy_Clause {
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseOr
-  extends Storm_Model_PersistenceStrategy_Clause {
-
-  public function getValueOrArray() {
-    return $this->_value_or_array ?? [];
-  }
-
-
-  public function getFormatDb($table) : string {
-    $request = '';
-    $operator = ' ' . $this->_operator . ' ';
-    foreach ($this->getValueOrArray() as $clause)
-      $request = implode($operator, array_filter([$request,
-                                                  $clause->getFormatDb($table)]));
-
-    return '(' . $request . ')';
-  }
-
-
-  public function containAttibuteInVolatile(array $model) : bool {
-    foreach ($this->getValueOrArray() as $clause)
-      if ($clause->containAttibuteInVolatile($model))
-        return true;
-
-    return false;
-  }
-}
-
-
-
-
-class Storm_Model_PersistenceStrategy_ClauseAnd
-  extends Storm_Model_PersistenceStrategy_ClauseOr {
-
-  public function containAttibuteInVolatile(array $model) : bool {
-    foreach ($this->getValueOrArray() as $clause)
-      if (!$clause->containAttibuteInVolatile($model))
-        return false;
-
-    return true;
-  }
-}
-
-
-
-
-class Storm_Model_PersistenceStrategy_ClauseLike
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseLike
+  extends Storm_Query_Clause {
 
   protected function _clauseFormatDb() : string {
     return $this->_key . ' ' . $this->_getOperator() . ' ?';
@@ -331,8 +266,8 @@ class Storm_Model_PersistenceStrategy_ClauseLike
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseEqual
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseEqual
+  extends Storm_Query_Clause {
 
   protected function _getOperator() : string {
     return ($this->_negated ? '!' : '') . $this->_operator;
@@ -342,8 +277,8 @@ class Storm_Model_PersistenceStrategy_ClauseEqual
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseIn
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseIn
+  extends Storm_Query_Clause {
 
   public function getValueOrArray() {
     return $this->_value_or_array ?? [];
@@ -372,15 +307,15 @@ class Storm_Model_PersistenceStrategy_ClauseIn
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseIs
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseIs
+  extends Storm_Query_Clause {
 
   public function getValueOrArray() {
     return null;
   }
 
 
-  public function getFormatDb($table) : string {
+  public function getFormatDb() : string {
     return $this->_key . ' ' . $this->_getOperator() . ' null';
   }
 
@@ -393,8 +328,8 @@ class Storm_Model_PersistenceStrategy_ClauseIs
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseGreater
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseGreater
+  extends Storm_Query_Clause {
 
   public function containAttibuteInVolatile(array $model) : bool {
     if (!$this->_existKeyInModel($model))
@@ -407,8 +342,8 @@ class Storm_Model_PersistenceStrategy_ClauseGreater
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseGreaterEqual
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseGreaterEqual
+  extends Storm_Query_Clause {
 
   public function containAttibuteInVolatile(array $model) : bool {
     if (!$this->_existKeyInModel($model))
@@ -421,8 +356,8 @@ class Storm_Model_PersistenceStrategy_ClauseGreaterEqual
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseLesser
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseLesser
+  extends Storm_Query_Clause {
 
   public function containAttibuteInVolatile(array $model) : bool {
     if (!$this->_existKeyInModel($model))
@@ -435,8 +370,8 @@ class Storm_Model_PersistenceStrategy_ClauseLesser
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseLesserEqual
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseLesserEqual
+  extends Storm_Query_Clause {
 
   public function containAttibuteInVolatile(array $model) : bool {
     if (!$this->_existKeyInModel($model))
@@ -449,63 +384,8 @@ class Storm_Model_PersistenceStrategy_ClauseLesserEqual
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseMatch
-  extends Storm_Model_PersistenceStrategy_Clause {
-
-  public function getValueOrArray() {
-    return $this->_value_or_array ?? null;
-  }
-
-
-  public function getFormatDb($table) : string {
-    return ($value = $this->getValueOrArray())
-      ? ($this->_getOperator() . ' ' . $value->getFormatDb($table))
-      : '';
-  }
-
-
-  public function compare(array $a, array $b) : int {
-    if (!($value = $this->getValueOrArray()))
-      return 0;
-
-    return ($value->getCompareValue($this->_getContents($a))
-            - $value->getCompareValue($this->_getContents($b)));
-  }
-
-
-  public function containAttibuteInVolatile(array $model) : bool {
-    if (!($value = $this->getValueOrArray()))
-      return false;
-
-    return $value->containVolatile($this->_getContents($model));
-  }
-
-
-  protected function _getOperator() : string {
-    return $this->_operator . '(' . $this->_key . ')';
-  }
-
-
-  protected function _clauseFormatDb() : string {
-    return $this->_getOperator();
-  }
-
-
-  protected function _getContents(array $model) : string {
-    $contents = '';
-    foreach (explode(',', $this->_key) as $key)
-      if (array_key_exists($key, $model))
-        $contents = implode(' ', array_filter([$contents, $model[$key]]));
-
-    return $contents;
-  }
-}
-
-
-
-
-class Storm_Model_PersistenceStrategy_ClauseLimit
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseLimit
+  extends Storm_Query_Clause {
 
   public function assemble($select) : self {
     $select->limit($this->getValueOrArray());
@@ -513,7 +393,7 @@ class Storm_Model_PersistenceStrategy_ClauseLimit
   }
 
 
-  public function getFormatDb($table) : string {
+  public function getFormatDb() : string {
     return '';
   }
 
@@ -526,8 +406,8 @@ class Storm_Model_PersistenceStrategy_ClauseLimit
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseLimitPage
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseLimitPage
+  extends Storm_Query_Clause {
 
   public function getValueOrArray() {
     return $this->_value_or_array ?? [];
@@ -543,7 +423,7 @@ class Storm_Model_PersistenceStrategy_ClauseLimitPage
   }
 
 
-  public function getFormatDb($table) : string {
+  public function getFormatDb() : string {
     return '';
   }
 
@@ -556,8 +436,8 @@ class Storm_Model_PersistenceStrategy_ClauseLimitPage
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseGroupBy
-  extends Storm_Model_PersistenceStrategy_Clause {
+class Storm_Query_ClauseGroupBy
+  extends Storm_Query_Clause {
 
   public function assemble($select) : self {
     $select->group($this->getValueOrArray());
@@ -565,7 +445,7 @@ class Storm_Model_PersistenceStrategy_ClauseGroupBy
   }
 
 
-  public function getFormatDb($table) : string {
+  public function getFormatDb() : string {
     return '';
   }
 
@@ -578,65 +458,8 @@ class Storm_Model_PersistenceStrategy_ClauseGroupBy
 
 
 
-class Storm_Model_PersistenceStrategy_ClauseOrderBy
-  extends Storm_Model_PersistenceStrategy_Clause {
-
-  const ORDER_DESC = ' desc';
-  protected ?string $_order_mode = null;
-
-  public function getValueOrArray() {
-    return $this->_value_or_array ?? null;
-  }
-
-
-  public function assemble($select) : self {
-    $select->order((($clause = $this->getValueOrArray())
-                    ? $clause->getFormatDb($select->getTable())
-                    : $this->_key)
-                   . $this->_order_mode);
-    return $this;
-  }
-
-
-  public function getFormatDb($table) : string {
-    return '';
-  }
-
-
+class Storm_Query_ClauseWhere extends Storm_Query_Clause {
   public function containAttibuteInVolatile(array $model) : bool {
     return true;
   }
-
-
-  public function setOrder(bool $mode) : self {
-    $this->_order_mode = $mode ? static::ORDER_DESC : '';
-    return $this;
-  }
-
-
-  public function compare(Storm_Query_Order $query_order, array $a, array $b) : int {
-    $compare = $this->_compareValues($a, $b)
-      * $this->_descendant()
-      * $query_order->getPosition();
-
-    return ($next_order = $query_order->getNextQuery())
-      ? ($compare + $next_order->getClause()->compare($next_order, $a, $b))
-      : $compare;
-  }
-
-
-  protected function _compareValues(array $a, array $b) : int {
-    if ($match_clause = $this->getValueOrArray())
-      return $match_clause->compare($a, $b);
-
-    $first = $this->_existKeyInModel($a) ? $a[$this->_key] : '';
-    $second = $this->_existKeyInModel($b) ? $b[$this->_key] : '';
-
-    return ($first <=> $second);
-  }
-
-
-  protected function _descendant() : int {
-    return static::ORDER_DESC === $this->_order_mode ? -1 : 1;
-  }
 }
diff --git a/src/Storm/Query/Clause/Match.php b/src/Storm/Query/Clause/Match.php
new file mode 100644
index 0000000000000000000000000000000000000000..0a45094e91807a42b00e72dd61f91ebe60816e5d
--- /dev/null
+++ b/src/Storm/Query/Clause/Match.php
@@ -0,0 +1,117 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+
+class Storm_Query_Clause_Match
+  extends Storm_Query_Clause {
+
+  public function getValueOrArray() {
+    return $this->_value_or_array ?? null;
+  }
+
+
+  public function getFormatDb() : string {
+    if (!$value = $this->getValueOrArray())
+      return '';
+
+    $content = '';
+    foreach ($value->getTerms() as $key => $term)
+      $content = implode(' ',
+                         array_filter([$content,
+                                       $term->getFormatDb($value->isStrict())]));
+
+    $against = sprintf('AGAINST(%s%s)',
+                       Zend_Db_Table_Abstract::getDefaultAdapter()
+                       ->quoteInto('?', $content),
+                       ($value->isBooleanMode() ? ' IN BOOLEAN MODE' : ''));
+
+    return $this->_getOperator() . ' ' . $against;
+  }
+
+
+  public function compare(array $a, array $b) : int {
+    return ($this->_compare($this->_getContents($a))
+            - $this->_compare($this->_getContents($b)));
+  }
+
+
+  protected function _compare(string $contents) : int {
+    if (!$value = $this->getValueOrArray())
+      return 0;
+
+    $compare = 0;
+
+    foreach ($value->getTerms() as $term) {
+      $new_compare = $term->getCompareValue($contents);
+      if ($value->isStrict() && 0 === $new_compare)
+        return 0;
+
+      $compare += $term->getCompareValue($contents);
+    }
+
+    return $value->isBooleanMode()
+      ? min($compare, 1)
+      : ceil(($compare / count(explode(' ', $contents))) * 10);
+  }
+
+
+  public function containAttibuteInVolatile(array $model) : bool {
+    if (!($value = $this->getValueOrArray())
+        || !($contents = $this->_getContents($model)))
+      return false;
+
+    foreach ($value->getTerms() as $term) {
+      if ($value->isStrict() && !$term->containVolatile($contents))
+        return false;
+
+      if (!$value->isStrict() && $term->containVolatile($contents))
+        return true;
+    }
+
+    return $value->isStrict();
+  }
+
+
+  protected function _getOperator() : string {
+    return $this->_operator . '(' . $this->_key . ')';
+  }
+
+
+  protected function _clauseFormatDb() : string {
+    return $this->_getOperator();
+  }
+
+
+  protected function _getContents(array $model) : string {
+    $contents = '';
+    foreach (explode(',', $this->_key) as $key)
+      if (array_key_exists($key, $model))
+        $contents = implode(' ', array_filter([$contents, $model[$key]]));
+
+    return $contents;
+  }
+}
diff --git a/src/Storm/Query/Clause/MatchTerms.php b/src/Storm/Query/Clause/MatchTerms.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e07c6133f14a4198c9fbca9a7149bb1c38443fc
--- /dev/null
+++ b/src/Storm/Query/Clause/MatchTerms.php
@@ -0,0 +1,77 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+gof this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+class Storm_Query_Clause_MatchTerms {
+
+  protected array $_values;
+  protected bool $_exact;
+
+  public function __construct(array $values, bool $exact) {
+    $this->_values = $values;
+    $this->_exact = $exact;
+  }
+
+
+  public function getFormatDb(bool $strict) : string {
+    $values = implode(' ', $this->_values);
+    if ($this->_exact)
+      return $strict ? '+"' . $values . '"' : '"' . $values . '"';
+
+    return $strict ? '+(' . $values . ')' : $values;
+  }
+
+
+  public function containVolatile(string $contents) : bool {
+    if ($this->_exact)
+      return $this->_containValue($contents, implode(' ', $this->_values));
+
+    foreach ($this->_values as $value)
+      if ($this->_containValue($contents, $value))
+        return true;
+
+    return false;
+  }
+
+
+  public function getCompareValue(string $contents) : int {
+    if ($this->_exact)
+      return preg_match_all('/\b' . implode(' ', $this->_values) . '\b/', $contents);
+
+    $compare = 0;
+    foreach ($this->_values as $value)
+      $compare += ($new_compare = preg_match_all('/\b' . $value . '\b/', $contents))
+        ? $new_compare
+        : 0;
+
+    return $compare;
+  }
+
+
+  protected function _containValue(string $contents, string $value) : bool {
+    return preg_match('/\b' . $value . '\b/', $contents);
+  }
+}
diff --git a/src/Storm/Query/Clause/OrderBy.php b/src/Storm/Query/Clause/OrderBy.php
new file mode 100644
index 0000000000000000000000000000000000000000..72b742a64d38c9fa2dfa5e15ab7356b583ad02e7
--- /dev/null
+++ b/src/Storm/Query/Clause/OrderBy.php
@@ -0,0 +1,89 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+class Storm_Query_Clause_OrderBy
+  extends Storm_Query_Clause {
+
+  const ORDER_DESC = ' desc';
+  protected ?string $_order_mode = null;
+
+  public function getValueOrArray() {
+    return $this->_value_or_array ?? null;
+  }
+
+
+  public function assemble($select) : self {
+    $select->order((($clause = $this->getValueOrArray())
+                    ? $clause->getFormatDb($select->getTable())
+                    : $this->_key)
+                   . $this->_order_mode);
+    return $this;
+  }
+
+
+  public function getFormatDb() : string {
+    return '';
+  }
+
+
+  public function containAttibuteInVolatile(array $model) : bool {
+    return true;
+  }
+
+
+  public function setOrder(bool $mode) : self {
+    $this->_order_mode = $mode ? static::ORDER_DESC : '';
+    return $this;
+  }
+
+
+  public function compare(Storm_Query_Order $query_order, array $a, array $b) : int {
+    $compare = $this->_compareValues($a, $b)
+      * $this->_descendant()
+      * $query_order->getPosition();
+
+    return ($next_order = $query_order->getNextQuery())
+      ? ($compare + $next_order->getClause()->compare($next_order, $a, $b))
+      : $compare;
+  }
+
+
+  protected function _compareValues(array $a, array $b) : int {
+    if ($match_clause = $this->getValueOrArray())
+      return $match_clause->compare($a, $b);
+
+    $first = $this->_existKeyInModel($a) ? $a[$this->_key] : '';
+    $second = $this->_existKeyInModel($b) ? $b[$this->_key] : '';
+
+    return ($first <=> $second);
+  }
+
+
+  protected function _descendant() : int {
+    return static::ORDER_DESC === $this->_order_mode ? -1 : 1;
+  }
+}
diff --git a/src/Storm/Query/Criteria.php b/src/Storm/Query/Criteria.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb713fa37c93155dfaab6704d27eb2b45d55385a
--- /dev/null
+++ b/src/Storm/Query/Criteria.php
@@ -0,0 +1,205 @@
+<?php
+/*
+  STORM is under the MIT License (MIT)
+
+  Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+
+*/
+
+
+class Storm_Query_Criteria implements Storm_Query_CriteriaInterface {
+
+  const
+    SEPARATOR_AND = 'and',
+    SEPARATOR_OR = 'or';
+
+  protected array $_clauses;
+  protected string $_separator;
+
+
+  public function __construct() {
+    $this->_clauses = [];
+    $this->_separator = static::SEPARATOR_AND;
+  }
+
+
+  public function beOr() : self {
+    $this->_separator = static::SEPARATOR_OR;
+    return $this;
+  }
+
+
+  public function separator() : string {
+    return ' ' . $this->_separator . ' ';
+  }
+
+
+  public function assemble(Storm_Query_Sql $sql) : self {
+    foreach ($this->_clauses as $clause)
+      $sql->write($this->_assembleOne($clause));
+
+    return $this;
+  }
+
+
+  /**
+   * @param $clause Storm_Query_CriteriaInterface|Storm_Query_Clause
+   */
+  protected function _assembleOne(object $clause) : string {
+    if ($clause instanceof Storm_Query_CriteriaInterface) {
+      $sql = new Storm_Query_Sql;
+      $clause->assemble($sql);
+
+      return '(' . $sql->implode($clause->separator()) . ')';
+    }
+
+    return $clause->getFormatDb();
+  }
+
+
+  public function containsAllAttributes(array $model) : bool {
+    foreach ($this->_clauses as $clause)
+      if ($this->_isAnd() !== $this->_containsOne($clause, $model))
+        return !$this->_isAnd();
+
+    return $this->_isAnd();
+  }
+
+
+  protected function _containsOne($clause, array $model) : bool {
+    if ($clause instanceof Storm_Query_CriteriaInterface)
+      return $clause->containsAllAttributes($model);
+
+    return $clause->containAttibuteInVolatile($model);
+  }
+
+
+  protected function _isAnd() : bool {
+    return static::SEPARATOR_AND === $this->_separator;
+  }
+
+
+  public function eq(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::equal($key, $value);
+    return $this;
+  }
+
+
+  public function not_eq(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::equal($key, $value)
+      ->setNegated(true);
+    return $this;
+  }
+
+
+  public function like(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::like($key, $value);
+    return $this;
+  }
+
+
+  public function not_like(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::like($key, $value)
+      ->setNegated(true);
+    return $this;
+  }
+
+
+  public function gt(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::greater($key, $value);
+    return $this;
+  }
+
+
+  public function gt_eq(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::greaterEqual($key,
+                                                                              $value);
+    return $this;
+  }
+
+
+  public function lt(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::lesser($key, $value);
+    return $this;
+  }
+
+
+  public function lt_eq(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::lesserEqual($key, $value);
+    return $this;
+  }
+
+
+  public function is(string $key) : self {
+    $this->_clauses [] = Storm_Query_Clause::is($key);
+    return $this;
+  }
+
+
+  public function not_is(string $key) : self {
+    $this->_clauses [] = Storm_Query_Clause::is($key)
+      ->setNegated(true);
+    return $this;
+  }
+
+
+  public function in(string $key, array $array) : self {
+    $this->_clauses [] = Storm_Query_Clause::in($key, $array);
+    return $this;
+  }
+
+
+  public function not_in(string $key, array $array) : self {
+    $this->_clauses [] = Storm_Query_Clause::in($key, $array)
+      ->setNegated(true);
+    return $this;
+  }
+
+
+  public function start(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::start($key, $value);
+    return $this;
+  }
+
+
+  public function end(string $key, string $value) : self {
+    $this->_clauses [] = Storm_Query_Clause::end($key, $value);
+    return $this;
+  }
+
+
+  public function or(Storm_Query_CriteriaInterface $criteria) : self {
+    $this->_clauses [] = $criteria->beOr();
+    return $this;
+  }
+
+
+  public function and(Storm_Query_CriteriaInterface $criteria) : self {
+    $this->_clauses [] = $criteria;
+    return $this;
+  }
+
+
+  public function match(Storm_Query_MatchRating $match) : self {
+    $this->_clauses [] = Storm_Query_Clause::match($match);
+    return $this;
+  }
+}
diff --git a/src/Storm/Query/CriteriaInterface.php b/src/Storm/Query/CriteriaInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..a26a7be164dcca491f9495b0ba0123b23457da56
--- /dev/null
+++ b/src/Storm/Query/CriteriaInterface.php
@@ -0,0 +1,82 @@
+<?php
+/*
+  STORM is under the MIT License (MIT)
+
+  Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+
+*/
+
+
+interface Storm_Query_CriteriaInterface {
+
+  public function beOr() : self;
+
+
+  public function eq(string $key, string $value) : self;
+
+
+  public function not_eq(string $key, string $value) : self;
+
+
+  public function like(string $key, string $value) : self;
+
+
+  public function not_like(string $key, string $value) : self;
+
+
+  public function gt(string $key, string $value) : self;
+
+
+  public function gt_eq(string $key, string $value) : self;
+
+
+  public function lt(string $key, string $value) : self;
+
+
+  public function lt_eq(string $key, string $value) : self;
+
+
+  public function is(string $key) : self;
+
+
+  public function not_is(string $key) : self;
+
+
+  public function in(string $key, array $array) : self;
+
+
+  public function not_in(string $key, array $array) : self;
+
+
+  public function start(string $key, string $value) : self;
+
+
+  public function end(string $key, string $value) : self;
+
+
+  public function or(Storm_Query_CriteriaInterface $criteria) : self;
+
+
+  public function and(Storm_Query_CriteriaInterface $criteria) : self;
+
+
+  public function match(Storm_Query_MatchRating $match) : self;
+}
diff --git a/src/Storm/Query/MatchBoolean.php b/src/Storm/Query/MatchBoolean.php
new file mode 100644
index 0000000000000000000000000000000000000000..212f37aa75af15c6c6499d6fbe499be1d918ef1c
--- /dev/null
+++ b/src/Storm/Query/MatchBoolean.php
@@ -0,0 +1,39 @@
+<?php
+/*
+  STORM is under the MIT License (MIT)
+
+  Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+
+*/
+
+
+class Storm_Query_MatchBoolean extends Storm_Query_MatchRating {
+
+  public function __construct(string $key) {
+    parent::__construct($key);
+    $this->_boolean_mode = true;
+  }
+
+
+  public function exact($array_or_value) : self {
+    return $this->_addTerm($array_or_value, true);
+  }
+}
diff --git a/src/Storm/Query/MatchRating.php b/src/Storm/Query/MatchRating.php
new file mode 100644
index 0000000000000000000000000000000000000000..4dd2b1c565fe65f1d7771a8b1e9783d141fb7ac1
--- /dev/null
+++ b/src/Storm/Query/MatchRating.php
@@ -0,0 +1,82 @@
+<?php
+/*
+  STORM is under the MIT License (MIT)
+
+  Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+
+*/
+
+
+class Storm_Query_MatchRating {
+
+  protected string $_key;
+  protected array $_terms;
+  protected bool $_boolean_mode;
+  protected bool $_strict;
+
+  public function __construct(string $key) {
+    $this->_key = $key;
+    $this->_terms = [];
+    $this->_boolean_mode = false;
+    $this->_strict = false;
+  }
+
+
+  public function isBooleanMode() : bool {
+    return $this->_boolean_mode;
+  }
+
+
+  public function isStrict() : bool {
+    return $this->_strict;
+  }
+
+
+  public function getKey() : string {
+    return $this->_key;
+  }
+
+
+  public function getTerms() : array {
+    return $this->_terms;
+  }
+
+
+  public function beStrict() : self {
+    $this->_strict = true;
+    return $this;
+  }
+
+
+  public function against($array_or_value) : self {
+    return $this->_addTerm($array_or_value);
+  }
+
+
+  protected function _addTerm($array_or_value, bool $exact = false) : self {
+    if (!is_array($array_or_value))
+      $array_or_value = explode(' ', $array_or_value);
+
+    $this->_terms [] = (new Storm_Query_Clause_MatchTerms($array_or_value,
+                                                          $exact));
+    return $this;
+  }
+}
diff --git a/src/Storm/Query/Sql.php b/src/Storm/Query/Sql.php
new file mode 100644
index 0000000000000000000000000000000000000000..53b347978859740a6b0c070175a3c3e208c80a99
--- /dev/null
+++ b/src/Storm/Query/Sql.php
@@ -0,0 +1,41 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+class Storm_Query_Sql {
+
+  protected array $_sql = [];
+
+  public function write(string $sql) : self {
+    $this->_sql [] = $sql;
+    return $this;
+  }
+
+
+  public function implode(string $glue) : string {
+    return implode($glue, $this->_sql);
+  }
+}
diff --git a/src/Storm/Test/ModelTestCase.php b/src/Storm/Test/ModelTestCase.php
index 70fd693750af9719e6369b8bbbfb1e43a6b69071..274aa246d8aabb868c836dd0b1e53245512eb42f 100644
--- a/src/Storm/Test/ModelTestCase.php
+++ b/src/Storm/Test/ModelTestCase.php
@@ -27,13 +27,38 @@ THE SOFTWARE.
 abstract class Storm_Test_ModelTestCase extends PHPUnit_Framework_TestCase {
   use Storm_Test_THelpers;
 
+  protected $_old_adapter;
+
   protected function setUp() {
     Storm_Model_Abstract::unsetLoaders();
+
+    $this->_old_adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+    $adapter = new class() extends Zend_Db_Adapter_Mysqli
+    {
+      public function __construct($config=[]) { }
+
+
+      public function getConfig() {
+        return ['dbname' => 'mockdb'];
+      }
+
+
+      protected function _connect() {
+        $this->_connection = Storm_Test_ObjectWrapper::mock()
+          ->whenCalled('real_escape_string')
+          ->willDo(fn($value) => $value);
+
+        return true;
+      }
+    };
+
+    Zend_Db_Table_Abstract::setDefaultAdapter($adapter);
   }
 
 
   protected function tearDown() {
     Storm_Model_Abstract::unsetLoaders();
+    Zend_Db_Table_Abstract::setDefaultAdapter($this->_old_adapter);
   }
 
 
@@ -44,4 +69,4 @@ abstract class Storm_Test_ModelTestCase extends PHPUnit_Framework_TestCase {
   }
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/tests/Storm/Test/LoaderTest.php b/tests/Storm/Test/LoaderTest.php
index f3b68db384ee10bfa498066ee560b3b60060395c..cc6a120f11be41a617add804b17cba464ed8a4d4 100644
--- a/tests/Storm/Test/LoaderTest.php
+++ b/tests/Storm/Test/LoaderTest.php
@@ -158,10 +158,10 @@ class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase {
     return
       [
        [ ['name' => null] , ['name is null'] ],
-       [ ['name' => ['Harlock', 'Nausicaa']], ['name in (\'Harlock\',\'Nausicaa\')'] ],
+       [ ['name' => ['Harlock', 'Nausicaa']], ['name in (\'Harlock\', \'Nausicaa\')'] ],
        [ ['name not' => 'Harlock'], ['name!=\'Harlock\''] ],
        [ ['name not' => ['Harlock', 'Nausicaa']],
-        ['name not in (\'Harlock\',\'Nausicaa\')'] ],
+        ['name not in (\'Harlock\', \'Nausicaa\')'] ],
        [ ['name not' => null], ['name is not null'] ],
        [ ['login like' => '%aus%'], ['login like \'%aus%\''] ],
        [ ['login not like' => '%aus%'], ['login not like \'%aus%\''] ],
@@ -218,7 +218,7 @@ class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase {
     $this->_table->whenCalled('delete')->answers(2);
     $this->_loader->basicDeleteBy(['nom' => ['bond', 'l\'upin']]);
 
-    $this->assertEquals("nom in ('bond','l'upin')",
+    $this->assertEquals("nom in ('bond', 'l'upin')",
                         $this->_table->getFirstAttributeForLastCallOn('delete'));
   }
 
@@ -261,7 +261,7 @@ class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase {
                               ['type' => 'fictive']);
 
     $this->assertEquals([['type' => 'fictive'],
-                         "nom in ('bond','lupin') and type='real'"],
+                         "nom in ('bond', 'lupin') and type='real'"],
                         $this->_table->getAttributesForLastCallOn('update'));
   }
 
@@ -292,7 +292,7 @@ class Storm_Test_LoaderSaveWithIdTest extends Storm_Test_LoaderTestCase {
       ->answers($this->_select = $this->mock());
 
     $this->_select
-      ->whenCalled('where')->with('id=\'4\'')->answers($this->_select)
+      ->whenCalled('where')->with('id=4')->answers($this->_select)
       ->whenCalled('from')->with($this->_table, ['count(*) as numberof'])->answers($this->_select);
 
   }
@@ -358,8 +358,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseGreaterThanShouldReturnQuery() {
-    Storm_Query::gt('id', '30')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->gt('id', '30')
       ->fetchAll();
 
     $this->assertEquals(['id>\'30\''],
@@ -369,8 +369,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseGreaterThanEqualShouldReturnQuery() {
-    Storm_Query::gt_eq('id', '30')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->gt_eq('id', '30')
       ->fetchAll();
 
     $this->assertEquals(['id>=\'30\''],
@@ -380,8 +380,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseLesserThanShouldReturnQuery() {
-    Storm_Query::lt('id', '30')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->lt('id', '30')
       ->fetchAll();
 
     $this->assertEquals(['id<\'30\''],
@@ -391,8 +391,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseLesserThanEqualShouldReturnQuery() {
-    Storm_Query::lt_eq('id', '30')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->lt_eq('id', '30')
       ->fetchAll();
 
     $this->assertEquals(['id<=\'30\''],
@@ -402,8 +402,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseIsShouldReturnQuery() {
-    Storm_Query::is('name')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->is('name')
       ->fetchAll();
 
     $this->assertEquals(['name is null'],
@@ -413,8 +413,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseIsNotShouldReturnQuery() {
-    Storm_Query::not_is('name')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->not_is('name')
       ->fetchAll();
 
     $this->assertEquals(['name is not null'],
@@ -424,30 +424,30 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseInShouldReturnQuery() {
-    Storm_Query::in('name', ['Harlock', 'Nausicaa'])
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->in('name', ['Harlock', 'Nausicaa'])
       ->fetchAll();
 
-    $this->assertEquals(['name in (\'Harlock\',\'Nausicaa\')'],
+    $this->assertEquals(['name in (\'Harlock\', \'Nausicaa\')'],
                         $this->_select->getAttributesForLastCallOn('where'));
   }
 
 
   /** @test */
   public function withClauseNotInShouldReturnQuery() {
-    Storm_Query::not_in('name', ['Harlock', 'Nausicaa'])
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->not_in('name', ['Harlock', 'Nausicaa'])
       ->fetchAll();
 
-    $this->assertEquals(['name not in (\'Harlock\',\'Nausicaa\')'],
+    $this->assertEquals(['name not in (\'Harlock\', \'Nausicaa\')'],
                         $this->_select->getAttributesForLastCallOn('where'));
   }
 
 
   /** @test */
   public function withClauseEqualShouldReturnQuery() {
-    Storm_Query::eq('left(name, 5)', 'ganda')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->eq('left(name, 5)', 'ganda')
       ->fetchAll();
 
     $this->assertEquals(['left(name, 5)=\'ganda\''],
@@ -457,8 +457,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseNotEqualShouldReturnQuery() {
-    Storm_Query::not_eq('name', 'Harlock')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->not_eq('name', 'Harlock')
       ->fetchAll();
 
     $this->assertEquals(['name!=\'Harlock\''],
@@ -468,8 +468,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseLikeShouldReturnQuery() {
-    Storm_Query::like('login', '%aus%')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->like('login', '%aus%')
       ->fetchAll();
 
     $this->assertEquals(['login like \'%aus%\''],
@@ -479,8 +479,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseNotLikeShouldReturnQuery() {
-    Storm_Query::not_like('login', '%aus%')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->not_like('login', '%aus%')
       ->fetchAll();
 
     $this->assertEquals(['login not like \'%aus%\''],
@@ -490,8 +490,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseStartShouldReturnQuery() {
-    Storm_Query::start('login', 'aus')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->start('login', 'aus')
       ->fetchAll();
 
     $this->assertEquals(['login like \'aus%\''],
@@ -501,8 +501,8 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseEndShouldReturnQuery() {
-    Storm_Query::end('login', 'aus')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->end('login', 'aus')
       ->fetchAll();
 
     $this->assertEquals(['login like \'%aus\''],
@@ -512,31 +512,35 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseOrShouldReturnQuery() {
-    Storm_Query::or(Storm_Query::eq('login', 'aus'),
-                    Storm_Query::in('role', ['admin', 'invite']))
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->or((new Storm_Query_Criteria)
+           ->eq('login', 'aus')
+           ->in('role', ['admin', 'invite']))
       ->fetchAll();
 
-    $this->assertEquals(['(login=\'aus\' or role in (\'admin\',\'invite\'))'],
+    $this->assertEquals(['(login=\'aus\' or role in (\'admin\', \'invite\'))'],
                         $this->_select->getAttributesForLastCallOn('where'));
   }
 
 
   /** @test */
   public function withClauseAndShouldReturnQuery() {
-    Storm_Query::and(Storm_Query::eq('login', 'aus')->in('role', ['admin', 'invite']))
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->and((new Storm_Query_Criteria)
+            ->eq('login', 'aus')
+            ->in('role', ['admin', 'invite']))
       ->fetchAll();
 
-    $this->assertEquals(['(login=\'aus\' and role in (\'admin\',\'invite\'))'],
+    $this->assertEquals(['(login=\'aus\' and role in (\'admin\', \'invite\'))'],
                         $this->_select->getAttributesForLastCallOn('where'));
   }
 
 
   /** @test */
   public function withClauseMatchShouldReturnQueryInBooleanMode() {
-    Storm_Query::match('login, role', Storm_Query::terms(['ADMIN', 'INVITE']))
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->match((new Storm_Query_MatchBoolean('login, role'))
+              ->against(['ADMIN', 'INVITE']))
       ->fetchAll();
 
     $this->assertEquals(['MATCH(login, role) AGAINST(\'ADMIN INVITE\' IN BOOLEAN MODE)'],
@@ -546,8 +550,10 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseMatchMultipleTermsShouldReturnQueryInBooleanMode() {
-    Storm_Query::match('login,role', Storm_Query::terms('ADMIN INVITE')->terms('HUGO ADRIEN'))
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->match((new Storm_Query_MatchBoolean('login,role'))
+              ->against('ADMIN INVITE')
+              ->against('HUGO ADRIEN'))
       ->fetchAll();
 
     $this->assertEquals(['MATCH(login,role) AGAINST(\'ADMIN INVITE HUGO ADRIEN\' IN BOOLEAN MODE)'],
@@ -557,25 +563,28 @@ class Storm_Test_LoaderQueryTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseMatchStrictModeShouldRturnQueryInBooleanMode() {
-    Storm_Query::match('login,role', Storm_Query::terms('ADMIN INVITE')->terms('HUGO ADRIEN'),
-                       true)
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->match((new Storm_Query_MatchRating('login,role'))
+              ->beStrict()
+              ->against('ADMIN INVITE')
+              ->against('HUGO ADRIEN'))
       ->fetchAll();
 
-    $this->assertEquals(['MATCH(login,role) AGAINST(\'+(ADMIN INVITE) +(HUGO ADRIEN)\' IN BOOLEAN MODE)'],
+    $this->assertEquals(['MATCH(login,role) AGAINST(\'+(ADMIN INVITE) +(HUGO ADRIEN)\')'],
                         $this->_select->getAttributesForLastCallOn('where'));
   }
 
 
   /** @test */
   public function withClauseMatchStrictAndOneExactExpressionWithNoBooleanModeShouldReturnQuery() {
-    Storm_Query::match('login,role', Storm_Query::terms('ADMIN INVITE', true)->terms('HUGO ADRIEN'),
-                       true,
-                       false)
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->match((new Storm_Query_MatchBoolean('login,role'))
+              ->beStrict()
+              ->exact('ADMIN INVITE')
+              ->against('HUGO ADRIEN'))
       ->fetchAll();
 
-    $this->assertEquals(['MATCH(login,role) AGAINST(\'+"ADMIN INVITE" +(HUGO ADRIEN)\')'],
+    $this->assertEquals(['MATCH(login,role) AGAINST(\'+"ADMIN INVITE" +(HUGO ADRIEN)\' IN BOOLEAN MODE)'],
                         $this->_select->getAttributesForLastCallOn('where'));
   }
 }
@@ -601,8 +610,8 @@ class Storm_Test_LoaderQueryLimitTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseLimit30ShouldReturnQuery() {
-    Storm_Query::limit(30)
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->limit(30)
       ->fetchAll();
 
     $this->assertEquals([30],
@@ -612,8 +621,8 @@ class Storm_Test_LoaderQueryLimitTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseLimit30Offset10ShouldReturnQuery() {
-    Storm_Query::limit('10, 30')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->limit('10, 30')
       ->fetchAll();
 
     $this->assertEquals(['10, 30'],
@@ -642,8 +651,8 @@ class Storm_Test_LoaderQueryLimitPageTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseLimitPageShouldReturnQuery() {
-    Storm_Query::limit_page([1, 100])
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->limit_page([1, 100])
       ->fetchAll();
 
     $this->assertEquals([1, 100],
@@ -673,8 +682,8 @@ class Storm_Test_LoaderQueryOrderTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseOrderIdShouldReturnOrderId() {
-    Storm_Query::order('id')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->order('id')
       ->fetchAll();
 
     $this->assertEquals(['id'],
@@ -684,8 +693,8 @@ class Storm_Test_LoaderQueryOrderTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withClauseOrderIdDescShouldReturnOrderIdDesc() {
-    Storm_Query::order_desc('id')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->order_desc('id')
       ->fetchAll();
 
     $this->assertEquals(['id desc'],
@@ -695,10 +704,10 @@ class Storm_Test_LoaderQueryOrderTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withMultipleClauseOrderShouldReturnAllOrders() {
-    Storm_Query::order_desc('id')
+    Storm_Query::from($this->_loader)
+      ->order_desc('id')
       ->order('name')
       ->order_desc('role')
-      ->from($this->_loader)
       ->fetchAll();
 
     $this->assertEquals(['id desc', 'name', 'role desc'],
@@ -710,8 +719,9 @@ class Storm_Test_LoaderQueryOrderTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withMatchClauseOrderShouldReturnMatchOrder() {
-    Storm_Query::order_match('login, role', Storm_Query::terms(['ADMIN', 'INVITE']), false, false)
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->order_match((new Storm_Query_MatchRating('login, role'))
+                    ->against(['ADMIN', 'INVITE']))
       ->fetchAll();
 
     $this->assertEquals(['MATCH(login, role) AGAINST(\'ADMIN INVITE\')'],
@@ -721,8 +731,9 @@ class Storm_Test_LoaderQueryOrderTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withMatchClauseOrderDescShouldReturnMatchOrder() {
-    Storm_Query::order_match_desc('login, role', Storm_Query::terms(['ADMIN', 'INVITE']))
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->order_match_desc((new Storm_Query_MatchBoolean('login, role'))
+                         ->against(['ADMIN', 'INVITE']))
       ->fetchAll();
 
     $this->assertEquals(['MATCH(login, role) AGAINST(\'ADMIN INVITE\' IN BOOLEAN MODE) desc'],
@@ -751,8 +762,8 @@ class Storm_Test_LoaderQueryGroupByTest extends Storm_Test_LoaderTestCase {
 
   /** @test */
   public function withGroupByLoaderShouldCallGroupOnDbSelect() {
-    Storm_Query::group('name')
-      ->from($this->_loader)
+    Storm_Query::from($this->_loader)
+      ->group('name')
       ->fetchAll();
 
     $this->assertEquals(['name'],
diff --git a/tests/Storm/Test/LoaderVolatileTest.php b/tests/Storm/Test/LoaderVolatileTest.php
index 4dcd13c1a05b1e710f0a212a219befc02477f06e..243f24a2ac22424ca04816565c28aea8460ba2a2 100644
--- a/tests/Storm/Test/LoaderVolatileTest.php
+++ b/tests/Storm/Test/LoaderVolatileTest.php
@@ -610,8 +610,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function withClauseLimitOneShouldReturnOnlyUserOneAlbert() {
     $this->assertEquals([$this->albert],
-                        Storm_Query::limit(1)
-                        ->from(Storm_Test_VolatileUser::getLoader())
+                        Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+                        ->limit(1)
                         ->fetchAll());
   }
 
@@ -619,8 +619,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function withClauseLimitOneTwoShouldReturnTwoUserHubertAndZoe() {
     $this->assertEquals([$this->hubert, $this->zoe],
-                        Storm_Query::limit('1, 2')
-                        ->from(Storm_Test_VolatileUser::getLoader())
+                        Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+                        ->limit('1, 2')
                         ->fetchAll());
   }
 
@@ -826,8 +826,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectAllCatsWhereIdMoreThanThreeShouldAnswersFifiAndLoulou() {
     $this->assertEquals(['fifi', 'loulou'],
-                        (new Storm_Model_Collection(Storm_Query::gt('id', 3)
-                                                    ->from(Storm_Test_VolatileCat::getLoader())
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileCat::getLoader())
+                                                    ->gt('id', 3)
                                                     ->fetchAll()))
                         ->collect('name')
                         ->getArrayCopy());
@@ -837,8 +837,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectAllCatsWhereIdMoreOrEqualThanForShouldAnswersFifiAndLoulou() {
     $this->assertEquals(['fifi', 'loulou'],
-                        (new Storm_Model_Collection(Storm_Query::gt_eq('id', 4)
-                                                    ->from(Storm_Test_VolatileCat::getLoader())
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileCat::getLoader())
+                                                    ->gt_eq('id', 4)
                                                     ->fetchAll()))
                         ->collect('name')
                         ->getArrayCopy());
@@ -848,8 +848,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectAllCatsWhereIdLesserThanFiveShouldAnswersRiriAndFifi() {
     $this->assertEquals(['riri', 'fifi'],
-                        (new Storm_Model_Collection(Storm_Query::lt('id', 5)
-                                                    ->from(Storm_Test_VolatileCat::getLoader())
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileCat::getLoader())
+                                                    ->lt('id', 5)
                                                     ->fetchAll()))
                         ->collect('name')
                         ->getArrayCopy());
@@ -859,8 +859,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectAllCatsWhereIdLesserOrEqualThanForShouldAnswersRiriAndFifi() {
     $this->assertEquals(['riri', 'fifi'],
-                        (new Storm_Model_Collection(Storm_Query::lt_eq('id', 4)
-                                                    ->from(Storm_Test_VolatileCat::getLoader())
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileCat::getLoader())
+                                                    ->lt_eq('id', 4)
                                                     ->fetchAll()))
                         ->collect('name')
                         ->getArrayCopy());
@@ -870,8 +870,8 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectUserLoginNotHubertShouldReturnAlbertAndZoe() {
     $this->assertEquals(['albert', 'zoe'],
-                        (new Storm_Model_Collection(Storm_Query::not_eq('login', 'hubert')
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+                                                    ->not_eq('login', 'hubert')
                                                     ->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
@@ -881,9 +881,9 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectUserLoginInAlbertZoeAndFooNotLikeSnafuShouldReturnAlbert() {
     $this->assertEquals(['albert'],
-                        (new Storm_Model_Collection(Storm_Query::in('login', ['albert', 'zoe'])
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+                                                    ->in('login', ['albert', 'zoe'])
                                                     ->not_like('foo', '%naf%')
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
                                                     ->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
@@ -893,9 +893,9 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectUserLoginInAlbertZoeAndFooStartSnaShouldReturnZoe() {
     $this->assertEquals(['zoe'],
-                        (new Storm_Model_Collection(Storm_Query::in('login', ['albert', 'zoe'])
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+                                                    ->in('login', ['albert', 'zoe'])
                                                     ->start('foo', 'sna')
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
                                                     ->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
@@ -905,9 +905,9 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
   /** @test */
   public function selectUserLoginInAlbertZoeAndFooEndAfuShouldReturnZoe() {
     $this->assertEquals(['zoe'],
-                        (new Storm_Model_Collection(Storm_Query::in('login', ['albert', 'zoe'])
+                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+                                                    ->in('login', ['albert', 'zoe'])
                                                     ->end('foo', 'afu')
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
                                                     ->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
@@ -953,10 +953,11 @@ class Storm_Test_LoaderVolatileClauseWhereTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function withMatchLevelAgainstAdminShouldAnswersOnlyUser_Admin() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->match((new Storm_Query_MatchBoolean('level'))
+              ->against(['admin']));
     $this->assertEquals(['user_admin'],
-                        (new Storm_Model_Collection(Storm_Query::match('level', Storm_Query::terms(['admin']))
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -964,11 +965,11 @@ class Storm_Test_LoaderVolatileClauseWhereTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function withMatchLevelAgainstAdminDeuxiemeShouldAnswersUser_AdminAndUser_AdministrateurAndUser_Invite() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->match((new Storm_Query_MatchBoolean('level,foo'))
+              ->against(['admin', 'deuxieme']));
     $this->assertEquals(['user_admin', 'user_administrateur', 'user_invite'],
-                        (new Storm_Model_Collection(Storm_Query::match('level,foo',
-                                                                       Storm_Query::terms(['admin', 'deuxieme']))
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -976,13 +977,13 @@ class Storm_Test_LoaderVolatileClauseWhereTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function withMatchLevelAgainstStrictAdminRedacteur_DeuxiemeShouldAnswersUser_AdminAndUser_Administrateur() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->match((new Storm_Query_MatchBoolean('level,foo'))
+              ->beStrict()
+              ->against(['admin', 'redacteur'])
+              ->against(['deuxieme']));
     $this->assertEquals(['user_admin', 'user_administrateur'],
-                        (new Storm_Model_Collection(Storm_Query::match('level,foo',
-                                                                       Storm_Query::terms(['admin', 'redacteur'])
-                                                                       ->terms(['deuxieme']),
-                                                                       true)
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -990,12 +991,11 @@ class Storm_Test_LoaderVolatileClauseWhereTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function withMatchLevelAgainstExactDeuxiemeTroisiemeShouldAnswersOnlyUser_Admin() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->match((new Storm_Query_MatchBoolean('level,foo'))
+              ->exact('deuxieme redacteur'));
     $this->assertEquals(['user_administrateur'],
-                        (new Storm_Model_Collection(Storm_Query::match('level,foo',
-                                                                       Storm_Query::terms('deuxieme redacteur',
-                                                                                          true))
-                                                    ->from(Storm_Test_VolatileUser::getLoader())
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -1003,11 +1003,12 @@ class Storm_Test_LoaderVolatileClauseWhereTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function withOrClause_LevelInviteOrFooLikePremierShouldAnswersUser_AdminAndUser_Invite() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->or((new Storm_Query_Criteria)
+           ->eq('level', 'invite')
+           ->like('foo', '%premier%'));
     $this->assertEquals(['user_admin', 'user_invite'],
-                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-                                                    ->or(Storm_Query::eq('level', 'invite')
-                                                         ->like('foo', '%premier%'))
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -1015,11 +1016,12 @@ class Storm_Test_LoaderVolatileClauseWhereTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function withAndClause_LevelInviteOrFooLikePremierShouldAnswersOnlyUser_Invite() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->and((new Storm_Query_Criteria)
+            ->eq('level', 'invite')
+            ->like('foo', '%premier%'));
     $this->assertEquals(['user_invite'],
-                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-                                                    ->and(Storm_Query::eq('level', 'invite')
-                                                         ->like('foo', '%premier%'))
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -1064,10 +1066,10 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function with_Order1Desc_ShouldAnswersInOrdered() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->order_desc('order1');
     $this->assertEquals(['user_invite', 'user_administrateur', 'user_admin'],
-                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-                                                    ->order_desc('order1')
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -1075,11 +1077,11 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function with_Order3Asc_Order2Desc_ShouldAnswersOrderedUser() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->order('order3')
+      ->order_desc('order2');
     $this->assertEquals(['user_administrateur', 'user_admin', 'user_invite'],
-                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-                                                    ->order('order3')
-                                                    ->order_desc('order2')
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -1087,11 +1089,11 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function with_Order3Desc_Order2Desc_ShouldAnswersOrderedUser() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->order_desc('order3')
+      ->order_desc('order2');
     $this->assertEquals(['user_invite', 'user_administrateur', 'user_admin'],
-                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-                                                    ->order_desc('order3')
-                                                    ->order_desc('order2')
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }
@@ -1100,8 +1102,8 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
   /** @test */
   public function withMatch_Level_Administrateur_ShouldAnswersOrderedUser() {
     $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-      ->order_match('level', Storm_Query::terms('administrateur'),
-                         false, false);
+      ->order_match((new Storm_Query_MatchRating('level'))
+                    ->against('administrateur'));
     $this->assertEquals(['user_admin', 'user_invite', 'user_administrateur'],
                         (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
@@ -1112,8 +1114,8 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
   /** @test */
   public function withMatch_Level_Administrateur_Redacteur_Desc_ShouldAnswersOrderedUser() {
     $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-      ->order_match_desc('level', Storm_Query::terms('administrateur redacteur'),
-                         false, false);
+      ->order_match_desc((new Storm_Query_MatchRating('level'))
+                         ->against('administrateur redacteur'));
     $this->assertEquals(['user_administrateur', 'user_admin', 'user_invite'],
                         (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
@@ -1124,7 +1126,8 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
   /** @test */
   public function withMatchInBooleanMode_Level_Administrateur_Redacteur_Desc_ShouldAnswersOrderedUser() {
     $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-      ->order_match_desc('level', Storm_Query::terms('administrateur redacteur'));
+      ->order_match_desc((new Storm_Query_MatchBoolean('level'))
+                         ->against('administrateur redacteur'));
     $this->assertEquals(['user_admin', 'user_administrateur', 'user_invite'],
                         (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
@@ -1135,9 +1138,10 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
   /** @test */
   public function withMatch_FooDescExacteInBooleanMode_Forth_Fifth_Level_Administrateur_Redacteur_ShouldAnswersOrderedUser() {
     $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-      ->order_match_desc('foo', Storm_Query::terms('forth fifth', true))
-      ->order_match('level', Storm_Query::terms('administrateur redacteur'),
-                    false, false);
+      ->order_match_desc((new Storm_Query_MatchBoolean('foo'))
+                         ->exact('forth fifth'))
+      ->order_match((new Storm_Query_MatchRating('level'))
+                    ->against('administrateur redacteur'));
     $this->assertEquals(['user_invite', 'user_admin', 'user_administrateur'],
                         (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
@@ -1148,8 +1152,10 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
   /** @test */
   public function withMatch_FooLevelStrict_InviteRedacteur_SecondThird_ShouldAnswersOrderedUser() {
     $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-      ->order_match('foo,level', Storm_Query::terms('invite redacteur')
-                    ->terms('second third'), true);
+      ->order_match((new Storm_Query_MatchBoolean('foo,level'))
+                    ->beStrict()
+                    ->against('invite redacteur')
+                    ->against('second third'));
     $this->assertEquals(['user_administrateur', 'user_invite', 'user_admin'],
                         (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
@@ -1159,10 +1165,10 @@ class Storm_Test_LoaderVolatileClauseOrderTest extends Storm_Test_ModelTestCase
 
   /** @test */
   public function selectUsersGroupByOrder3ShouldAnswersUserAdminAndUserInvite() {
+    $query = Storm_Query::from(Storm_Test_VolatileUser::getLoader())
+      ->group('order3');
     $this->assertEquals([0 => 'user_admin', 2 => 'user_invite'],
-                        (new Storm_Model_Collection(Storm_Query::from(Storm_Test_VolatileUser::getLoader())
-                                                    ->group('order3')
-                                                    ->fetchAll()))
+                        (new Storm_Model_Collection($query->fetchAll()))
                         ->collect('login')
                         ->getArrayCopy());
   }