systemConfig = $this->createMock(SystemConfig::class); $this->logger = $this->createMock(LoggerInterface::class); } /** * Build a QueryBuilder backed by a non-SQLite (MySQL 8) platform so the * generated SQL exposes the locking clause the way it would in production. */ private function newMysqlQueryBuilder(): QueryBuilder { $inner = $this->createMock(Connection::class); $inner->method('getDatabasePlatform')->willReturn(new MySQL80Platform()); $adapter = $this->createMock(ConnectionAdapter::class); $adapter->method('getInner')->willReturn($inner); $adapter->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_MYSQL); return new QueryBuilder($adapter, $this->systemConfig, $this->logger); } public function testClaimQueryContainsForUpdateSkipLocked(): void { $qb = $this->newMysqlQueryBuilder(); $qb->select('id', 'status', 'type', 'last_updated') ->from('taskprocessing_tasks') ->where($qb->expr()->eq('status', $qb->createPositionalParameter(1, IQueryBuilder::PARAM_INT))) ->orderBy('last_updated', 'ASC') ->setMaxResults(1) ->forUpdate(ConflictResolutionMode::SkipLocked); $sql = $qb->getSQL(); self::assertStringContainsString('FOR UPDATE', $sql); self::assertStringContainsString('SKIP LOCKED', $sql); } public function testOrdinaryForUpdateHasNoSkipLocked(): void { // Sanity check: only the SkipLocked mode adds the SKIP LOCKED clause. $qb = $this->newMysqlQueryBuilder(); $qb->select('id') ->from('taskprocessing_tasks') ->setMaxResults(1) ->forUpdate(ConflictResolutionMode::Ordinary); $sql = $qb->getSQL(); self::assertStringContainsString('FOR UPDATE', $sql); self::assertStringNotContainsString('SKIP LOCKED', $sql); } }