Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server-side sorting #1510

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading