@@ -913,23 +913,29 @@ public function insert(Entity $entity): Bookmark {
913913 $ entityToInsert ->setLastPreview (0 );
914914 $ entityToInsert ->setClickcount (0 );
915915
916+ // Wrap the insert in a (possibly nested) transaction so that a failed
917+ // statement – e.g. a unique constraint violation when the URL already
918+ // exists – can be rolled back cleanly. When this runs inside a larger
919+ // transaction (e.g. the HTML importer) Nextcloud uses a SAVEPOINT, so
920+ // rolling back discards only the failed INSERT instead of poisoning or
921+ // committing the outer transaction. This keeps the connection usable for
922+ // the insertOrUpdate() fallback below on SQLite, MySQL and Postgres alike.
923+ $ this ->db ->beginTransaction ();
916924 try {
917925 parent ::insert ($ entityToInsert );
918- if (isset ($ this ->userBookmarkCount [$ entityToInsert ->getUserId ()])) {
919- $ this ->userBookmarkCount [$ entityToInsert ->getUserId ()] += 1 ;
920- }
921- $ this ->eventDispatcher ->dispatchTyped (new InsertEvent ('bookmark ' , $ entityToInsert ->getId ()));
922- return $ entityToInsert ;
926+ $ this ->db ->commit ();
923927 } catch (Exception $ e ) {
928+ $ this ->db ->rollBack ();
924929 if ($ e ->getReason () === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION ) {
925- if ($ this ->db ->inTransaction ()) {
926- $ this ->db ->commit ();
927- $ this ->db ->beginTransaction ();
928- }
929930 throw new AlreadyExistsError ('A bookmark with this URL already exists ' );
930931 }
931932 throw $ e ;
932933 }
934+ if (isset ($ this ->userBookmarkCount [$ entityToInsert ->getUserId ()])) {
935+ $ this ->userBookmarkCount [$ entityToInsert ->getUserId ()] += 1 ;
936+ }
937+ $ this ->eventDispatcher ->dispatchTyped (new InsertEvent ('bookmark ' , $ entityToInsert ->getId ()));
938+ return $ entityToInsert ;
933939 }
934940
935941 /**
0 commit comments