connection = $connection; $this->db = $connection->getDbAdapter(); } /** * @param UuidInterface $uuid * @return ?Branch */ public function fetchBranchByUuid(UuidInterface $uuid) { return $this->newFromDbResult( $this->select()->where('b.uuid = ?', $this->connection->quoteBinary($uuid->getBytes())) ); } /** * @param string $name * @return ?Branch */ public function fetchBranchByName($name) { return $this->newFromDbResult($this->select()->where('b.branch_name = ?', $name)); } public function cloneBranchForSync(Branch $branch, $newName, $owner) { $this->runTransaction(function ($db) use ($branch, $newName, $owner) { $tables = BranchSupport::BRANCHED_TABLES; $tables[] = self::TABLE_ACTIVITY; $newBranch = $this->createBranchByName($newName, $owner); $oldQuotedUuid = DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db); $quotedUuid = DbUtil::quoteBinaryCompat($newBranch->getUuid()->getBytes(), $db); // $timestampNs = (int)floor(microtime(true) * 1000000); // Hint: would love to do SELECT *, $quotedUuid AS branch_uuid FROM $table INTO $table foreach ($tables as $table) { $rows = $db->fetchAll($db->select()->from($table)->where('branch_uuid = ?', $oldQuotedUuid)); foreach ($rows as $row) { $modified = (array)$row; $modified['branch_uuid'] = $quotedUuid; if ($table === self::TABLE_ACTIVITY) { $modified['timestamp_ns'] = round($modified['timestamp_ns'] / 1000000); } $db->insert($table, $modified); } } }); return $this->fetchBranchByName($newName); } protected function runTransaction($callback) { $db = $this->db; $db->beginTransaction(); try { $callback($db); $db->commit(); } catch (\Exception $e) { try { $db->rollBack(); } catch (\Exception $ignored) { // } throw $e; } } public function wipeBranch(Branch $branch, $after = null) { $this->runTransaction(function ($db) use ($branch, $after) { $tables = BranchSupport::BRANCHED_TABLES; $tables[] = self::TABLE_ACTIVITY; $quotedUuid = DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db); $where = $db->quoteInto('branch_uuid = ?', $quotedUuid); foreach ($tables as $table) { if ($after && $table === self::TABLE_ACTIVITY) { $db->delete($table, $where . ' AND timestamp_ns > ' . (int) $after); } else { $db->delete($table, $where); } } }); } protected function newFromDbResult($query) { if ($row = $this->db->fetchRow($query)) { if (is_resource($row->uuid)) { $row->uuid = stream_get_contents($row->uuid); } return Branch::fromDbRow($row); } return null; } public function setReadyForMerge(Branch $branch) { $update = [ 'ts_merge_request' => (int) floor(microtime(true) * 1000000), 'description' => $branch->getDescription(), ]; $name = $branch->getName(); if (preg_match('#^/enforced/(.+)$#', $name, $match)) { $update['branch_name'] = '/merge/' . substr(sha1($branch->getUuid()->getBytes()), 0, 7) . '/' . $match[1]; } $this->db->update('director_branch', $update, $this->db->quoteInto( 'uuid = ?', $this->connection->quoteBinary($branch->getUuid()->getBytes()) )); } protected function select() { return $this->db->select()->from(['b' => 'director_branch'], [ 'uuid' => 'b.uuid', 'owner' => 'b.owner', 'branch_name' => 'b.branch_name', 'description' => 'b.description', 'ts_merge_request' => 'b.ts_merge_request', 'cnt_activities' => 'COUNT(ba.timestamp_ns)', ])->joinLeft( ['ba' => self::TABLE_ACTIVITY], 'b.uuid = ba.branch_uuid', [] )->group('b.uuid'); } /** * @param string $name * @return Branch * @throws \Zend_Db_Adapter_Exception */ public function fetchOrCreateByName($name, $owner) { if ($branch = $this->fetchBranchByName($name)) { return $branch; } return $this->createBranchByName($name, $owner); } /** * @param string $branchName * @param string $owner * @return Branch * @throws \Zend_Db_Adapter_Exception */ public function createBranchByName($branchName, $owner) { $uuid = Uuid::uuid4(); $properties = [ 'uuid' => $this->connection->quoteBinary($uuid->getBytes()), 'branch_name' => $branchName, 'owner' => $owner, 'description' => null, 'ts_merge_request' => null, ]; $this->db->insert(self::TABLE, $properties); if ($branch = static::fetchBranchByUuid($uuid)) { return $branch; } throw new \RuntimeException(sprintf( 'Branch with UUID=%s has been created, but could not be fetched from DB', $uuid->toString() )); } public function deleteByUuid(UuidInterface $uuid) { return $this->db->delete(self::TABLE, $this->db->quoteInto( 'uuid = ?', $this->connection->quoteBinary($uuid->getBytes()) )); } /** * @param string $name * @return int */ public function deleteByName($name) { return $this->db->delete(self::TABLE, $this->db->quoteInto( 'branch_name = ?', $name )); } public function delete(Branch $branch) { return $this->deleteByUuid($branch->getUuid()); } /** * @param Branch $branch * @param ?int $after * @return float|null */ public function getLastActivityTime(Branch $branch, $after = null) { $db = $this->db; $query = $db->select() ->from(self::TABLE_ACTIVITY, 'MAX(timestamp_ns)') ->where('branch_uuid = ?', DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db)); if ($after) { $query->where('timestamp_ns > ?', (int) $after); } $last = $db->fetchOne($query); if ($last) { return $last / 1000000; } return null; } }