Skip to content

Commit

Permalink
Merge pull request #1510 from nextcloud/enh/1506/server-side-sorting
Browse files Browse the repository at this point in the history
Server-side sorting
  • Loading branch information
blizzz authored Feb 13, 2025
2 parents 4f66b93 + 6636b49 commit 49d3db8
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 43 deletions.
65 changes: 59 additions & 6 deletions lib/Db/Row2Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? []);
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 16 additions & 4 deletions lib/Db/RowSleeveMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
112 changes: 112 additions & 0 deletions tests/integration/features/APIv2.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1328,3 +1328,115 @@ Feature: APIv2
Then the reported status is "404"
And the user "participant1-v2" fetches table "t1", it has exactly these rows "r-not-updatable,r-not-deletable"
And the column "statement" of row "r-not-updatable" has the value "testing not updated"

@views
Scenario: have a view with a sort order
Given as user "participant1-v2"
And table "Table 1 via api v2" with emoji "👋" exists for user "participant1-v2" as "t1" via v2
And column "statement" exists with following properties
| type | text |
| subtype | line |
| mandatory | 1 |
| description | State your business |
And column "weight" exists with following properties
| type | number |
| mandatory | 1 |
| description | Importance of statement |
And column "code" exists with following properties
| type | text |
| subtype | line |
| mandatory | 1 |
| description | Shorthand of Statement |
And user "participant1-v2" create view "Important Statements" with emoji "⚡️" for "t1" as "v1"
And user "participant1-v2" sets columns "statement,weight,code" to view "v1"
And following sort order is applied to view "v1":
| weight | DESC |
And using "view" "v1"
And user "participant1-v2" creates row "r1" with following values:
| statement | Be yourself; everyone else is already taken. |
| weight | 75 |
| code | wil01 |
And user "participant1-v2" creates row "r2" with following values:
| statement | A room without books is like a body without a soul. |
| weight | 67 |
| code | cic01 |
And user "participant1-v2" creates row "r3" with following values:
| statement | To live is the rarest thing in the world. Most people exist, that is all. |
| weight | 92 |
| code | wil02 |
Then "view" "v1" has exactly these rows "r3,r1,r2" in exactly this order

@views
Scenario: have a view with a sort order from a meta column
Given as user "participant1-v2"
And table "Table 1 via api v2" with emoji "👋" exists for user "participant1-v2" as "t1" via v2
And column "statement" exists with following properties
| type | text |
| subtype | line |
| mandatory | 1 |
| description | State your business |
And column "weight" exists with following properties
| type | number |
| mandatory | 1 |
| description | Importance of statement |
And column "code" exists with following properties
| type | text |
| subtype | line |
| mandatory | 1 |
| description | Shorthand of Statement |
And user "participant1-v2" create view "Important Statements" with emoji "⚡️" for "t1" as "v1"
And user "participant1-v2" sets columns "statement,weight,code" to view "v1"
And following sort order is applied to view "v1":
| meta-id | DESC |
And using "view" "v1"
And user "participant1-v2" creates row "r1" with following values:
| statement | Be yourself; everyone else is already taken. |
| weight | 75 |
| code | wil01 |
And user "participant1-v2" creates row "r2" with following values:
| statement | A room without books is like a body without a soul. |
| weight | 67 |
| code | cic01 |
And user "participant1-v2" creates row "r3" with following values:
| statement | To live is the rarest thing in the world. Most people exist, that is all. |
| weight | 92 |
| code | wil02 |
Then "view" "v1" has exactly these rows "r3,r2,r1" in exactly this order

@views
Scenario: have a view with a multiple sort orders
Given as user "participant1-v2"
And table "Table 1 via api v2" with emoji "👋" exists for user "participant1-v2" as "t1" via v2
And column "statement" exists with following properties
| type | text |
| subtype | line |
| mandatory | 1 |
| description | State your business |
And column "weight" exists with following properties
| type | number |
| mandatory | 1 |
| description | Importance of statement |
And column "code" exists with following properties
| type | text |
| subtype | line |
| mandatory | 1 |
| description | Shorthand of Statement |
And user "participant1-v2" create view "Important Statements" with emoji "⚡️" for "t1" as "v1"
And user "participant1-v2" sets columns "statement,weight,code" to view "v1"
And following sort order is applied to view "v1":
| weight | DESC |
| code | ASC |
And using "view" "v1"
And user "participant1-v2" creates row "r1" with following values:
| statement | Be yourself; everyone else is already taken. |
| weight | 92 |
| code | wil01 |
And user "participant1-v2" creates row "r2" with following values:
| statement | A room without books is like a body without a soul. |
| weight | 67 |
| code | cic01 |
And user "participant1-v2" creates row "r3" with following values:
| statement | To live is the rarest thing in the world. Most people exist, that is all. |
| weight | 92 |
| code | wil02 |
Then "view" "v1" has exactly these rows "r1,r3,r2" in exactly this order
Loading

0 comments on commit 49d3db8

Please sign in to comment.