Skip to content

Room search filters seating capacity as a string, dropping rooms that fit #64413

Description

@Rikdekker

Summary

searchPrincipalsByCapacity() compares the requested seating capacity as a string, because the column it filters on is a varchar. Rooms are therefore filtered by lexicographic order rather than by number, and rooms that easily fit the meeting are dropped from the result.

The most visible consequence is in Calendar's room suggestions, which pass the attendee count as the capacity filter: a room seating 100 disappears from the suggestions for a 2-person meeting, while a room seating 4 is kept.

Steps to reproduce

  1. Create several bookable rooms with seating capacities that differ in digit count, e.g. 4, 6, 8, 10, 12, 16, 20, 100.
  2. In Calendar, create an event and add 2 attendees.
  3. Open the room suggestions.

Expected behaviour

Every room seating 2 or more is a candidate — all eight rooms.

Actual behaviour

Only 4, 6, 8, 20, 25, 30 survive the filter. The rooms seating 10, 12, 16 and 100 are dropped, because "10" < "2" as a string.

Worked out over a realistic set:

Attendees Rooms passing the filter
2 4, 6, 8, 20, 25, 30
3 4, 6, 8, 30
4 4, 6, 8
5 6, 8

The pattern is counter-intuitive in both directions: adding attendees can make more rooms appear, and the largest rooms vanish first.

Cause

https://github.com/nextcloud/server/blob/master/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php#L354-L358

private function searchPrincipalsByCapacity(string $key, string $value, array $usersGroups = []): array {
    $query = $this->getMetadataQuery($key);
    $query->andWhere($query->expr()->gte('value', $query->createNamedParameter($value)));
    return $this->getRows($query, $usersGroups);
}

getMetadataQuery() selects from the metadata table (calendar_rooms_md / calendar_resources_md), whose value column is a string type shared by every metadata key — building address, room type, features and seating capacity all live in it. gte on that column is a string comparison in every supported database.

This is not a caller problem: {http://nextcloud.com/ns}room-seating-capacity is a DAV property and arrives as a string by definition, so a backend has nothing else to hand over.

Suggested fix

Cast on the comparison for this one key, e.g. CAST(value AS INTEGER) (or the DBAL equivalent that works across sqlite/mysql/postgres), guarded so it only applies to the capacity key — the same column holds non-numeric values for other keys.

A cast on a varchar is not index-friendly. If that matters, a dedicated numeric column for capacity avoids both the cast and the type confusion.

Note on test coverage

apps/dav/tests/unit/CalDAV/ResourceBooking/ has no test exercising the capacity filter, which is likely why this has gone unnoticed. Any fix is worth pairing with a case that mixes single- and multi-digit capacities — a same-digit-length fixture passes either way.

Server configuration

Nextcloud version: reproduced against 34.0.4; the same code is present on master
Database: PostgreSQL (the comparison is string-based on all supported databases)

Found while investigating a downstream report against a third-party room backend (RoomVox#43), but the behaviour is independent of which backend supplies the rooms.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions