1717use CodeIgniter \Database \Exceptions \DatabaseException ;
1818use CodeIgniter \Database \Exceptions \DataException ;
1919use CodeIgniter \Database \OCI8 \Builder as OCI8Builder ;
20+ use CodeIgniter \Database \Postgre \Builder as PostgreBuilder ;
2021use CodeIgniter \Database \RawSql ;
2122use CodeIgniter \Database \SQLite3 \Builder as SQLite3Builder ;
2223use CodeIgniter \Database \SQLSRV \Builder as SQLSRVBuilder ;
@@ -417,6 +418,28 @@ public function testLockForUpdateResetsWithSelect(): void
417418 $ this ->assertSame ('SELECT * FROM "users" ' , str_replace ("\n" , ' ' , $ builder ->getCompiledSelect ()));
418419 }
419420
421+ public function testLockForUpdateThrowsExceptionWithUnion (): void
422+ {
423+ $ builder = new BaseBuilder ('users ' , $ this ->db );
424+
425+ $ this ->expectException (DatabaseException::class);
426+ $ this ->expectExceptionMessage ('Query Builder does not support lockForUpdate() with union() or unionAll(). ' );
427+
428+ $ builder ->union (new BaseBuilder ('jobs ' , $ this ->db ))->lockForUpdate ()->getCompiledSelect ();
429+ }
430+
431+ public function testLockForUpdateThrowsExceptionWithSQLSRVUnion (): void
432+ {
433+ $ this ->db = new MockConnection (['DBDriver ' => 'SQLSRV ' , 'database ' => 'test ' , 'schema ' => 'dbo ' ]);
434+
435+ $ builder = new SQLSRVBuilder ('users ' , $ this ->db );
436+
437+ $ this ->expectException (DatabaseException::class);
438+ $ this ->expectExceptionMessage ('Query Builder does not support lockForUpdate() with union() or unionAll(). ' );
439+
440+ $ builder ->union (new SQLSRVBuilder ('jobs ' , $ this ->db ))->lockForUpdate ()->getCompiledSelect ();
441+ }
442+
420443 public function testLockForUpdateWithOCI8 (): void
421444 {
422445 $ builder = new OCI8Builder ('users ' , $ this ->db );
@@ -436,6 +459,66 @@ public function testLockForUpdateThrowsExceptionWithOCI8Limit(): void
436459 $ builder ->limit (1 )->lockForUpdate ()->getCompiledSelect ();
437460 }
438461
462+ #[DataProvider('provideLockForUpdateUnsupportedSelectClauses ' )]
463+ public function testLockForUpdateThrowsExceptionWithOCI8SelectClause (string $ clause ): void
464+ {
465+ $ builder = new OCI8Builder ('users ' , $ this ->db );
466+
467+ $ this ->expectException (DatabaseException::class);
468+ $ this ->expectExceptionMessage ('OCI8 does not support lockForUpdate() with distinct(), groupBy(), having(), or aggregate helper selections. ' );
469+
470+ $ this ->applyLockForUpdateUnsupportedClause ($ builder , $ clause )
471+ ->lockForUpdate ()
472+ ->getCompiledSelect ();
473+ }
474+
475+ public function testLockForUpdateWithPostgre (): void
476+ {
477+ $ builder = new PostgreBuilder ('users ' , $ this ->db );
478+
479+ $ expected = 'SELECT * FROM "users" FOR UPDATE ' ;
480+
481+ $ this ->assertSame ($ expected , str_replace ("\n" , ' ' , $ builder ->lockForUpdate ()->getCompiledSelect ()));
482+ }
483+
484+ #[DataProvider('provideLockForUpdateUnsupportedSelectClauses ' )]
485+ public function testLockForUpdateThrowsExceptionWithPostgreSelectClause (string $ clause ): void
486+ {
487+ $ builder = new PostgreBuilder ('users ' , $ this ->db );
488+
489+ $ this ->expectException (DatabaseException::class);
490+ $ this ->expectExceptionMessage ('Postgre does not support lockForUpdate() with distinct(), groupBy(), having(), or aggregate helper selections. ' );
491+
492+ $ this ->applyLockForUpdateUnsupportedClause ($ builder , $ clause )
493+ ->lockForUpdate ()
494+ ->getCompiledSelect ();
495+ }
496+
497+ /**
498+ * @return iterable<string, list<string>>
499+ */
500+ public static function provideLockForUpdateUnsupportedSelectClauses (): iterable
501+ {
502+ yield 'distinct ' => ['distinct ' ];
503+
504+ yield 'groupBy ' => ['groupBy ' ];
505+
506+ yield 'having ' => ['having ' ];
507+
508+ yield 'aggregate selection ' => ['aggregate ' ];
509+ }
510+
511+ private function applyLockForUpdateUnsupportedClause (BaseBuilder $ builder , string $ clause ): BaseBuilder
512+ {
513+ return match ($ clause ) {
514+ 'distinct ' => $ builder ->distinct (),
515+ 'groupBy ' => $ builder ->groupBy ('role ' ),
516+ 'having ' => $ builder ->having ('COUNT(id) > ' , 1 , false ),
517+ 'aggregate ' => $ builder ->selectCount ('id ' ),
518+ default => throw new DatabaseException ('Unsupported clause: ' . $ clause ),
519+ };
520+ }
521+
439522 public function testLockForUpdateThrowsExceptionOnSQLite3 (): void
440523 {
441524 $ builder = new SQLite3Builder ('users ' , $ this ->db );
0 commit comments