From d9700dc2dacf3c68ba47a05e5197fad3ddcc8de3 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 27 Sep 2019 07:26:20 +0200 Subject: [PATCH 1/4] new sql structure + write requests Signed-off-by: Maxence Lange --- lib/Db/StreamActionsRequest.php | 22 ++- .../Version0002Date20190925000001.php | 167 ++++++++++++++++++ lib/Model/StreamAction.php | 5 + lib/Service/BoostService.php | 6 +- lib/Service/LikeService.php | 8 +- 5 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 lib/Migration/Version0002Date20190925000001.php diff --git a/lib/Db/StreamActionsRequest.php b/lib/Db/StreamActionsRequest.php index b1e4d84a..5242b98d 100644 --- a/lib/Db/StreamActionsRequest.php +++ b/lib/Db/StreamActionsRequest.php @@ -50,14 +50,23 @@ class StreamActionsRequest extends StreamActionsRequestBuilder { */ public function create(StreamAction $action) { $qb = $this->getStreamActionInsertSql(); + + $values = $action->getValues(); $qb->setValue('actor_id', $qb->createNamedParameter($action->getActorId())) ->setValue('actor_id_prim', $qb->createNamedParameter($this->prim($action->getActorId()))) ->setValue('stream_id', $qb->createNamedParameter($action->getStreamId())) ->setValue('stream_id_prim', $qb->createNamedParameter($this->prim($action->getStreamId()))) ->setValue( 'values', $qb->createNamedParameter( - json_encode($action->getValues(), JSON_UNESCAPED_SLASHES) + json_encode($values, JSON_UNESCAPED_SLASHES) ) + ) + ->setValue('liked', $qb->createNamedParameter($this->getBool(StreamAction::LIKED, $values, false))) + ->setValue( + 'boosted', $qb->createNamedParameter($this->getBool(StreamAction::BOOSTED, $values, false)) + ) + ->setValue( + 'replied', $qb->createNamedParameter($this->getBool(StreamAction::REPLIED, $values, false)) ); $qb->execute(); @@ -74,13 +83,18 @@ class StreamActionsRequest extends StreamActionsRequestBuilder { public function update(StreamAction $action): int { $qb = $this->getStreamActionUpdateSql(); - $values = json_encode($action->getValues(), JSON_UNESCAPED_SLASHES); - $qb->set('values', $qb->createNamedParameter($values)); + $values = $action->getValues(); + $qb->set('values', $qb->createNamedParameter(json_encode($values, JSON_UNESCAPED_SLASHES))) + ->set('liked', $qb->createNamedParameter($this->getBool(StreamAction::LIKED, $values, false))) + ->set('boosted', $qb->createNamedParameter($this->getBool(StreamAction::BOOSTED, $values, false))) + ->set('replied', $qb->createNamedParameter($this->getBool(StreamAction::REPLIED, $values, false))); $this->limitToActorId($qb, $action->getActorId()); $this->limitToStreamId($qb, $action->getStreamId()); - return $qb->execute(); + $count = $qb->execute(); + + return $count; } diff --git a/lib/Migration/Version0002Date20190925000001.php b/lib/Migration/Version0002Date20190925000001.php new file mode 100644 index 00000000..606bf810 --- /dev/null +++ b/lib/Migration/Version0002Date20190925000001.php @@ -0,0 +1,167 @@ + + * @copyright 2018, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCA\Social\Migration; + + +use Closure; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use Doctrine\DBAL\Schema\SchemaException; +use Exception; +use OCP\DB\ISchemaWrapper; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + + +/** + * Class Version0002Date20190925000001 + * + * @package OCA\Social\Migration + */ +class Version0002Date20190925000001 extends SimpleMigrationStep { + + + /** @var IDBConnection */ + private $connection; + + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * + * @return ISchemaWrapper + * @throws SchemaException + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options + ): ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('social_a2_stream_action'); + if (!$table->hasColumn('liked')) { + $table->addColumn('liked', 'boolean'); + } + if (!$table->hasColumn('boosted')) { + $table->addColumn('boosted', 'boolean'); + } + if (!$table->hasColumn('replied')) { + $table->addColumn('replied', 'boolean'); + } + + return $schema; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * + * @throws Exception + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $this->fillTableStreamActions($schema); + } + + /** + * @param ISchemaWrapper $schema + */ + private function fillTableStreamActions(ISchemaWrapper $schema) { + + $start = 0; + $limit = 1000; + while (true) { + $qb = $this->connection->getQueryBuilder(); + $qb->select('id', 'actor_id', 'stream_id', 'values') + ->from('social_a2_stream_action') + ->setMaxResults(1000) + ->setFirstResult($start); + + $cursor = $qb->execute(); + $count = 0; + while ($data = $cursor->fetch()) { + $count++; + + $this->updateStreamActions($data); + } + $cursor->closeCursor(); + + $start += $count; + if ($count < $limit) { + break; + } + } + } + + + /** + * @param array $data + */ + private function updateStreamActions(array $data) { + $update = $this->connection->getQueryBuilder(); + $update->update('social_a2_stream_action'); + + $id = $data['id']; + $actorId = $data['actor_id']; + $streamId = $data['stream_id']; + + $values = json_decode($data['values'], true); + $liked = (int)((array_key_exists('liked', $values)) ? $values['liked'] : 0); + $boosted = (int)((array_key_exists('boosted', $values)) ? $values['boosted'] : 0); + $replied = (int)((array_key_exists('replied', $values)) ? $values['replied'] : 0); + + $update->set('actor_id_prim', $update->createNamedParameter(hash('sha512', $actorId))); + $update->set('stream_id_prim', $update->createNamedParameter(hash('sha512', $streamId))); + $update->set('liked', $update->createNamedParameter($liked)); + $update->set('boosted', $update->createNamedParameter($boosted)); + $update->setValue('replied', $update->createNamedParameter($replied)); + + $expr = $update->expr(); + $update->where($expr->eq('id', $update->createNamedParameter($id))); + try { + $update->execute(); + } catch (UniqueConstraintViolationException $e) { + } + } + +} diff --git a/lib/Model/StreamAction.php b/lib/Model/StreamAction.php index 4235960d..798e1d86 100644 --- a/lib/Model/StreamAction.php +++ b/lib/Model/StreamAction.php @@ -48,6 +48,11 @@ class StreamAction implements JsonSerializable { use TStringTools; + const LIKED = 'liked'; + const BOOSTED = 'boosted'; + const REPLIED = 'replied'; + + /** @var integer */ private $id = 0; diff --git a/lib/Service/BoostService.php b/lib/Service/BoostService.php index b865266e..499d2c50 100644 --- a/lib/Service/BoostService.php +++ b/lib/Service/BoostService.php @@ -34,7 +34,6 @@ use daita\MySmallPhpTools\Traits\TStringTools; use Exception; use OCA\Social\AP; use OCA\Social\Db\StreamRequest; -use OCA\Social\Exceptions\ItemNotFoundException; use OCA\Social\Exceptions\ItemUnknownException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\StreamNotFoundException; @@ -44,6 +43,7 @@ use OCA\Social\Model\ActivityPub\Actor\Person; use OCA\Social\Model\ActivityPub\Object\Announce; use OCA\Social\Model\ActivityPub\Object\Note; use OCA\Social\Model\ActivityPub\Stream; +use OCA\Social\Model\StreamAction; /** @@ -143,7 +143,7 @@ class BoostService { $interface->save($announce); - $this->streamActionService->setActionBool($actor->getId(), $postId, 'boosted', true); + $this->streamActionService->setActionBool($actor->getId(), $postId, StreamAction::BOOSTED, true); $this->signatureService->signObject($actor, $announce); $token = $this->activityService->request($announce); @@ -205,7 +205,7 @@ class BoostService { } catch (StreamNotFoundException $e) { } - $this->streamActionService->setActionBool($actor->getId(), $postId, 'boosted', false); + $this->streamActionService->setActionBool($actor->getId(), $postId, StreamAction::BOOSTED, false); return $undo; } diff --git a/lib/Service/LikeService.php b/lib/Service/LikeService.php index 67600eb4..11f89b4b 100644 --- a/lib/Service/LikeService.php +++ b/lib/Service/LikeService.php @@ -40,13 +40,13 @@ use OCA\Social\Exceptions\ItemUnknownException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\StreamNotFoundException; use OCA\Social\Model\ActivityPub\ACore; -use OCA\Social\Model\ActivityPub\Object\Like; use OCA\Social\Model\ActivityPub\Activity\Undo; use OCA\Social\Model\ActivityPub\Actor\Person; -use OCA\Social\Model\ActivityPub\Object\Announce; +use OCA\Social\Model\ActivityPub\Object\Like; use OCA\Social\Model\ActivityPub\Object\Note; use OCA\Social\Model\ActivityPub\Stream; use OCA\Social\Model\InstancePath; +use OCA\Social\Model\StreamAction; /** @@ -139,7 +139,7 @@ class LikeService { $interface = AP::$activityPub->getInterfaceFromType(Like::TYPE); $interface->save($like); - $this->streamActionService->setActionBool($actor->getId(), $postId, 'liked', true); + $this->streamActionService->setActionBool($actor->getId(), $postId, StreamAction::LIKED, true); $token = $this->activityService->request($like); return $like; @@ -198,7 +198,7 @@ class LikeService { } catch (ItemNotFoundException $e) { } - $this->streamActionService->setActionBool($actor->getId(), $postId, 'liked', false); + $this->streamActionService->setActionBool($actor->getId(), $postId, StreamAction::LIKED, false); return $undo; } From 9d5343f0eae74ab6fbe280d4f353933efa9b2e73 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 27 Sep 2019 07:29:21 +0200 Subject: [PATCH 2/4] switch to unique_index Signed-off-by: Maxence Lange --- lib/Migration/Version0002Date20190916000002.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Migration/Version0002Date20190916000002.php b/lib/Migration/Version0002Date20190916000002.php index 8a5fb22a..bd18c1fa 100644 --- a/lib/Migration/Version0002Date20190916000002.php +++ b/lib/Migration/Version0002Date20190916000002.php @@ -114,7 +114,7 @@ class Version0002Date20190916000002 extends SimpleMigrationStep { $table = $schema->getTable('social_a2_stream_action'); if (!$table->hasIndex('sa')) { - $table->addIndex(['stream_id_prim', 'actor_id_prim'], 'sa'); + $table->addUniqueIndex(['stream_id_prim', 'actor_id_prim'], 'sa'); } return $schema; From e32f4fa234e8f789883ce576bcd43f29b29f3f05 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 27 Sep 2019 07:53:05 +0200 Subject: [PATCH 3/4] rewrite sql for 'timeline liked' Signed-off-by: Maxence Lange --- lib/Db/CoreRequestBuilder.php | 18 ++++++++++++++++++ lib/Db/StreamRequest.php | 23 +++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index 8d75f7fe..7a437ce5 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -793,6 +793,24 @@ class CoreRequestBuilder { } + /** + * @param IQueryBuilder $qb + * @param string $alias + */ + protected function selectStreamActions(IQueryBuilder &$qb, string $alias = 'sa') { + if ($qb->getType() !== QueryBuilder::SELECT) { + return; + } + + $pf = (($alias === '') ? $this->defaultSelectAlias : $alias); + $qb->from(self::TABLE_STREAM_ACTIONS, $pf); + $qb->selectAlias('sa.id', 'streamaction_id') + ->selectAlias('sa.actor_id', 'streamaction_actor_id') + ->selectAlias('sa.stream_id', 'streamaction_stream_id') + ->selectAlias('sa.values', 'streamaction_values'); + } + + /** * @param IQueryBuilder $qb * @param string $fieldActorId diff --git a/lib/Db/StreamRequest.php b/lib/Db/StreamRequest.php index fe71ddb0..35fe8508 100644 --- a/lib/Db/StreamRequest.php +++ b/lib/Db/StreamRequest.php @@ -44,7 +44,6 @@ use OCA\Social\Model\ActivityPub\ACore; use OCA\Social\Model\ActivityPub\Actor\Person; use OCA\Social\Model\ActivityPub\Internal\SocialAppNotification; use OCA\Social\Model\ActivityPub\Object\Document; -use OCA\Social\Model\ActivityPub\Object\Like; use OCA\Social\Model\ActivityPub\Object\Note; use OCA\Social\Model\ActivityPub\Stream; use OCA\Social\Service\ConfigService; @@ -499,21 +498,29 @@ class StreamRequest extends StreamRequestBuilder { * * @param int $since * @param int $limit - * @param bool $localOnly * * @return Stream[] * @throws DateTimeException */ - public function getTimelineLiked(int $since = 0, int $limit = 5, bool $localOnly = true): array { + public function getTimelineLiked(int $since = 0, int $limit = 5): array { + if ($this->viewer === null) { + return []; + } + + $actorId = $this->viewer->getId(); + $qb = $this->getStreamSelectSql(); + $this->limitToType($qb, Note::TYPE); $this->limitPaginate($qb, $since, $limit); - $this->limitToType($qb, Note::TYPE); + $expr = $qb->expr(); + $this->selectCacheActors($qb, 'ca'); + $qb->andWhere($expr->eq('s.attributed_to_prim', 'ca.id_prim')); - $this->leftJoinStreamAction($qb); - $this->leftJoinCacheActors($qb, 'attributed_to'); - $this->leftJoinActions($qb, Like::TYPE); - $this->filterDBField($qb, 'id', '', false, 'a'); + $this->selectStreamActions($qb, 'sa'); + $qb->andWhere($expr->eq('sa.stream_id_prim', 's.id_prim')); + $qb->andWhere($expr->eq('sa.actor_id_prim', $qb->createNamedParameter($this->prim($actorId)))); + $qb->andWhere($expr->eq('sa.liked', $qb->createNamedParameter(1))); return $this->getStreamsFromRequest($qb); } From ae9e03a2b19ad92ffcf02a90f648f2152c2ce09a Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 27 Sep 2019 15:13:51 +0200 Subject: [PATCH 4/4] save as int instead of bool Signed-off-by: Maxence Lange --- lib/Db/StreamActionsRequest.php | 24 +++++++++++-------- .../Version0002Date20190925000001.php | 6 ++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/Db/StreamActionsRequest.php b/lib/Db/StreamActionsRequest.php index 5242b98d..99cf906d 100644 --- a/lib/Db/StreamActionsRequest.php +++ b/lib/Db/StreamActionsRequest.php @@ -52,6 +52,10 @@ class StreamActionsRequest extends StreamActionsRequestBuilder { $qb = $this->getStreamActionInsertSql(); $values = $action->getValues(); + $liked = $this->getBool(StreamAction::LIKED, $values, false); + $boosted = $this->getBool(StreamAction::BOOSTED, $values, false); + $replied = $this->getBool(StreamAction::REPLIED, $values, false); + $qb->setValue('actor_id', $qb->createNamedParameter($action->getActorId())) ->setValue('actor_id_prim', $qb->createNamedParameter($this->prim($action->getActorId()))) ->setValue('stream_id', $qb->createNamedParameter($action->getStreamId())) @@ -61,13 +65,9 @@ class StreamActionsRequest extends StreamActionsRequestBuilder { json_encode($values, JSON_UNESCAPED_SLASHES) ) ) - ->setValue('liked', $qb->createNamedParameter($this->getBool(StreamAction::LIKED, $values, false))) - ->setValue( - 'boosted', $qb->createNamedParameter($this->getBool(StreamAction::BOOSTED, $values, false)) - ) - ->setValue( - 'replied', $qb->createNamedParameter($this->getBool(StreamAction::REPLIED, $values, false)) - ); + ->setValue('liked', $qb->createNamedParameter(($liked) ? 1 : 0)) + ->setValue('boosted', $qb->createNamedParameter(($boosted) ? 1 : 0)) + ->setValue('replied', $qb->createNamedParameter(($replied) ? 1 : 0)); $qb->execute(); } @@ -84,10 +84,14 @@ class StreamActionsRequest extends StreamActionsRequestBuilder { $qb = $this->getStreamActionUpdateSql(); $values = $action->getValues(); + $liked = $this->getBool(StreamAction::LIKED, $values, false); + $boosted = $this->getBool(StreamAction::BOOSTED, $values, false); + $replied = $this->getBool(StreamAction::REPLIED, $values, false); + $qb->set('values', $qb->createNamedParameter(json_encode($values, JSON_UNESCAPED_SLASHES))) - ->set('liked', $qb->createNamedParameter($this->getBool(StreamAction::LIKED, $values, false))) - ->set('boosted', $qb->createNamedParameter($this->getBool(StreamAction::BOOSTED, $values, false))) - ->set('replied', $qb->createNamedParameter($this->getBool(StreamAction::REPLIED, $values, false))); + ->set('liked', $qb->createNamedParameter(($liked) ? 1 : 0)) + ->set('boosted', $qb->createNamedParameter(($boosted) ? 1 : 0)) + ->set('replied', $qb->createNamedParameter(($replied) ? 1 : 0)); $this->limitToActorId($qb, $action->getActorId()); $this->limitToStreamId($qb, $action->getStreamId()); diff --git a/lib/Migration/Version0002Date20190925000001.php b/lib/Migration/Version0002Date20190925000001.php index 606bf810..50ea07bf 100644 --- a/lib/Migration/Version0002Date20190925000001.php +++ b/lib/Migration/Version0002Date20190925000001.php @@ -146,9 +146,9 @@ class Version0002Date20190925000001 extends SimpleMigrationStep { $streamId = $data['stream_id']; $values = json_decode($data['values'], true); - $liked = (int)((array_key_exists('liked', $values)) ? $values['liked'] : 0); - $boosted = (int)((array_key_exists('boosted', $values)) ? $values['boosted'] : 0); - $replied = (int)((array_key_exists('replied', $values)) ? $values['replied'] : 0); + $liked = (array_key_exists('liked', $values) && ($values['liked'])) ? 1 : 0; + $boosted = (array_key_exists('boosted', $values) && $values['boosted']) ? 1 : 0; + $replied = (array_key_exists('replied', $values) && $values['replied']) ? 1 : 0; $update->set('actor_id_prim', $update->createNamedParameter(hash('sha512', $actorId))); $update->set('stream_id_prim', $update->createNamedParameter(hash('sha512', $streamId)));