2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage;
21 use TYPO3\CMS\Extbase\Persistence\Generic\Qom;
22 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException;
102 $this->databaseHandle =
$GLOBALS[
'TYPO3_DB'];
112 $this->tableColumnCache = $this->cacheManager->getCache(
'extbase_typo3dbbackend_tablecolumns');
124 $hashPartials = array(
127 array_keys($parameters),
131 $hash = md5(serialize($hashPartials));
133 return array($hash, $parameters);
151 $parameters = array();
152 $operators = array();
153 $objectsToParse = array();
158 $objectsToParse = array($comparison->getConstraint1(), $comparison->getConstraint2());
161 $objectsToParse = array($comparison->getConstraint1(), $comparison->getConstraint2());
164 $objectsToParse = array($comparison->getConstraint());
166 $operand1 = $comparison->getOperand1();
168 $comparison->setParameterIdentifier($parameterIdentifier);
169 $operator = $comparison->getOperator();
170 $operand2 = $comparison->getOperand2();
173 foreach ($operand2 as $value) {
174 $value = $this->dataMapper->getPlainValue($value);
175 if ($value !== null) {
179 $parameters[$parameterIdentifier] = $items;
181 $parameters[$parameterIdentifier] = $operand2;
183 $operators[] = $operator;
184 }
elseif (!is_object($comparison)) {
185 $parameters = array(array(), $comparison);
186 return array($parameters, $operators);
188 throw new \Exception(
'Can not hash Query Component "' . get_class($comparison) .
'".', 1392840462);
191 $childObjectIterator = 0;
192 foreach ($objectsToParse as $objectToParse) {
193 list($preparsedParameters, $preparsedOperators) = $this->
preparseComparison($objectToParse, $qomPath . $delimiter . $childObjectIterator++);
194 if (!empty($preparsedParameters)) {
195 $parameters = array_merge($parameters, $preparsedParameters);
197 if (!empty($preparsedOperators)) {
198 $operators = array_merge($operators, $preparsedOperators);
202 return array($parameters, $operators);
214 return ':' . preg_replace(
'/[^A-Za-z0-9]/',
'', $identifier);
225 $this->tablePropertyMap = array();
227 $sql[
'keywords'] = array();
228 $sql[
'tables'] = array();
229 $sql[
'unions'] = array();
230 $sql[
'fields'] = array();
231 $sql[
'where'] = array();
232 $sql[
'additionalWhereClause'] = array();
233 $sql[
'orderings'] = array();
234 $sql[
'limit'] = ((int)$query->
getLimit() ?: null);
235 $sql[
'offset'] = ((int)$query->
getOffset() ?: null);
236 $sql[
'tableAliasMap'] = array();
242 foreach ($sql[
'tableAliasMap'] as $tableAlias => $tableName) {
244 if ($additionalWhereClause !==
'') {
246 $sql[
'additionalWhereClause'][] = $additionalWhereClause;
264 if (!isset($sql[
'additionalWhereClause'])) {
265 throw new \InvalidArgumentException(
'Invalid statement given.', 1399512421);
267 foreach ($sql[
'tableAliasMap'] as $tableAlias => $tableName) {
269 if ($statement !==
'') {
271 $sql[
'additionalWhereClause'][] = $statement;
289 if (isset($sql[
'unions'][$tableAlias])) {
290 $statement =
'((' . $statement .
') OR ' . $tableAlias .
'.uid' .
' IS NULL)';
306 $className = $source->getNodeTypeName();
307 $tableName = $this->dataMapper->getDataMap($className)->getTableName();
310 $sql[
'fields'][$tableName] = $tableName .
'.*';
311 $sql[
'tables'][$tableName] = $tableName;
328 $sql[
'where'][] =
'(';
330 $sql[
'where'][] =
' AND ';
332 $sql[
'where'][] =
')';
334 $sql[
'where'][] =
'(';
336 $sql[
'where'][] =
' OR ';
338 $sql[
'where'][] =
')';
340 $sql[
'where'][] =
'NOT (';
342 $sql[
'where'][] =
')';
359 foreach ($orderings as $propertyName => $order) {
368 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException(
'Unsupported order encountered.', 1242816074);
373 $className = $source->getNodeTypeName();
374 $tableName = $this->dataMapper->convertClassNameToTableName($className);
375 $fullPropertyPath =
'';
376 while (strpos($propertyName,
'.') !==
false) {
377 $this->
addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
380 $tableName = $source->getLeft()->getSelectorName();
382 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
383 if ($tableName !==
'') {
384 $sql[
'orderings'][] = $tableName .
'.' . $columnName .
' ' . $order;
386 $sql[
'orderings'][] = $columnName .
' ' . $order;
405 $operator = $comparison->getOperator();
406 $operand2 = $comparison->getOperand2();
409 foreach ($operand2 as $value) {
410 $value = $this->dataMapper->getPlainValue($value);
411 if ($value !== null) {
412 $parameters[] = $value;
416 if ($hasValue ===
false) {
417 $sql[
'where'][] =
'1<>1';
422 if ($operand2 === null) {
423 $sql[
'where'][] =
'1<>1';
426 throw new \RuntimeException(
'Source is not of type "SelectorInterface"', 1395362539);
428 $className = $source->getNodeTypeName();
429 $tableName = $this->dataMapper->convertClassNameToTableName($className);
430 $operand1 = $comparison->getOperand1();
431 $propertyName = $operand1->getPropertyName();
432 $fullPropertyPath =
'';
433 while (strpos($propertyName,
'.') !==
false) {
434 $this->
addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
436 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
437 $dataMap = $this->dataMapper->getDataMap($className);
438 $columnMap = $dataMap->getColumnMap($propertyName);
441 $relationTableName = $columnMap->getRelationTableName();
443 $sql[
'where'][] = $tableName .
'.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() .
' FROM ' . $relationTableName .
' WHERE ' . $columnMap->getChildKeyFieldName() .
'=' . $parameterIdentifier . $additionalWhereForMatchFields .
')';
445 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
446 if (isset($parentKeyFieldName)) {
447 $childTableName = $columnMap->getChildTableName();
448 $sql[
'where'][] = $tableName .
'.uid=(SELECT ' . $childTableName .
'.' . $parentKeyFieldName .
' FROM ' . $childTableName .
' WHERE ' . $childTableName .
'.uid=' . $parameterIdentifier .
')';
450 $sql[
'where'][] =
'FIND_IN_SET(' . $parameterIdentifier .
', ' . $tableName .
'.' . $columnName .
')';
453 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException(
'Unsupported or non-existing property name "' . $propertyName .
'" used in relation matching.', 1327065745);
472 $operand = $comparison->getOperand1();
474 $constraintSQL = $this->
parseOperand($operand, $source, $sql) .
' ' . $operator .
' ';
477 if ($operator ===
'IN') {
478 $parameterIdentifier =
'(' . $parameterIdentifier .
')';
480 $constraintSQL .= $parameterIdentifier;
482 $sql[
'where'][] = $constraintSQL;
495 $constraintSQL =
'LOWER(' . $this->
parseOperand($operand->getOperand(), $source, $sql) .
')';
497 $constraintSQL =
'UPPER(' . $this->
parseOperand($operand->getOperand(), $source, $sql) .
')';
499 $propertyName = $operand->getPropertyName();
503 $className = $source->getNodeTypeName();
504 $tableName = $this->dataMapper->convertClassNameToTableName($className);
505 $fullPropertyPath =
'';
506 while (strpos($propertyName,
'.') !==
false) {
507 $this->
addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
510 $tableName = $source->getJoinCondition()->getSelector1Name();
512 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
513 $constraintSQL = (!empty($tableName) ? $tableName .
'.' :
'') . $columnName;
515 throw new \InvalidArgumentException(
'Given operand has invalid type "' . get_class($operand) .
'".', 1395710211);
517 return $constraintSQL;
529 if ($className !== null) {
530 $dataMap = $this->dataMapper->getDataMap($className);
531 if ($dataMap->getRecordTypeColumnName() !== null) {
532 $recordTypes = array();
533 if ($dataMap->getRecordType() !== null) {
534 $recordTypes[] = $dataMap->getRecordType();
536 foreach ($dataMap->getSubclasses() as $subclassName) {
537 $subclassDataMap = $this->dataMapper->getDataMap($subclassName);
538 if ($subclassDataMap->getRecordType() !== null) {
539 $recordTypes[] = $subclassDataMap->getRecordType();
542 if (!empty($recordTypes)) {
543 $recordTypeStatements = array();
544 foreach ($recordTypes as $recordType) {
545 $tableName = $dataMap->getTableName();
546 $recordTypeStatements[] = $tableName .
'.' . $dataMap->getRecordTypeColumnName() .
'=' . $this->databaseHandle->fullQuoteStr($recordType, $tableName);
548 $sql[
'additionalWhereClause'][] =
'(' . implode(
' OR ', $recordTypeStatements) .
')';
566 $additionalWhereForMatchFields =
'';
568 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
569 if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
570 $additionalWhere = array();
571 foreach ($relationTableMatchFields as $fieldName => $value) {
572 $additionalWhere[] = $childTableAlias .
'.' . $fieldName .
' = ' . $this->databaseHandle->fullQuoteStr($value, $childTableName);
574 $additionalWhereForMatchFields .=
' AND ' . implode(
' AND ', $additionalWhere);
577 if (isset($parentTable)) {
578 $parentTableFieldName = $columnMap->getParentTableFieldName();
579 if (isset($parentTableFieldName) && $parentTableFieldName !==
'') {
580 $additionalWhereForMatchFields .=
' AND ' . $childTableAlias .
'.' . $parentTableFieldName .
' = ' . $this->databaseHandle->fullQuoteStr($parentTable, $childTableAlias);
584 return $additionalWhereForMatchFields;
597 $sysLanguageStatement =
'';
602 $pageIdStatement =
'';
607 if ($sysLanguageStatement !==
'' && $pageIdStatement !==
'') {
608 $whereClause = $sysLanguageStatement .
' AND ' . $pageIdStatement;
609 }
elseif ($sysLanguageStatement !==
'') {
610 $whereClause = $sysLanguageStatement;
612 $whereClause = $pageIdStatement;
629 if (is_array(
$GLOBALS[
'TCA'][$tableName][
'ctrl'])) {
633 if ($this->environmentService->isEnvironmentInFrontendMode()) {
639 if (!empty($statement)) {
641 $statement = strtolower(substr($statement, 1, 3)) ===
'and' ? substr($statement, 5) : $statement;
660 if ($ignoreEnableFields && !$includeDeleted) {
661 if (!empty($enableFieldsToBeIgnored)) {
663 $statement .= $this->
getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
667 }
elseif (!$ignoreEnableFields && !$includeDeleted) {
669 }
elseif (!$ignoreEnableFields && $includeDeleted) {
670 throw new InconsistentQuerySettingsException(
'Query setting "ignoreEnableFields=FALSE" can not be used together with "includeDeleted=TRUE" in frontend context.', 1327678173);
686 if (!$ignoreEnableFields) {
689 if (!$includeDeleted) {
705 $sysLanguageStatement =
'';
706 if (is_array(
$GLOBALS[
'TCA'][$tableName][
'ctrl'])) {
707 if (!empty(
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'])) {
709 $additionalWhereClause = $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
' IN (' . (int)$querySettings->getLanguageUid() .
',-1)';
712 if (isset(
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'transOrigPointerField'])
713 && $querySettings->getLanguageUid() > 0
715 $mode = $querySettings->getLanguageMode();
716 if ($mode ===
'strict') {
717 $additionalWhereClause = $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
'=-1' .
718 ' OR (' . $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
' = ' . (int)$querySettings->getLanguageUid() .
719 ' AND ' . $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'transOrigPointerField'] .
'=0' .
720 ') OR (' . $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
'=0' .
721 ' AND ' . $tableAlias .
'.uid IN (SELECT ' . $tableName .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'transOrigPointerField'] .
722 ' FROM ' . $tableName .
723 ' WHERE ' . $tableName .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'transOrigPointerField'] .
'>0' .
724 ' AND ' . $tableName .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
'=' . (int)$querySettings->getLanguageUid();
726 $additionalWhereClause .=
' OR (' . $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
'=0' .
727 ' AND ' . $tableAlias .
'.uid NOT IN (SELECT ' . $tableAlias .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'transOrigPointerField'] .
728 ' FROM ' . $tableName .
729 ' WHERE ' . $tableName .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'transOrigPointerField'] .
'>0' .
730 ' AND ' . $tableName .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'languageField'] .
'=' . (int)$querySettings->getLanguageUid();
734 if (isset(
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'])) {
735 $additionalWhereClause .=
' AND ' . $tableName .
'.' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] .
'=0';
737 $additionalWhereClause .=
'))';
739 $sysLanguageStatement =
'(' . $additionalWhereClause .
')';
742 return $sysLanguageStatement;
756 $pageIdStatement =
'';
757 $tableColumns = $this->tableColumnCache->get($tableName);
758 if ($tableColumns ===
false) {
759 $tableColumns = $this->databaseHandle->admin_get_fields($tableName);
760 $this->tableColumnCache->set($tableName, $tableColumns);
762 if (is_array(
$GLOBALS[
'TCA'][$tableName][
'ctrl']) && array_key_exists(
'pid', $tableColumns)) {
763 $rootLevel = (int)
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'rootLevel'];
764 switch ($rootLevel) {
767 return $tableAlias .
'.pid = 0';
770 if (empty($storagePageIds)) {
771 return $tableAlias .
'.pid = 0';
773 $storagePageIds[] = 0;
777 if (empty($storagePageIds)) {
785 $pageIdStatement = $tableAlias .
'.pid IN (' . implode(
',', $this->databaseHandle->cleanIntArray($storagePageIds)) .
')';
787 return $pageIdStatement;
799 $leftSource = $join->getLeft();
800 $leftClassName = $leftSource->getNodeTypeName();
801 $leftTableName = $leftSource->getSelectorName();
803 $rightSource = $join->getRight();
805 $left = $rightSource->getLeft();
806 $rightClassName = $left->getNodeTypeName();
807 $rightTableName = $left->getSelectorName();
809 $rightClassName = $rightSource->getNodeTypeName();
810 $rightTableName = $rightSource->getSelectorName();
811 $sql[
'fields'][$leftTableName] = $rightTableName .
'.*';
815 $sql[
'tables'][$leftTableName] = $leftTableName;
817 $sql[
'unions'][$rightTableName] =
'LEFT JOIN ' . $rightTableName;
818 $joinCondition = $join->getJoinCondition();
820 $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftClassName);
821 $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightClassName);
822 $sql[
'unions'][$rightTableName] .=
' ON ' . $leftTableName .
'.' . $column1Name .
' = ' . $rightTableName .
'.' . $column2Name;
824 if ($rightSource instanceof Qom\JoinInterface) {
838 protected function getUniqueAlias(array &$sql, $tableName, $fullPropertyPath = null)
840 if (isset($fullPropertyPath) && isset($this->tablePropertyMap[$fullPropertyPath])) {
841 return $this->tablePropertyMap[$fullPropertyPath];
846 while (isset($sql[
'tableAliasMap'][$alias])) {
847 $alias = $tableName . $i;
851 $sql[
'tableAliasMap'][$alias] = $tableName;
853 if (isset($fullPropertyPath)) {
854 $this->tablePropertyMap[$fullPropertyPath] = $alias;
873 protected function addUnionStatement(&$className, &$tableName, &$propertyPath, array &$sql, &$fullPropertyPath)
875 $explodedPropertyPath = explode(
'.', $propertyPath, 2);
876 $propertyName = $explodedPropertyPath[0];
877 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
878 $realTableName = $this->dataMapper->convertClassNameToTableName($className);
879 $tableName = isset($this->tablePropertyMap[$fullPropertyPath]) ? $this->tablePropertyMap[$fullPropertyPath] : $realTableName;
880 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
882 if ($columnMap === null) {
883 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException(
'The ColumnMap for property "' . $propertyName .
'" of class "' . $className .
'" is missing.', 1355142232);
886 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
887 $childTableName = $columnMap->getChildTableName();
889 if ($childTableName === null) {
890 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException(
'The relation information for property "' . $propertyName .
'" of class "' . $className .
'" is missing.', 1353170925);
893 $fullPropertyPath .= ($fullPropertyPath ===
'') ? $propertyName :
'.' . $propertyName;
894 $childTableAlias = $this->
getUniqueAlias($sql, $childTableName, $fullPropertyPath);
897 if (isset($sql[
'unions'][$childTableAlias])) {
898 $propertyPath = $explodedPropertyPath[1];
899 $tableName = $childTableAlias;
900 $className = $this->dataMapper->getType($className, $propertyName);
905 if (isset($parentKeyFieldName)) {
906 $sql[
'unions'][$childTableAlias] =
'LEFT JOIN ' . $childTableName .
' AS ' . $childTableAlias .
' ON ' . $tableName .
'.uid=' . $childTableAlias .
'.' . $parentKeyFieldName;
908 $sql[
'unions'][$childTableAlias] =
'LEFT JOIN ' . $childTableName .
' AS ' . $childTableAlias .
' ON ' . $tableName .
'.' . $columnName .
'=' . $childTableAlias .
'.uid';
912 if (isset($parentKeyFieldName)) {
913 $sql[
'unions'][$childTableAlias] =
'LEFT JOIN ' . $childTableName .
' AS ' . $childTableAlias .
' ON ' . $tableName .
'.uid=' . $childTableAlias .
'.' . $parentKeyFieldName;
915 $onStatement =
'(FIND_IN_SET(' . $childTableAlias .
'.uid, ' . $tableName .
'.' . $columnName .
'))';
916 $sql[
'unions'][$childTableAlias] =
'LEFT JOIN ' . $childTableName .
' AS ' . $childTableAlias .
' ON ' . $onStatement;
920 $relationTableName = $columnMap->getRelationTableName();
921 $relationTableAlias = $relationTableAlias = $this->
getUniqueAlias($sql, $relationTableName, $fullPropertyPath .
'_mm');
922 $sql[
'unions'][$relationTableAlias] =
'LEFT JOIN ' . $relationTableName .
' AS ' . $relationTableAlias .
' ON ' . $tableName .
'.uid=' . $relationTableAlias .
'.' . $columnMap->getParentKeyFieldName();
923 $sql[
'unions'][$childTableAlias] =
'LEFT JOIN ' . $childTableName .
' AS ' . $childTableAlias .
' ON ' . $relationTableAlias .
'.' . $columnMap->getChildKeyFieldName() .
'=' . $childTableAlias .
'.uid';
926 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception(
'Could not determine type of relation.', 1252502725);
929 $sql[
'keywords'][
'distinct'] =
'DISTINCT';
930 $propertyPath = $explodedPropertyPath[1];
931 $tableName = $childTableAlias;
932 $className = $this->dataMapper->getType($className, $propertyName);
946 if ($tableAlias !== $tableName) {
947 $statement = str_replace($tableName .
'.', $tableAlias .
'.', $statement);
976 $operator =
'IS NOT';
994 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception(
'Unsupported operator encountered.', 1242816073);
1004 if (!$this->pageRepository instanceof \TYPO3\CMS\Frontend\Page\PageRepository) {
1005 if ($this->environmentService->isEnvironmentInFrontendMode() && is_object(
$GLOBALS[
'TSFE'])) {
1006 $this->pageRepository =
$GLOBALS[
'TSFE']->sys_page;
1008 $this->pageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);