diff --git a/lib/Db/Row2Mapper.php b/lib/Db/Row2Mapper.php index 2cabb62a3..fb83ac54c 100644 --- a/lib/Db/Row2Mapper.php +++ b/lib/Db/Row2Mapper.php @@ -175,9 +175,7 @@ public function findAll(array $tableColumns, array $columns, int $tableId, ?int $wantedRowIdsArray = $this->getWantedRowIds($userId, $tableId, $filter, $limit, $offset); - // TODO add sorting - - return $this->getRows($wantedRowIdsArray, $columnIdsArray); + return $this->getRows($wantedRowIdsArray, $columnIdsArray, $sort ?? []); } /** @@ -186,7 +184,7 @@ public function findAll(array $tableColumns, array $columns, int $tableId, ?int * @return Row2[] * @throws InternalError */ - private function getRows(array $rowIds, array $columnIds): array { + private function getRows(array $rowIds, array $columnIds, array $sort = []): array { $qb = $this->db->getQueryBuilder(); $qbSqlForColumnTypes = null; @@ -224,14 +222,16 @@ private function getRows(array $rowIds, array $columnIds): array { ->innerJoin('t1', 'tables_row_sleeves', 'rs', 'rs.id = t1.row_id'); try { - $result = $this->db->executeQuery($qb->getSQL(), $qb->getParameters(), $qb->getParameterTypes()); + $result = $qb->executeQuery(); } catch (Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage(), ); } try { - $sleeves = $this->rowSleeveMapper->findMultiple($rowIds); + $sleeves = $this->rowSleeveMapper->findMultiple($rowIds, function (IQueryBuilder $qb, string $sleevesAlias) use ($sort) { + $this->addSortQueryForMultipleSleeveFinder($qb, $sleevesAlias, $sort); + }); } catch (Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); @@ -263,6 +263,59 @@ private function addFilterToQuery(IQueryBuilder &$qb, array $filters, string $us } } + /** + * This method is passed to RowSleeveMapper::findMultiple() when the rows need sorting. The RowSleeveMapper does not have + * knowledge about the column information, as they reside in this class, and the mapper is called from here. + * + * @throws InternalError + */ + private function addSortQueryForMultipleSleeveFinder(IQueryBuilder $qb, string $sleevesAlias, array $sort): void { + $i = 1; + foreach (array_reverse($sort) as $sortData) { + if (!isset($this->columns[$sortData['columnId']]) && !isset($this->allColumns[$sortData['columnId']]) && $sortData['columnId'] > 0) { + throw new InternalError('No column found to build filter with for id ' . $sortData['columnId']); + } + + // if is normal column + if ($sortData['columnId'] >= 0) { + $column = $this->columns[$sortData['columnId']] ?? $this->allColumns[$sortData['columnId']]; + $valueTable = 'tables_row_cells_' . $column->getType(); + $alias = 'sort' . $i; + $qb->leftJoin($sleevesAlias, $valueTable, $alias, + $qb->expr()->andX( + $qb->expr()->eq($sleevesAlias . '.id', $alias . '.row_id'), + $qb->expr()->eq($alias . '.column_id', $qb->createNamedParameter($sortData['columnId'])) + ) + ); + $qb->orderBy($alias . '.value', $sortData['mode']); + } elseif (Column::isValidMetaTypeId($sortData['columnId'])) { + $fieldName = match ($sortData['columnId']) { + Column::TYPE_META_ID => 'id', + Column::TYPE_META_CREATED_BY => 'created_by', + Column::TYPE_META_CREATED_AT => 'created_at', + Column::TYPE_META_UPDATED_BY => 'updated_by', + Column::TYPE_META_UPDATED_AT => 'updated_at', + default => null, + }; + + if ($fieldName === null) { + // Can happen, when– + // … a new meta column was introduced, but not considered here + // … a meta column was removed and existing sort rules are not being adapted + // those case are being ignored, but would require developer attention + $this->logger->error('No meta column (ID: {columnId}) found for sorting id', [ + 'columnId' => $sortData['columnId'], + ]); + continue; + } + + $qb->orderBy($sleevesAlias . '.' . $fieldName, $sortData['mode']); + } + $i++; + } + } + + private function replacePlaceholderValues(array &$filters, string $userId): void { foreach ($filters as &$filterGroup) { foreach ($filterGroup as &$filter) { diff --git a/lib/Db/RowSleeveMapper.php b/lib/Db/RowSleeveMapper.php index a61f7531f..e590baf8d 100644 --- a/lib/Db/RowSleeveMapper.php +++ b/lib/Db/RowSleeveMapper.php @@ -40,14 +40,26 @@ public function find(int $id): RowSleeve { /** * @param int[] $ids + * @param ?callable(IQueryBuilder, string): void $sorterCallback * @return RowSleeve[] * @throws Exception */ - public function findMultiple(array $ids): array { + public function findMultiple(array $ids, ?callable $sorterCallback = null): array { + $sleeveAlias = 'sleeves'; $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from($this->table) - ->where($qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); + $qb->select( + $sleeveAlias . '.id', + $sleeveAlias . '.table_id', + $sleeveAlias . '.created_by', + $sleeveAlias . '.created_at', + $sleeveAlias . '.last_edit_by', + $sleeveAlias . '.last_edit_at', + ) + ->from($this->table, $sleeveAlias) + ->where($qb->expr()->in($sleeveAlias . '.id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); + if ($sorterCallback !== null) { + $sorterCallback($qb, $sleeveAlias); + } return $this->findEntities($qb); }