discourse: save superfluous query of `likes` table (#1302)

Summary:
When inserting a “like” action with `INSERT OR IGNORE` semantics, we
also learn whether the action had any effect. We can use this bit to
avoid a separate query checking whether the “like” already exists.

As mentioned here:
<https://github.com/sourcecred/sourcecred/pull/1298#discussion_r314994911>

Test Plan:
Running `yarn test` passes as is, and fails if you change `addLike` to
always return either `changed: true` or `changed: false`.

wchargin-branch: discourse-likes-one-query
This commit is contained in:
William Chargin 2019-08-18 12:00:59 -07:00 committed by GitHub
parent 5e26424d82
commit 9fc1482d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 32 additions and 22 deletions

View File

@ -391,39 +391,49 @@ export class Mirror implements DiscourseData {
// who we either haven't scanned in the last week, or who have been active // who we either haven't scanned in the last week, or who have been active
// since our last scan. This would likely improve the performance of this // since our last scan. This would likely improve the performance of this
// section of the update significantly. // section of the update significantly.
const insertLike = db.prepare(
/**
* Add a like action to the database. The user of the like is
* assumed to already exist in the database; if this is not known to
* be the case, run `addUser(like.username)` first.
*
* Returns a status indicating whether the database changed as a
* result of this call.
*/
const addLike: (like: LikeAction) => {|+changed: boolean|} = (() => {
const query = db.prepare(
dedent`\ dedent`\
INSERT INTO likes ( INSERT OR IGNORE INTO likes (
post_id, timestamp_ms, username post_id,
timestamp_ms,
username
) VALUES ( ) VALUES (
:post_id, :timestamp_ms, :username :post_id,
:timestamp_ms,
:username
) )
` `
); );
const alreadySeenLike = db return function addLike(like: LikeAction) {
.prepare( const runResult = query.run({
dedent`\ post_id: like.postId,
SELECT * FROM likes timestamp_ms: like.timestampMs,
WHERE post_id = :post_id AND username = :username username: like.username,
` });
) return {changed: runResult.changes > 0};
.pluck(); };
})();
for (const user of this.users()) { for (const user of this.users()) {
let offset = 0; let offset = 0;
let upToDate = false; let upToDate = false;
while (!upToDate) { while (!upToDate) {
const likeActions = await this._fetcher.likesByUser(user, offset); const likeActions = await this._fetcher.likesByUser(user, offset);
possiblePageSize = Math.max(likeActions.length, possiblePageSize); possiblePageSize = Math.max(likeActions.length, possiblePageSize);
for (const {timestampMs, postId, username} of likeActions) { for (const like of likeActions) {
if (alreadySeenLike.get({post_id: postId, username: username})) { if (!addLike(like).changed) {
upToDate = true; upToDate = true;
break; break;
} }
insertLike.run({
post_id: postId,
timestamp_ms: timestampMs,
username: username,
});
} }
if (likeActions.length === 0 || likeActions.length < possiblePageSize) { if (likeActions.length === 0 || likeActions.length < possiblePageSize) {
upToDate = true; upToDate = true;