[+BUGFIX] Extbase (Persistence): Fixed typo in Exception class.
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Storage / Typo3DbBackend.php
index 0204bae..6aecfd7 100644 (file)
  * A Storage backend
  *
  * @package Extbase
- * @subpackage Persistence
+ * @subpackage Persistence\Storage
  * @version $Id: $
  */
 class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persistence_Storage_BackendInterface, t3lib_Singleton {
 
+       const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
+       const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
+
        /**
         * The TYPO3 database object
         *
@@ -42,36 +45,34 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        protected $databaseHandle;
 
        /**
+        * @var Tx_Extbase_Persistence_DataMapper
+        */
+       protected $dataMapper;
+
+       /**
         * The TYPO3 page select object. Used for language and workspace overlay
         *
         * @var t3lib_pageSelect
         */
        protected $pageSelectObject;
-       
-       /**
-        * TRUE if automatic cache clearing in TCEMAIN should be done on insert/update/delete, FALSE otherwise.
-        *  
-        * @var boolean
-        */
-       protected $automaticCacheClearing = FALSE;
 
        /**
         * Constructs this Storage Backend instance
+        *
+        * @param t3lib_db $databaseHandle The database handle
         */
-       public function __construct() {
-               $this->databaseHandle = $GLOBALS['TYPO3_DB'];
+       public function __construct($databaseHandle) {
+               $this->databaseHandle = $databaseHandle;
        }
-       
+
        /**
-        * Set the automatic cache clearing flag.
-        * If TRUE, then inserted/updated/deleted records trigger a TCEMAIN cache clearing.
-        * 
-        * @param $automaticCacheClearing boolean if TRUE, enables automatic cache clearing
+        * Injects the DataMapper to map nodes to objects
+        *
+        * @param Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper
         * @return void
-        * @internal
         */
-       public function setAutomaticCacheClearing($automaticCacheClearing) {
-               $this->automaticCacheClearing = (boolean)$automaticCacheClearing;
+       public function injectDataMapper(Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper) {
+               $this->dataMapper = $dataMapper;
        }
 
        /**
@@ -79,13 +80,16 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         *
         * @param string $tableName The database table name
         * @param array $row The row to be inserted
+        * @param boolean $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
         * @return int The uid of the inserted row
         */
-       public function addRow($tableName, array $row) {
+       public function addRow($tableName, array $row, $isRelation = FALSE) {
                $fields = array();
                $values = array();
                $parameters = array();
-               unset($row['uid']); // TODO Check if the offset exists
+               if (isset($row['uid'])) {
+                       unset($row['uid']);
+               }
                foreach ($row as $columnName => $value) {
                        $fields[] = $columnName;
                        $values[] = '?';
@@ -94,10 +98,12 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
 
                $sqlString = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
                $this->replacePlaceholders($sqlString, $parameters);
-
                $this->databaseHandle->sql_query($sqlString);
+               $this->checkSqlErrors();
                $uid = $this->databaseHandle->sql_insert_id();
-               $this->clearPageCache($tableName, $uid);
+               if (!$isRelation) {
+                       $this->clearPageCache($tableName, $uid);
+               }
                return $uid;
        }
 
@@ -106,9 +112,10 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         *
         * @param string $tableName The database table name
         * @param array $row The row to be updated
+        * @param boolean $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
         * @return void
         */
-       public function updateRow($tableName, array $row) {
+       public function updateRow($tableName, array $row, $isRelation = FALSE) {
                if (!isset($row['uid'])) throw new InvalidArgumentException('The given row must contain a value for "uid".');
                $uid = (int)$row['uid'];
                unset($row['uid']);
@@ -124,7 +131,10 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                $this->replacePlaceholders($sqlString, $parameters);
 
                $returnValue = $this->databaseHandle->sql_query($sqlString);
-               $this->clearPageCache($tableName, $uid);
+               $this->checkSqlErrors();
+               if (!$isRelation) {
+                       $this->clearPageCache($tableName, $uid);
+               }
                return $returnValue;
        }
 
@@ -132,14 +142,25 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * Deletes a row in the storage
         *
         * @param string $tableName The database table name
-        * @param array $uid The uid of the row to be deleted
+        * @param array $identifyer An array of identifyer array('fieldname' => value). This array will be transformed to a WHERE clause
+        * @param boolean $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
         * @return void
         */
-       public function removeRow($tableName, $uid) {
-               $sqlString = 'DELETE FROM ' . $tableName . ' WHERE uid=?';
-               $this->replacePlaceholders($sqlString, array((int)$uid));
-               $this->clearPageCache($tableName, $uid);
-               $returnValue = $this->databaseHandle->sql_query($sqlString);
+       public function removeRow($tableName, array $identifyer, $isRelation = FALSE) {
+               $fieldNames = array_keys($identifyer);
+               $suffixedFieldNames = array();
+               foreach ($fieldNames as $fieldName) {
+                       $suffixedFieldNames[] = $fieldName . '=?';
+               }
+               $parameters = array_values($identifyer);
+               $statement = 'DELETE FROM ' . $tableName;
+               $statement .= ' WHERE ' . implode(' AND ', $suffixedFieldNames);
+               $this->replacePlaceholders($statement, $parameters);
+               if (!$isRelation) {
+                       $this->clearPageCache($tableName, $uid, $isRelation);
+               }
+               $returnValue = $this->databaseHandle->sql_query($statement);
+               $this->checkSqlErrors();
                return $returnValue;
        }
 
@@ -150,34 +171,72 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * @return array The matching tuples
         */
        public function getRows(Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query) {
-               $sql = array();
-               $parameters = array();
-               $tuples = array();
+               $statement = $this->parseQuery($query);
+               // debug($statement, -2); // FIXME remove debug code
+               $result = $this->databaseHandle->sql_query($statement);
+               $this->checkSqlErrors();
+               if ($result) {
+                       $tuples = $this->getRowsFromResult($query->getSource(), $result);
+               }
 
-               $this->parseSource($query, $sql, $parameters);
-               $sqlString = 'SELECT ' . implode(',', $sql['fields']) . ' FROM ' . implode(' ', $sql['tables']);
+               return $tuples;
+       }
 
-               $this->parseConstraint($query->getConstraint(), $sql, $parameters, $query->getBoundVariableValues());
-               if (!empty($sql['where'])) {
-                       $sqlString .= ' WHERE ' . implode('', $sql['where']) . ' AND ' . implode(' AND ', $sql['enableFields']);
+       /**
+        * Returns an array with tuples matching the query.
+        *
+        * @param Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query
+        * @return array The matching tuples
+        */
+       public function parseQuery(Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query) {
+               $statement = '';
+               $parameters = array();
+               $constraint = $query->getConstraint();
+               if($constraint instanceof Tx_Extbase_Persistence_QOM_StatementInterface) {
+                       if ($constraint->getLanguage() === Tx_Extbase_Persistence_QOM_QueryObjectModelInterface::TYPO3_SQL_MYSQL) {
+                               $statement = $constraint->getStatement();
+                               $parameters= $query->getBoundVariableValues();
+                       } else {
+                               throw new Tx_Extbase_Persistence_Exception('Unsupported query language.', 1248701951);
+                       }
                } else {
-                       $sqlString .= ' WHERE ' . implode(' AND ', $sql['enableFields']);
-               }
-
-               $this->parseOrderings($query->getOrderings(), $sql, $parameters, $query->getBoundVariableValues());
-               if (!empty($sql['orderings'])) {
-                       $sqlString .= ' ORDER BY ' . implode(', ', $sql['orderings']);
-               }
+                       $sql = array();
+                       $sql['tables'] = array();
+                       $sql['fields'] = array();
+                       $sql['where'] = array();
+                       $sql['additionalWhereClause'] = array();
+                       $sql['orderings'] = array();
+                       $sql['limit'] = array();
+                       $tuples = array();
+
+                       $source = $query->getSource();
+                       $this->parseSource($query, $source, $sql, $parameters);
+
+                       $statement = 'SELECT ' . implode(',', $sql['fields']) . ' FROM ' . implode(' ', $sql['tables']);
+
+                       $this->parseConstraint($constraint, $source, $sql, $parameters, $query->getBoundVariableValues());
+
+                       if (!empty($sql['where'])) {
+                               $statement .= ' WHERE ' . implode('', $sql['where']);
+                               if (!empty($sql['additionalWhereClause'])) {
+                                       $statement .= ' AND ' . implode(' AND ', $sql['additionalWhereClause']);
+                               }
+                       } elseif (!empty($sql['additionalWhereClause'])) {
+                               $statement .= ' WHERE ' . implode(' AND ', $sql['additionalWhereClause']);
+                       }
 
-               $this->replacePlaceholders($sqlString, $parameters);
+                       $this->parseOrderings($query->getOrderings(), $source, $sql);
+                       if (!empty($sql['orderings'])) {
+                               $statement .= ' ORDER BY ' . implode(', ', $sql['orderings']);
+                       }
 
-               $result = $this->databaseHandle->sql_query($sqlString);
-               if ($result) {
-                       // TODO Check for selector name
-                       $tuples = $this->getRowsFromResult($query->getSelectorName(), $result);
+                       $this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $sql);
+                       if (!empty($sql['limit'])) {
+                               $statement .= ' LIMIT ' . $sql['limit'];
+                       }
                }
-               
-               return $tuples;
+               $this->replacePlaceholders($statement, $parameters);
+               return $statement;
        }
 
        /**
@@ -185,21 +244,32 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         *
         * @param array $properties The properties of the Value Object
         * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The Data Map
-        * @return array The matching tuples
+        * @return array The matching uid
         */
        public function hasValueObject(array $properties, Tx_Extbase_Persistence_Mapper_DataMap $dataMap) {
                $fields = array();
                $parameters = array();
                foreach ($properties as $propertyName => $propertyValue) {
-                       if ($dataMap->isPersistableProperty($propertyName) && ($propertyName !== 'uid')) {
+                       // FIXME We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method
+                       if ($dataMap->isPersistableProperty($propertyName) && ($propertyName !== 'uid')  && ($propertyName !== 'pid') && ($propertyName !== 'isClone')) {
                                $fields[] = $dataMap->getColumnMap($propertyName)->getColumnName() . '=?';
                                $parameters[] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
                        }
                }
+               $sql = array();
+               $sql['additionalWhereClause'] = array();
 
-               $sqlString = 'SELECT * FROM ' . $dataMap->getTableName() .  ' WHERE ' . implode('', $fields);
-               $this->replacePlaceholders($sqlString, $parameters);
-               $res = $this->databaseHandle->sql_query($sqlString);
+               $tableName = $dataMap->getTableName();
+               $this->addEnableFieldsStatement($tableName, $sql);
+
+               $statement = 'SELECT * FROM ' . $tableName;
+               $statement .= ' WHERE ' . implode(' AND ', $fields);
+               if (!empty($sql['additionalWhereClause'])) {
+                       $statement .= ' AND ' . implode(' AND ', $sql['additionalWhereClause']);
+               }
+               $this->replacePlaceholders($statement, $parameters);
+               $res = $this->databaseHandle->sql_query($statement);
+               $this->checkSqlErrors();
                $row = $this->databaseHandle->sql_fetch_assoc($res);
                if ($row !== FALSE) {
                        return $row['uid'];
@@ -212,80 +282,103 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * Transforms a Query Source into SQL and parameter arrays
         *
         * @param Tx_Extbase_Persistence_QOM_QueryObjectModel $query
+        * @param Tx_Extbase_Persistence_QOM_SourceInterface $source The source
         * @param array &$sql
         * @param array &$parameters
         * @return void
         */
-       protected function parseSource(Tx_Extbase_Persistence_QOM_QueryObjectModel $query, array &$sql, array &$parameters) {
-               $source = $query->getSource();
+       protected function parseSource(Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query, Tx_Extbase_Persistence_QOM_SourceInterface $source, array &$sql, array &$parameters) {
                if ($source instanceof Tx_Extbase_Persistence_QOM_SelectorInterface) {
-                       $selectorName = $source->getSelectorName();
-                       $sql['fields'][] = $selectorName . '.*';
-                       $sql['tables'][] = $selectorName;
-                       // TODO Should we make the usage of enableFields configurable? And how? Because the Query object and even the QOM should be abstracted from the storage backend.
-                       $this->addEnableFieldsStatement($selectorName, $sql);
+                       $tableName = $source->getSelectorName();
+                       $sql['fields'][] = $tableName . '.*';
+                       $sql['tables'][] = $tableName;
+                       $querySettings = $query->getQuerySettings();
+                       if ($querySettings instanceof Tx_Extbase_Persistence_Typo3QuerySettingsInterface) {
+                               if ($querySettings->getRespectEnableFields()) {
+                                       $this->addEnableFieldsStatement($tableName, $sql);
+                               }
+                               if ($querySettings->getRespectStoragePage()) {
+                                       $this->addPageIdStatement($tableName, $sql);
+                               }
+                       }
                } elseif ($source instanceof Tx_Extbase_Persistence_QOM_JoinInterface) {
-                       $this->parseJoin($source, $sql, $parameters);
+                       $this->parseJoin($query, $source, $sql);
                }
        }
 
        /**
         * Transforms a Join into SQL and parameter arrays
         *
-        * @param Tx_Extbase_Persistence_QOM_JoinInterface $join
-        * @param array &$sql
-        * @param array &$parameters
+        * @param Tx_Extbase_Persistence_QOM_QueryObjectModel $query The Query Object Model
+        * @param Tx_Extbase_Persistence_QOM_JoinInterface $join The join
+        * @param array &$sql The query parts
         * @return void
         */
-       protected function parseJoin(Tx_Extbase_Persistence_QOM_JoinInterface $join, array &$sql, array &$parameters) {
-               $leftSelectorName = $join->getLeft()->getSelectorName();
-               $rightSelectorName = $join->getRight()->getSelectorName();
+       protected function parseJoin(Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query, Tx_Extbase_Persistence_QOM_JoinInterface $join, array &$sql) {
+               $leftSource = $join->getLeft();
+               $leftTableName = $leftSource->getSelectorName();
+               $rightSource = $join->getRight();
+               $rightTableName = $rightSource->getSelectorName();
 
-               $sql['fields'][] = $leftSelectorName . '.*';
-               $sql['fields'][] = $rightSelectorName . '.*';
+               $sql['fields'][] = $leftTableName . '.*';
+               $sql['fields'][] = $rightTableName . '.*';
 
                // TODO Implement support for different join types and nested joins
-               $sql['tables'][] = $leftSelectorName . ' LEFT JOIN ' . $rightSelectorName;
+               $sql['tables'][] = $leftTableName . ' LEFT JOIN ' . $rightTableName;
 
                $joinCondition = $join->getJoinCondition();
                // TODO Check the parsing of the join
                if ($joinCondition instanceof Tx_Extbase_Persistence_QOM_EquiJoinCondition) {
-                       $sql['tables'][] = 'ON ' . $joinCondition->getSelector1Name() . '.' . $joinCondition->getProperty1Name() . ' = ' . $joinCondition->getSelector2Name() . '.' . $joinCondition->getProperty2Name();
+                       // TODO Discuss, if we should use $leftSource instead of $selector1Name
+                       $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftSource->getNodeTypeName());
+                       $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightSource->getNodeTypeName());
+                       $sql['tables'][] = 'ON ' . $joinCondition->getSelector1Name() . '.' . $column1Name . ' = ' . $joinCondition->getSelector2Name() . '.' . $column2Name;
                }
                // TODO Implement childtableWhere
 
-               $this->addEnableFieldsStatement($leftSelectorName, $sql);
-               $this->addEnableFieldsStatement($rightSelectorName, $sql);
+               $querySettings = $query->getQuerySettings();
+               if ($querySettings instanceof Tx_Extbase_Persistence_Typo3QuerySettingsInterface) {
+                       if ($querySettings->getRespectEnableFields()) {
+                               $this->addEnableFieldsStatement($leftTableName, $sql);
+                               $this->addEnableFieldsStatement($rightTableName, $sql);
+                       }
+                       if ($querySettings->getRespectStoragePage()) {
+                               $this->addPageIdStatement($leftTableName, $sql);
+                               $this->addPageIdStatement($rightTableName, $sql);
+                       }
+               }
        }
 
        /**
         * Transforms a constraint into SQL and parameter arrays
         *
-        * @param Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint
-        * @param array &$sql
-        * @param array &$parameters
-        * @param array $boundVariableValues
+        * @param Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint The constraint
+        * @param Tx_Extbase_Persistence_QOM_SourceInterface $source The source
+        * @param array &$sql The query parts
+        * @param array &$parameters The parameters that will replace the markers
+        * @param array $boundVariableValues The bound variables in the query (key) and their values (value)
         * @return void
         */
-       protected function parseConstraint(Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint = NULL, array &$sql, array &$parameters, array $boundVariableValues) {
+       protected function parseConstraint(Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint = NULL, Tx_Extbase_Persistence_QOM_SourceInterface $source, array &$sql, array &$parameters, array $boundVariableValues) {
+               if ($constraint === NULL) return;
                if ($constraint instanceof Tx_Extbase_Persistence_QOM_AndInterface) {
                        $sql['where'][] = '(';
-                       $this->parseConstraint($constraint->getConstraint1(), $sql, $parameters, $boundVariableValues);
+                       $this->parseConstraint($constraint->getConstraint1(), $source, $sql, $parameters, $boundVariableValues);
                        $sql['where'][] = ' AND ';
-                       $this->parseConstraint($constraint->getConstraint2(), $sql, $parameters, $boundVariableValues);
+                       $this->parseConstraint($constraint->getConstraint2(), $source, $sql, $parameters, $boundVariableValues);
                        $sql['where'][] = ')';
                } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_OrInterface) {
                        $sql['where'][] = '(';
-                       $this->parseConstraint($constraint->getConstraint1(), $sql, $parameters, $boundVariableValues);
+                       $this->parseConstraint($constraint->getConstraint1(), $source, $sql, $parameters, $boundVariableValues);
                        $sql['where'][] = ' OR ';
-                       $this->parseConstraint($constraint->getConstraint2(), $sql, $parameters, $boundVariableValues);
+                       $this->parseConstraint($constraint->getConstraint2(), $source, $sql, $parameters, $boundVariableValues);
                        $sql['where'][] = ')';
                } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_NotInterface) {
-                       $sql['where'][] = '(NOT ';
-                       $this->parseConstraint($constraint->getConstraint(), $sql, $parameters, $boundVariableValues);
+                       $sql['where'][] = 'NOT (';
+                       $this->parseConstraint($constraint->getConstraint(), $source, $sql, $parameters, $boundVariableValues);
                        $sql['where'][] = ')';
                } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_ComparisonInterface) {
-                       $this->parseComparison($constraint, $sql, $parameters, $boundVariableValues);
+                       $this->parseComparison($constraint, $source, $sql, $parameters, $boundVariableValues);
                } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_RelatedInterface) {
                        $this->parseRelated($constraint, $sql, $parameters, $boundVariableValues);
                }
@@ -295,17 +388,29 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * Parse a Comparison into SQL and parameter arrays.
         *
         * @param Tx_Extbase_Persistence_QOM_ComparisonInterface $comparison The comparison to parse
+        * @param Tx_Extbase_Persistence_QOM_SourceInterface $source The source
         * @param array &$sql SQL query parts to add to
         * @param array &$parameters Parameters to bind to the SQL
         * @param array $boundVariableValues The bound variables in the query and their values
         * @return void
         */
-       protected function parseComparison(Tx_Extbase_Persistence_QOM_ComparisonInterface $comparison, array &$sql, array &$parameters, array $boundVariableValues) {
-               $this->parseDynamicOperand($comparison->getOperand1(), $comparison->getOperator(), $sql, $parameters);
-
-               if ($comparison->getOperand2() instanceof Tx_Extbase_Persistence_QOM_BindVariableValueInterface) {
-                       $parameters[] = $boundVariableValues[$comparison->getOperand2()->getBindVariableName()];
+       protected function parseComparison(Tx_Extbase_Persistence_QOM_ComparisonInterface $comparison, Tx_Extbase_Persistence_QOM_SourceInterface $source, array &$sql, array &$parameters, array $boundVariableValues) {
+               if (!($comparison->getOperand2() instanceof Tx_Extbase_Persistence_QOM_BindVariableValueInterface)) throw new Tx_Extbase_Persistence_Exception('Type of operand is not supported', 1247581135);
+
+               $value = $boundVariableValues[$comparison->getOperand2()->getBindVariableName()];
+               $operator = $comparison->getOperator();
+               if ($value === NULL) {
+                       if ($operator === Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO) {
+                               $operator = self::OPERATOR_EQUAL_TO_NULL;
+                       } elseif ($operator === Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_NOT_EQUAL_TO) {
+                               $operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
+                       } else {
+                               // TODO Throw exception
+                       }
                }
+               $parameters[] = $value;
+
+               $this->parseDynamicOperand($comparison->getOperand1(), $operator, $source, $sql, $parameters);
        }
 
        /**
@@ -313,27 +418,33 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         *
         * @param Tx_Extbase_Persistence_QOM_DynamicOperandInterface $operand
         * @param string $operator One of the JCR_OPERATOR_* constants
-        * @param array $boundVariableValues
-        * @param array &$parameters
-        * @param string $valueFunction an aoptional SQL function to apply to the operand value
+        * @param Tx_Extbase_Persistence_QOM_SourceInterface $source The source
+        * @param array &$sql The query parts
+        * @param array &$parameters The parameters that will replace the markers
+        * @param string $valueFunction an optional SQL function to apply to the operand value
         * @return void
         */
-       protected function parseDynamicOperand(Tx_Extbase_Persistence_QOM_DynamicOperandInterface $operand, $operator, array &$sql, array &$parameters, $valueFunction = NULL) {
+       protected function parseDynamicOperand(Tx_Extbase_Persistence_QOM_DynamicOperandInterface $operand, $operator, Tx_Extbase_Persistence_QOM_SourceInterface $source, array &$sql, array &$parameters, $valueFunction = NULL) {
                if ($operand instanceof Tx_Extbase_Persistence_QOM_LowerCaseInterface) {
-                       $this->parseDynamicOperand($operand->getOperand(), $operator, $sql, $parameters, 'LOWER');
+                       $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, $parameters, 'LOWER');
                } elseif ($operand instanceof Tx_Extbase_Persistence_QOM_UpperCaseInterface) {
-                       $this->parseDynamicOperand($operand->getOperand(), $operator, $sql, $parameters, 'UPPER');
+                       $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, $parameters, 'UPPER');
                } elseif ($operand instanceof Tx_Extbase_Persistence_QOM_PropertyValueInterface) {
-                       $selectorName = $operand->getSelectorName();
+                       $tableName = $operand->getSelectorName();
+                       // FIXME Discuss the translation from propertyName to columnName
+                       if ($source instanceof Tx_Extbase_Persistence_QOM_SelectorInterface) {
+                               $className = $source->getNodeTypeName();
+                       } else {
+                               $className = '';
+                       }
+                       $columnName = $this->dataMapper->convertPropertyNameToColumnName($operand->getPropertyName(), $className);
                        $operator = $this->resolveOperator($operator);
 
-                       $constraintSQL = '(';
                        if ($valueFunction === NULL) {
-                               $constraintSQL .= (!empty($selectorName) ? $selectorName . '.' : '') . $operand->getPropertyName() .  ' ' . $operator . ' ?';
+                               $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName .  ' ' . $operator . ' ?';
                        } else {
-                               $constraintSQL .= $valueFunction . '(' . (!empty($selectorName) ? $selectorName . '.' : '') . $operand->getPropertyName() .  ' ' . $operator . ' ?';
+                               $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName .  ' ' . $operator . ' ?';
                        }
-                       $constraintSQL .= ') ';
 
                        $sql['where'][] = $constraintSQL;
                }
@@ -347,6 +458,12 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         */
        protected function resolveOperator($operator) {
                switch ($operator) {
+                       case self::OPERATOR_EQUAL_TO_NULL:
+                               $operator = 'IS';
+                               break;
+                       case self::OPERATOR_NOT_EQUAL_TO_NULL:
+                               $operator = 'IS NOT';
+                               break;
                        case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO:
                                $operator = '=';
                                break;
@@ -379,64 +496,133 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * Replace query placeholders in a query part by the given
         * parameters.
         *
-        * @param string $queryPart The query part with placeholders
+        * @param string $sqlString The query part with placeholders
         * @param array $parameters The parameters
         * @return string The query part with replaced placeholders
         */
        protected function replacePlaceholders(&$sqlString, array $parameters) {
                if (substr_count($sqlString, '?') !== count($parameters)) throw new Tx_Extbase_Persistence_Exception('The number of question marks to replace must be equal to the number of parameters.', 1242816074);
+               $offset = 0;
                foreach ($parameters as $parameter) {
-                       $markPosition = strpos($sqlString, '?');
+                       $markPosition = strpos($sqlString, '?', $offset);
                        if ($markPosition !== FALSE) {
-                               $sqlString = substr($sqlString, 0, $markPosition) . '"' . $parameter . '"' . substr($sqlString, $markPosition + 1);
+                               if ($parameter === NULL) {
+                                       $parameter = 'NULL';
+                               } else {
+                                       $parameter = $this->databaseHandle->fullQuoteStr($parameter, 'foo'); // FIXME This may not work with DBAL; check this
+                               }
+                               $sqlString = substr($sqlString, 0, $markPosition) . $parameter . substr($sqlString, $markPosition + 1);
                        }
+                       $offset = $markPosition + strlen($parameter);
                }
        }
 
        /**
         * Builds the enable fields statement
         *
-        * @param string $selectorName The selector name (= database table name)
+        * @param string $tableName The database table name
         * @param array &$sql The query parts
         * @return void
         */
-       protected function addEnableFieldsStatement($selectorName, array &$sql) {
-               // TODO We have to call the appropriate API method if we are in TYPO3BE mode
-               if (is_array($GLOBALS['TCA'][$selectorName]['ctrl'])) {
-                       $statement = substr($GLOBALS['TSFE']->sys_page->enableFields($selectorName), 4);
+       protected function addEnableFieldsStatement($tableName, array &$sql) {
+               if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
+                       if (TYPO3_MODE === 'FE') {
+                               $statement = $GLOBALS['TSFE']->sys_page->enableFields($tableName);
+                       } else { // TYPO3_MODE === 'BE'
+                               $statement = t3lib_BEfunc::deleteClause($tableName);
+                               $statement .= t3lib_BEfunc::BEenableFields($tableName);
+                       }
                        if(!empty($statement)) {
-                               $sql['enableFields'][] = $statement;
+                               $statement = substr($statement, 5);
+                               $sql['additionalWhereClause'][] = $statement;
                        }
                }
        }
 
        /**
-        * Transforms orderings into SQL
+        * Builds the page ID checking statement
         *
-        * @param array $orderings
-        * @param array &$sql
-        * @param array &$parameters
-        * @param array $boundVariableValues
+        * @param string $tableName The database table name
+        * @param array &$sql The query parts
+        * @return void
+        */
+       protected function addPageIdStatement($tableName, array &$sql) {
+               $columns = $this->databaseHandle->admin_get_fields($tableName);         
+               if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('pid', $columns)) {
+                       $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+                       $sql['additionalWhereClause'][] = $tableName . '.pid IN (' . implode(', ', t3lib_div::intExplode(',', $extbaseFrameworkConfiguration['persistence']['storagePid'])) . ')';
+               }
+       }
+
+       /**
+        * Transforms orderings into SQL.
+        *
+        * @param array $orderings Ann array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
+        * @param Tx_Extbase_Persistence_QOM_SourceInterface $source The source
+        * @param array &$sql The query parts
         * @return void
         */
-       protected function parseOrderings(array $orderings = NULL, array &$sql, array &$parameters, array $boundVariableValues) {
-               if (is_array($orderings)) {
-                       foreach ($orderings as $propertyName => $ordering) {
-                               // TODO Implement
+       protected function parseOrderings(array $orderings, Tx_Extbase_Persistence_QOM_SourceInterface $source, array &$sql) {
+               foreach ($orderings as $ordering) {
+                       $operand = $ordering->getOperand();
+                       $order = $ordering->getOrder();
+                       if ($operand instanceof Tx_Extbase_Persistence_QOM_PropertyValue) {
+                               switch ($order) {
+                                       case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_ORDER_ASCENDING:
+                                               $order = 'ASC';
+                                               break;
+                                       case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_ORDER_DESCENDING:
+                                               $order = 'DESC';
+                                               break;
+                                       default:
+                                               throw new Tx_Extbase_Persistence_Exception_UnsupportedOrder('Unsupported order encountered.', 1242816074);
+                               }
+                               $tableName = $operand->getSelectorName();
+                               $className = '';
+                               if ($source instanceof Tx_Extbase_Persistence_QOM_SelectorInterface) {
+                                       $className = $source->getNodeTypeName();
+                               }
+                               $columnName = $this->dataMapper->convertPropertyNameToColumnName($operand->getPropertyName(), $className);
+                               if (strlen($tableName) > 0) {
+                                       $sql['orderings'][] = $tableName . '.' . $columnName . ' ' . $order;
+                               } else {
+                                       $sql['orderings'][] = $columnName . ' ' . $order;
+                               }
                        }
                }
        }
 
        /**
+        * Transforms limit and offset into SQL
+        *
+        * @param int $limit
+        * @param int $offset
+        * @param array &$sql
+        * @return void
+        */
+       protected function parseLimitAndOffset($limit, $offset, array &$sql) {
+               if ($limit !== NULL && $offset !== NULL) {
+                       $sql['limit'] = $offset . ', ' . $limit;
+               } elseif ($limit !== NULL) {
+                       $sql['limit'] = $limit;
+               }
+       }
+
+       /**
         * Transforms a Resource from a database query to an array of rows. Performs the language and
         * workspace overlay before.
         *
+        * @param Tx_Extbase_Persistence_QOM_SourceInterface $source The source (selector od join)
+        * @param resource &$sql The resource
         * @return array The result as an array of rows (tuples)
         */
-       protected function getRowsFromResult($tableName, $res) {
+       protected function getRowsFromResult(Tx_Extbase_Persistence_QOM_SourceInterface $source, $res) {
                $rows = array();
                while ($row = $this->databaseHandle->sql_fetch_assoc($res)) {
-                       $row = $this->doLanguageAndWorkspaceOverlay($tableName, $row);
+                       if      ($source instanceof Tx_Extbase_Persistence_QOM_SelectorInterface) {
+                               // FIXME The overlay is only performed if we query a single table; no joins
+                               $row = $this->doLanguageAndWorkspaceOverlay($source->getSelectorName(), $row);
+                       }
                        if (is_array($row)) {
                                // TODO Check if this is necessary, maybe the last line is enough
                                $arrayKeys = range(0,count($row));
@@ -478,7 +664,6 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                        } else {
                                require_once(PATH_t3lib . 'class.t3lib_page.php');
                                $this->pageSelectObject = t3lib_div::makeInstance( 't3lib_pageSelect' );
-                               //$this->pageSelectObject->versioningPreview =  TRUE;
                                if ($workspaceUid === NULL) {
                                        $workspaceUid = $GLOBALS['BE_USER']->workspace;
                                }
@@ -493,46 +678,70 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        }
 
        /**
+        * Checks if there are SQL errors in the last query, and if yes, throw an exception.
+        *
+        * @return void
+        * @throws Tx_Extbase_Persistence_Storage_Exception_SqlError
+        */
+       protected function checkSqlErrors() {
+               $error = $this->databaseHandle->sql_error();
+               if ($error !== '') {
+                       throw new Tx_Extbase_Persistence_Storage_Exception_SqlError($error, 1247602160);
+               }
+       }
+
+       /**
         * Clear the TYPO3 page cache for the given record.
+        * If the record lies on a page, then we clear the cache of this page.
+        * If the record has no PID column, we clear the cache of the current page as best-effort.
+        *
         * Much of this functionality is taken from t3lib_tcemain::clear_cache() which unfortunately only works with logged-in BE user.
-        * 
+        *
         * @param $tableName Table name of the record
         * @param $uid UID of the record
         * @return void
         */
        protected function clearPageCache($tableName, $uid) {
-               if ($this->automaticCacheClearing !== TRUE) return;
+               $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+               if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
+               } else {
+                       // if disabled, return
+                       return;
+               }
 
-               $pageCache = $GLOBALS['typo3CacheManager']->getCache('cache_pages');
-               $pageSectionCache = $GLOBALS['typo3CacheManager']->getCache('cache_pagesection');
-       
-               $result = $GLOBALS['TYPO3_DB']->exec_SELECTquery('pid', $tableName, 'uid='.intval($uid));
-       
                $pageIdsToClear = array();
-               if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result))      {
-                       $storagePage = $row['pid'];
+               $storagePage = NULL;
+
+               $columns = $this->databaseHandle->admin_get_fields($tableName);
+               if (array_key_exists('pid', $columns)) {
+                       $result = $this->databaseHandle->exec_SELECTquery('pid', $tableName, 'uid='.intval($uid));
+                       if ($row = $this->databaseHandle->sql_fetch_assoc($result))     {
+                               $storagePage = $row['pid'];
+                               $pageIdsToClear[] = $storagePage;
+                       }
+               } elseif (isset($GLOBALS['TSFE'])) {
+                       // No PID column - we can do a best-effort to clear the cache of the current page if in FE
+                       $storagePage = $GLOBALS['TSFE']->id;
                        $pageIdsToClear[] = $storagePage;
                }
-               if (!$storagePage) {
+
+
+               if ($storagePage === NULL) {
                        return;
                }
-               
+
                $pageTSConfig = t3lib_BEfunc::getPagesTSconfig($storagePage);
-               
-               if ($pageTSConfig['clearCacheCmd'])     {
-                       $clearCacheCommands = t3lib_div::trimExplode(',',strtolower($TSConfig['clearCacheCmd']),1);
-                               $clearCacheCommands = array_unique($clearCacheCommands);
-                               foreach ($clearCacheCommands as $clearCacheCommand)     {
-                                       if (t3lib_div::testInt($clearCacheCommand))     {
-                                               $pageIdsToClear[] = $clearCacheCommand;
-                                       }
+               if (isset($pageTSConfig['TCEMAIN.']['clearCacheCmd']))  {
+                       $clearCacheCommands = t3lib_div::trimExplode(',',strtolower($pageTSConfig['TCEMAIN.']['clearCacheCmd']),1);
+                       $clearCacheCommands = array_unique($clearCacheCommands);
+                       foreach ($clearCacheCommands as $clearCacheCommand)     {
+                               if (t3lib_div::testInt($clearCacheCommand))     {
+                                       $pageIdsToClear[] = $clearCacheCommand;
                                }
                        }
-               
-               foreach ($pageIdsToClear as $pageIdToClear) {
-                       $pageCache->flushByTag('pageId_' . $pageIdToClear);
-                       $pageSectionCache->flushByTag('pageId_' . $pageIdToClear);
                }
+
+               Tx_Extbase_Utility_Cache::clearPageCache($pageIdsToClear);
        }
 }