Skip to content

Conversation

@provokateurin
Copy link
Member

A unique index must be used to actually guarantee no duplicates, as this method is not even transaction safe (with READ_COMMITTED)

@provokateurin provokateurin added this to the Nextcloud 33 milestone Dec 16, 2025
@provokateurin provokateurin requested a review from a team as a code owner December 16, 2025 11:35
@provokateurin provokateurin requested review from Altahrim, CarlSchwan, icewind1991 and leftybournes and removed request for a team December 16, 2025 11:35
This was referenced Jan 7, 2026
@nextcloud-bot nextcloud-bot mentioned this pull request Jan 14, 2026
@nextcloud-bot nextcloud-bot mentioned this pull request Jan 20, 2026
@provokateurin provokateurin force-pushed the refactor/insert-if-not-exists branch from d9b3fde to d161459 Compare January 20, 2026 11:02
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
@provokateurin provokateurin force-pushed the refactor/insert-if-not-exists branch from d161459 to 7eb9092 Compare January 20, 2026 11:09
Comment on lines -42 to +52
$this->db->insertIfNotExist('*PREFIX*dav_shares', [
'principaluri' => 'principal:unknown',
'type' => 'calendar',
'access' => 2,
'resourceid' => 666,
]);
$this->db->insertIfNotExist('*PREFIX*dav_shares', [
'principaluri' => 'principals/remote-users/foobar',
'type' => 'calendar',
'access' => 2,
'resourceid' => 666,
]);
$qb = $this->db->getQueryBuilder();
$qb->insert('dav_shares');

foreach (['principal:unknown', 'principals/remote-users/foobar'] as $principaluri) {
$qb->values([
'principaluri' => $qb->createNamedParameter($principaluri),
'type' => $qb->createNamedParameter('calendar'),
'access' => $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT),
'resourceid' => $qb->createNamedParameter(666, IQueryBuilder::PARAM_INT),
]);
Copy link
Member Author

@provokateurin provokateurin Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous insertIfNotExists compared all input fields, while the unique index is create unique index dav_shares_index on oc_dav_shares (principaluri, resourceid, type, publicuri);. This seems to result in different numbers of rows, but only on sharding for some reason. I can't fully judge this, but I think the tests are incorrect in some way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't really wrap my head around the unique index vs the semantics of the insertIfNotExists. But the index definitely is not helping getting an atomic operation here.

How does the logic change with sharding?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the index definitely is not helping getting an atomic operation here.

How so? Before it was a SELECT + INSERT which was not atomic at all, leading to the duplicated oc_mounts issue. Now its UNIQUE INDEX + INSERT and this definitely is atomic.

How does the logic change with sharding?

This is what confuses me the most, it's not failing without sharding. I feel like this could be a bug in the sharding code? CC @icewind1991

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The index will only ensure that the (principaluri, resourceid, type, null) is unique. It doesn't cover the access column. So if you have an existing row (principaluri, resourceid, type, anything but null) the insert still happens without a unique constraint violation.

The old insertIfNotExist was not guaranteed to be atomic, but in the case of no concurrent modifications of the table (like in the sequentially executed phpunit tests) it still prevented duplicates with identical (principaluri, resourceid, type, access).

But I think we are on the same page anyway :)

The test outcome makes no sense. I also think that the sharding somehow doesn't send the deletions in the command to the same db instance as the select for the insertion.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants